Tern字幕翻译软件逆向记录
Tern(官网https://zh.tern.best)是一个字幕翻译的软件,他通过调用主流翻译平台的接口(需要自己注册)进行字幕翻译,说白了就是写了个gui。但是他免费版每月是限制字符数的。因此。。。
提取
这是一个eletron应用,外面就是一层chrome的壳,真正逻辑在resources/app.asar
,用npm安装asar工具对其解压。
npm -g install asar
asar e app.asar app
随便翻了翻,可以看到主要逻辑在js/app.cca879ab.js
里,本来要读这个打包出来文件非常头疼,但是这个软件竟然把sourcemap也打包了出来,那就直接用工具恢复源文件吧。这里用的是reverse-sourcemap,用起来很方便,只要reverse-sourcemap <目标文件>
就行了。
恢复之后就可以愉快的分析代码了(甚至还有详细的注释)。
分析
在源代码里乱翻找到了js/webpak/src/config/index.js
,好家伙,免费额度都直接写在这里了。
// js/webpak/src/config/index.js
// 配置
const one_minutes = 60
const one_hours = 60 * one_minutes
exports.config = {
free_plan_monthly_char_limit: 1000000, // 字幕翻译: 每月免费额度 (单位: 字符数)
// 这是100万字符
free_plan_monthly_duration_limit_in_seconds: one_hours * 10, // 语音转文字: 每月免费时长 (单位: 秒)
mianbaoduo_lifetime: "YZ6Vmps=",
mianbaoduo_7_day: "YZ6Vmpk=",
mianbaoduo_14_day: "YpyTl5s=",
mianbaoduo_30_day: "YZ2Zmpc=",
mianbaoduo_30_day_repeat: "Y5ubmJY=", // 30天可复购的版本
mianbaoduo_60_day: "YZ2ZmpY=",
mianbaoduo_90_day: "YZ2YmZ8=",
mianbaoduo_120_day: "YpWWm5s=",
mianbaoduo_valid_urlkey: [
"YZ6Vmps=", "YZ6Vmpk=", "YpyTl5s=", "YZ2Zmpc=", "YZ2ZmpY=", "YZ2YmZ8=", "YpWWm5s=", "Y5ubmJY="
],
mianbaoduo_developer_key: "77567:1iwLac:JEhBXIb4hcI5fl9_OeE8N5F2aBY",
doc_root: 'https://doc.tern.1c7.me/',
doc_root_cn: 'https://doc.tern.1c7.me/zh/'
}
但光提高免费额度肯定是不够的(目标当然是无限额),下面又定义了一些base64编码的字符串,目前还不知道是做什么用的,但是看名字就知道肯定和付费有关,那就全局搜索这些字符串的键,然后找到了js/webpack/src/lib/mianbaoduo.js
文件,翻了一下人傻了,测试用的订单号写注释里可还行,试了一下真可以用。(就放一部分出来给你们馋馋)
// 一些测试用的订单号
// f0030d99307cdc1d98d007a45fa611** // 7天
// 6a922475f64c2012cfb9bb4303c109** // 14天
// d8f3ed76a0d9706fecdbf13b415d98** // 30天
// 084c3ad39ac24f2c242ab493daa380** // 60天
// 3dd86e87e3f5dd41f39c71d4c11793** // 90天
// b32a367006c807271fc89861c082d6** // 120天
// c5c0960a5126058c0ab452763e405d** // 永久
// 1156121ab3602bf9576413b3c48d81** // 30天(可复购) 8月25号过期 (我自己买的测试版)
// 2bc7e9a6734f00fe75a58394881e86** // 30天, 复购了一次的版本, 第一次是7月11号过期,然后复购了一个月8月12号过期
这我肯定还是不满意,继续往下看http_order_detail
函数是在检验激活码,再往上找,找到调用它的函数,在js/webpack/src/mixin_plan.js
里面的validate_mianbaoduo_order_id
函数。
async validate_mianbaoduo_order_id() {
var that = this;
this.license_verifying_state = this.state_dict.verifying;
var order_id = this.mianbaoduo_order_id;
var mian_bao_duo = new MianBaoDuo({
order_id: order_id,
developer_key: config.mianbaoduo_developer_key,
valid_urlkeys: config.mianbaoduo_valid_urlkey
});
await mian_bao_duo.get_order_detail();
// 如果是个无效订单
if (mian_bao_duo.order_is_valid() == false) {
that.license_verifying_state = that.state_dict.invalid;
that.set_plan_free();
that.remove_license_code();
return;
}
// 如果 urlkey 不在有效数组内, 这里就直接返回了,无需执行后续判断
if (mian_bao_duo.urlkey_is_valid() == false) {
that.license_verifying_state = that.state_dict.invalid;
that.set_plan_free();
that.remove_license_code();
return;
}
var response = mian_bao_duo.order_info; // 订单的结果
var urlkey = response.result.urlkey;
// 如果是永久激活码
if (mian_bao_duo.is_lifetime_license()) {
that.license_verifying_state = that.state_dict.valid;
that.set_plan_pro();
that.set_license_code(order_id);
return;
}
var order_time = response.result.ordertime; // 支付时间的时间戳 timestamp like 1580205634
var orderamount = response.result.orderamount; // 支付金额
that.set_pay_time(order_time);
that.set_orderamount(orderamount);
var order_time_readable = moment
.unix(order_time)
.format("YYYY-MM-DD HH:mm:ss");
// 2020-01-28 18:00:34
// 7天/14天/30/60/90/120天
var valid_for_x_days = null;
switch (urlkey) {
case config.mianbaoduo_7_day:
valid_for_x_days = 7;
break;
case config.mianbaoduo_14_day:
valid_for_x_days = 14;
break;
case config.mianbaoduo_30_day:
valid_for_x_days = 30;
break;
case config.mianbaoduo_60_day:
valid_for_x_days = 60;
break;
case config.mianbaoduo_90_day:
valid_for_x_days = 90;
break;
case config.mianbaoduo_120_day:
valid_for_x_days = 120;
break;
default:
// 除非是30天复购版, 否则不会到 default 这里
// 这个复购版我们在后面单独处理
break;
}
if (valid_for_x_days != null) {
// 如果已过期
if (
that.now_is_after_ordertime_plus_day(order_time, valid_for_x_days)
) {
that.license_verifying_state = that.state_dict.expired;
that.expire_message = that.$t("plan.license_expired", {
date: order_time_readable,
day: valid_for_x_days,
expired_date: this.get_expired_datetime(
order_time,
valid_for_x_days
).format("YYYY-MM-DD HH:mm:ss")
});
that.set_plan_free();
that.remove_license_code();
return;
} else {
that.set_order_type(`${valid_for_x_days}天`);
that.set_expire_time(order_time, valid_for_x_days);
that.set_plan_limited();
that.set_license_code(order_id);
}
}
// 30天可复购版
if (urlkey == config.mianbaoduo_30_day_repeat) {
var valid = response.result.state == "success";
var re_exists = response.result.re != undefined; // 这个 re 字段代表是复购的,里面有复购的信息
if (valid) {
that.set_order_type("30天 (可复购)"); // 显示给用户看的
that.set_plan_limited();
that.set_license_code(order_id);
// 如果是复购的,过期时间和购买时间从 re 里面拿
if (re_exists) {
var expire_timestamp = response.result.re.expire_at;
that.set_expire_time_by_timestamp(expire_timestamp);
var order_timestamp = response.result.re.ordertime;
that.set_pay_time(order_timestamp);
} else {
that.set_expire_time_by_timestamp(response.result.expire_at);
}
} else {
that.license_verifying_state = that.state_dict.expired;
var expire_timestamp = null;
if (re_exists) {
expire_timestamp = response.result.re.expire_at;
} else {
expire_timestamp = response.result.expire_at;
}
var expired_date = moment
.unix(expire_timestamp)
.format("YYYY-MM-DD HH:mm:ss");
that.expire_message = that.$t("plan.license_expired_just_date", {
expired_date: expired_date
});
that.set_plan_free();
that.remove_license_code();
}
}
},
这是完整的校验逻辑,直接把这个改掉(改那个打包过的文件),换成永久激活的逻辑就行了。再用asar
重新打包一份,替换原来的app.asar
就完成了。
其它
逆向过程中还搜到了一个恢复chrome生成的pak文件的工具chrome-pak-customizer,这里也记录一下。
如有侵权,联系删除。