阴阳颠倒颠

发布于
拆解爱词霸的前端签名

静观龙虎战场战,暗把阴阳颠倒颠。 人言我是蒙眬汉,我却眠兮眠未眠。

之前用腾讯的翻译接口,后来不能用了。最近看到原来金山词霸有 在线翻译

本着互联网分享和学习的精神打开了控制台。

定位到翻译的方法

首先输入内容自动翻译的时候会调用接口,控制台会打印出响应的内容,于是很快定位到对应的 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-jsAES 解密,key 都写在前端代码里边了。不传入就用默认的 key。

找到签名的方法

处理完返回,再来处理入参。

这里有 t.tot.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 再进行加密,意义不大,虽然有编译之后的混淆,花一些时间,还是能还原对应的方法。

爱词霸没有做跨域限制,直接在本地可以调通。

screen shot

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