静观龙虎战场战,暗把阴阳颠倒颠。 人言我是蒙眬汉,我却眠兮眠未眠。
之前用腾讯的翻译接口,后来不能用了。最近看到原来金山词霸有 在线翻译。
本着互联网分享和学习的精神打开了控制台。
定位到翻译的方法
首先输入内容自动翻译的时候会调用接口,控制台会打印出响应的内容,于是很快定位到对应的 js 文件,看到翻译的对应的方法:
{
takeResult: function(e) {
var t = l().parse(e);
(null === t || void 0 === t ? void 0 : t.to) && "jp" === t.to && (t.to = "ja"),
(null === t || void 0 === t ? void 0 : t.from) && "jp" === t.from && (t.from = "ja"),
e = l().stringify(t);
var r = y()("6key_web_new_fanyi".concat(w.LI).concat(t.q.replace(/(^\s*)|(\s*$)/g, ""))).toString().substring(0, 16);
return r = (0,
_.$Q)(r),
(0,
n.ZP)("/index.php?c=trans&m=fy&client=6&auth_user=key_web_new_fanyi&sign=".concat(encodeURIComponent(r)), {
baseURL: "//ifanyi.iciba.com",
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: e
}).then((function(e) {
var t = 1 === (null === e || void 0 === e ? void 0 : e.status) ? C(C({}, e), {}, {
content: JSON.parse((0,
_.B6)(e.content))
}) : e;
return console.log(t),
t
}
)).catch((function(e) {
return e
}
))
}
}
整个请求看起来还算比较干净的,表单提交,带上固定的查询字符串和一个签名 sign,表单内容 from to 就是来源语言和目标语言,q 是要翻译的文本。
查看网络里边,返回的 content 是一个密文。
处理返回的密文
可以看到是将返回的内容,通过 B6 处理了,然后解析成为 JSON 对象。
在 _app-2e45a07fdca92c4b.js 文件里边找到 B6,最后定位到这里:
function f(e) {
var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "aahc3TfyfCEmER33";
if (!e)
return null;
var r = n.enc.Utf8.parse(t)
, o = n.AES.decrypt(e, r, {
mode: n.mode.ECB,
padding: n.pad.Pkcs7
});
return o.toString(n.enc.Utf8)
}
这里一眼可以看出来是 crypto-js 的 AES 解密,key 都写在前端代码里边了。不传入就用默认的 key。
找到签名的方法
处理完返回,再来处理入参。
这里有 t.to 和 t.from,所以 t 应该就是原始的入参了。
核心的逻辑是这一串
y()("6key_web_new_fanyi".concat(w.LI).concat(t.q.replace(/(^\s*)|(\s*$)/g, ""))).toString().substring(0, 16);
固定的 6key_web_new_fanyi 拼接 LI 拼接翻译的文字去掉首尾空格。然后再经过 y() 处理,截取 16 位长度,再经过 _.$Q 处理。
_.$Q 应该是全局的方法,定位到这里:
s = function(e) {
e = decodeURIComponent(e);
for (var t = String.fromCharCode(e.charCodeAt(0) - e.length), r = 1; r < e.length; r++)
t += String.fromCharCode(e.charCodeAt(r) - t.charCodeAt(r - 1));
return t
};
function l(e) {
var t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "%5C%C2%80%C2%9A%C2%A8%C2%B6%C2%B8y%C2%9B%C2%B2%C2%8F%7C%7F%C2%97%C3%88%C2%A9d"
, r = n.enc.Utf8.parse(s(t))
, o = n.AES.encrypt(e, r, {
mode: n.mode.ECB,
padding: n.pad.Pkcs7
});
return o.toString()
}
大概是将固定的一串字符,按位计算,然后作为 key,把输入的字符来 AES 加密。
y() 函数最后定位到一串逻辑,最后是 MD5,可以直接用 js-md5 试试。
找到 LI 是将一串固定字符传入 e$
, u = (0,
n.e$)("D%C2%9A%C2%BA%C3%80%C3%83%C2%A5%C2%92%C2%BF%C3%B3%C3%A0%C3%91%C2%B1%C2%B1%C3%96")
e$ 这里又是一个位计算,和之前的 _.$Q 是一样的。
s = function(e) {
e = decodeURIComponent(e);
for (var t = String.fromCharCode(e.charCodeAt(0) - e.length), r = 1; r < e.length; r++)
t += String.fromCharCode(e.charCodeAt(r) - t.charCodeAt(r - 1));
return t
}
就这样将这些函数组合到一起,就可以正确的处理输入输出了。
// 输出 YuhzphFoZ8yX5Z5qqLEMy9RriqVIAJSQ+xmfU0q7dIE=
// 输入 财经发
// 输出 sOasmaRfsCymoFGNqWzJT9RriqVIAJSQ+xmfU0q7dIE=
// 输入 财经
成果
总结一下,前端代码写入了固定的 key 再进行加密,意义不大,虽然有编译之后的混淆,花一些时间,还是能还原对应的方法。
爱词霸没有做跨域限制,直接在本地可以调通。

本文仅做研究学习,请遵守法律法规,不要滥用他人服务。