逆向系列-某翻译加密与解密过程

查看 136|回复 10
作者:LinCode   
逆向系列之某D翻译
案例仅作为学习使用
目标网站
aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tL2luZGV4Lmh0bWwjLw==
分析数据包
首先打开开发者工具—>网络—>XHR
接下来在文本框中随意输入一个英语单词,看看会传递出一个什么数据包。

经过测试,输入一个单词之后会出现两个数据包,分别是webtranslate和key

注意:
这两个数据包的请求方式均为post

点开webtranslate,分析请求头有没有不寻常的值。

很正常。
接下来,分析请求参数

从上图可以看到,i为需要翻译的单词,sign值是头部加密,mysticTime值为当前时间的时间戳。

接下来再测试一个单词,发现变化的值只有sign和mysticTime。
调试数据
1、断点调试
webtranslate的URL为:https://dict.youdao.com/webtranslate
结合上一篇文章的思路,继续断点URL的路径。

接下来点击左侧的调用堆栈,看看它是怎么样运行到这一步的。当然最重要的是找到数据包的URL在哪个程序。

终于一个个往下走的时候找到了目标所在的位置,这是一个箭头函数。在这里JavaScript基础比较薄弱的同学,我为大家普及一下JavaScript的常用函数声明方式。
  • 传统的调用方式

    它以关键字function开头
    function greet(name){
        return `hello, ${name}`
    }
    // 函数的调用
    console.log(greet("Jack"))
  • 函数表达式

    它是把一个函数赋值给一个变量,这种创建函数的方式可以是匿名的也可也是命名的。
    const sayGoodBye = function (name){
        return `GoodBye, ${name}`
    }
    console.log(sayGoodBye("Jack"))
  • 箭头函数

    ES6语法提供了更简洁的箭头函数
    const sayHello = (name) => `hello ${name}`
    console.log(sayHello("jack"))
    箭头后面的是返回值
    当然如果函数的内容比较复杂,依然可以使用花括号。
    const sayHello = (name) => {
        return `hello ${name}`
    }
    接下来回到刚刚js中的代码,并把它拷贝下来
    B = (e, t) => Object(a['d']) (
              'https://dict.youdao.com/webtranslate',
              Object(n['a']) (Object(n['a']) ({
              }, e), E(t)),
              {
                headers: {
                  'Content-Type': 'application/x-www-form-urlencoded'
                }
              }
            )
    稍微将上面的代码进行简单修改
    const B = (e, t) => a.d(
        'https://dict.youdao.com/webtranslate',
        n(['a']) (n['a']) ({}, e), E(t),
         {
                headers: {
                  'Content-Type': 'application/x-www-form-urlencoded'
                }
         }
    )

    这个B函数,传递两个参数,分别是t和e
    t值为:"fsdsogkndfokasodnaso"
    e值为请求参数的一部分
    Object { i: "app", from: "auto", to: "", domain: "0", dictResult: true, keyid: "webfanyi" }
    另外它的返回值也是一个函数,那要看看里面的函数究竟做了什么,可以在这里打一个断点。

    const B = (e, t) => a.d(
        'https://dict.youdao.com/webtranslate',
        n(['a']) (n['a']) ({}, e), E(t),
         {
                headers: {
                  'Content-Type': 'application/x-www-form-urlencoded'
                }
         }
    )
    相当于我在a.d处打了一个断点,看看这个a.d主要做了什么事情。a.d一共传递了三个参数:
    url: https://dict.youdao.com/webtranslate
    未知:n(['a']) (n['a']) ({}, e), E(t)
    headers:'Content-Type': 'application/x-www-form-urlencoded'

    注意: n(['a']) (n['a']) ({}, e)和E(t)做了拼接

    接下来在控制台中输出未知的参数,看看到底是一个什么

    一种久违的熟悉感又回来了吧。这个不就是webtranslate的数据包的的请求参数的一部分吗?加上前面的e的参数就齐全了。
    简单做个判断,应该是这个函数携带着三个参数向目标发起一个请求,获取到值。
    接下来,分析a.d函数的逻辑,看看它里面的请求参数从哪里过来的。

    function u(e, t, o) {
          return new Promise(
            (n, i) => {
              a['a'].post(e, t, o).then(e => {
                n(e.data)
              }).catch(e => {
                i(e)
              })
            }
          )
        }
    这里声明了一个Promise函数,大致意思就是成功调用n函数处理;失败调用i函数处理。
    接下来回到断点处,上面说到,第二个参数做了拼接。

    E(t)主要负责得到sign参数


    拨开云雾见月明
    function E(e, t) {
              const o = (new Date).getTime();
              return {
                sign: k(o, e),
                client: u,
                product: d,
                appVersion: p,
                vendor: g,
                pointParam: m,
                mysticTime: o,
                keyfrom: b,
                mid: A,
                screen: h,
                model: f,
                network: v,
                abtest: O,
                yduuid: t ||
                'abcdefg'
              }
            }
    从上上面可以看到sign值是通过k函数获取得到的。
    继续打个断点


    E函数里面传递了两个参数:
    e: fsdsogkndfokasodnaso
    t: undefined,不需要写实际是传递了一个参数
    从上图不难发现,它的逻辑,sign通过调用K函数,K函数调用j函数
    整体代码逻辑:
    function j(e) {
              return c.a.createHash('md5').update(e.toString()).digest('hex')
            }
    function k(e, t) {
        return j(`client=${ u }&mysticTime=${ e }&product=${ d }&key=${ t }`)
    }
    function E(e, t) {
        const o = (new Date).getTime();
        return {
            sign: k(o, e),
            client: u,
            product: d,
            appVersion: p,
            vendor: g,
            pointParam: m,
            mysticTime: o,
            keyfrom: b,
            mid: A,
            screen: h,
            model: f,
            network: v,
            abtest: O,
            yduuid: t ||
            'abcdefg'
        }
    }
    // 调用E函数
    console.log(E("fsdsogkndfokasodnaso"))
    执行上面的代码出现了如下错误:
    ReferenceError: u is not defined

    u值为:fanyideskweb
    按ctrl+F进行搜索,看看它的值在哪里

    将上述js代码再进行修改一次
    const u = 'fanyideskweb',
            d = 'webfanyi',
            m = 'client,mysticTime,product',
            p = '1.0.0',
            g = 'web',
            b = 'fanyi.web',
            A = 1,
            h = 1,
            f = 1,
            v = 'wifi',
            O = 0;
    function j(e) {
              return c.a.createHash('md5').update(e.toString()).digest('hex')
            }
    function k(e, t) {
        return j(`client=${ u }&mysticTime=${ e }&product=${ d }&key=${ t }`)
    }
    function E(e, t) {
        const o = (new Date).getTime();
        return {
            sign: k(o, e),
            client: u,
            product: d,
            appVersion: p,
            vendor: g,
            pointParam: m,
            mysticTime: o,
            keyfrom: b,
            mid: A,
            screen: h,
            model: f,
            network: v,
            abtest: O,
            yduuid: t ||
            'abcdefg'
        }
    }
    console.log(E("fsdsogkndfokasodnaso"))
    运行之后出现如下错误:
              return c.a.createHash('md5').update(e.toString()).digest('hex')
              ^
    ReferenceError: c is not defined
    这个是node.js独有的加密逻辑
    const CryptJs = require("crypto")
    const u = 'fanyideskweb',
            d = 'webfanyi',
            m = 'client,mysticTime,product',
            p = '1.0.0',
            g = 'web',
            b = 'fanyi.web',
            A = 1,
            h = 1,
            f = 1,
            v = 'wifi',
            O = 0;
    function j(e) {
              return CryptJs.createHash('md5').update(e.toString()).digest('hex')
            }
    function k(e, t) {
        return j(`client=${ u }&mysticTime=${ e }&product=${ d }&key=${ t }`)
    }
    function E(e, t) {
        const o = (new Date).getTime();
        return {
            sign: k(o, e),
            client: u,
            product: d,
            appVersion: p,
            vendor: g,
            pointParam: m,
            mysticTime: o,
            keyfrom: b,
            mid: A,
            screen: h,
            model: f,
            network: v,
            abtest: O,
            yduuid: t ||
            'abcdefg'
        }
    }
    console.log(E("fsdsogkndfokasodnaso"))
    运行结果
    {
      sign: '40b198684fe0d944ab0324a6cd54a403',
      client: 'fanyideskweb',
      product: 'webfanyi',
      appVersion: '1.0.0',
      vendor: 'web',
      pointParam: 'client,mysticTime,product',
      mysticTime: 1704772886214,
      keyfrom: 'fanyi.web',
      mid: 1,
      screen: 1,
      model: 1,
      network: 'wifi',
      abtest: 0,
      yduuid: 'abcdefg'
    }
    注意这个请求参数是不全的,因此不能忘记了还需要不全
    最终JS代码
    const CryptJs = require("crypto")
    const u = 'fanyideskweb',
            d = 'webfanyi',
            m = 'client,mysticTime,product',
            p = '1.0.0',
            g = 'web',
            b = 'fanyi.web',
            A = 1,
            h = 1,
            f = 1,
            v = 'wifi'
            O = 0;
    function j(e) {
              return CryptJs.createHash('md5').update(e.toString()).digest('hex')
            }
    function k(e, t) {
        return j(`client=${ u }&mysticTime=${ e }&product=${ d }&key=${ t }`)
    }
    function E(word, t) {
        let e = "fsdsogkndfokasodnaso"
        const o = (new Date).getTime();
        return {
            sign: k(o, e),
            client: u,
            product: d,
            appVersion: p,
            vendor: g,
            pointParam: m,
            mysticTime: o,
            keyfrom: b,
            mid: A,
            screen: h,
            model: f,
            network: v,
            abtest: O,
            yduuid: t ||
            'abcdefg',
            i: word,
            from: "auto",
            to: "",
            domain: "0",
            dictResult: true,
            keyid: "webfanyi"
        }
    }
    // console.log(E("app"))
    python代码
    import requests
    import execjs
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
        'Referer': '',
        'Cookie': ''
    }
    url = 'https://dict.youdao.com/webtranslate'
    with open('xzyoudao.js', 'r', encoding='utf8') as f:
        js = f.read()
    data = execjs.compile(js).call('E', 'apple')
    print(data)
    response = requests.post(url, headers=headers, data=data)
    print(response.text)
    运行结果是一串加密数据
    数据解密
    B = (e, t) => Object(a['d']) (
              'https://dict.youdao.com/webtranslate',
              Object(n['a']) (Object(n['a']) ({
              }, e), E(t)),
              {
                headers: {
                  'Content-Type': 'application/x-www-form-urlencoded'
                }
              }
            )
    刚刚有和大家分析过,这串代码的返回值是Object(n['a'],那么在这里继续打上断点。


    这个函数里面的e.data的值为
    "Z21kD9ZK1ke6ugku2ccWu-MeDWh3z252xRTQv-wZ6jd-f4VUaQOlThzHO02JcemZpYOzjRE7JK2Ol9PAth_lYO-ciiXQHRoDmiwMfUZ_6N8_yowUeIEJcbxPWJgY7dNYI4YyWpTc5DwB-Np7jdWB-O_x9nmNUoTP6Fyy1HVZWNTaxsHqw9N9NLEk3pek0iD_4LW7uWDilzVNEQ1jRbhDfcFCwPO40bM_pDjcYzkA4_AGiuoDWBMViRpVylnZ1cx3NXnZ6bJxX8wYgsKiDlfOj0ATQNdpDmuoo99ChkRcEgB8nhKHpxczVqfjl4L1ATBLIwgcllUGsTXtcIWwa24-AW81-c-C-iQ4rqNWYMo5J-AscCAl4JI_2OiAWdlfx5yFmRtLSepPBXGHa4-tww9pcM9rfE0vgz0Muf4SND5W_XNGv5NPKDiOf2prb-kQxX5pwE_DtuKVNhsws6acQC5NmGd3zm3njaj1ckG9egENVfzAYASvmQShnCLCkIyiFxAuK_n2AmgkMfYLuEcFgIsq-DNN4S0Z8NaKL3yqTTCmrNIrEImUqB4UfwgVm6CPa1Twt5LM6UjhgGUumThZswdKysm9sHoXLInlk5mICA1P8h2emAHO3VUxWS15U07mescRHF5e1gLoWnInX7QNROVBxslLHX6SCqKRUFEz3OA8OEmpIUwbmA6ej0E0vIlTEER5"
    刚好这个就是加密数据。那么它是被哪个函数所执行的呢?也就是说要找到当前函数的上一层函数。

    B指的是当前函数,而上一层函数是Qo
    刚刚的函数主要是发起请求,然后获取返回值,获取到返回值后由then进行处理。

    密文数据就是o
    o => {
                Po['a'].cancelLastGpt();
                const a = Po['a'].decodeData(o, Wo['a'].state.text.decodeKey, Wo['a'].state.text.decodeIv),
                n = a ? JSON.parse(a) : {
                };
                0 === n.code ? e.success &&
                t(e.success) (n) : e.fail &&
                t(e.fail) (n)
              }
    不难看出,这里的函数decodeData传递了三个参数,分别是o(加密数据)、decodeKey(密钥)、decodeIv(偏移量),很明显了这个就是解密函数
    紧接着在控制台获取密钥和偏移量

    function decodeData(){
        var decodeKey = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl";
        var decodeIv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4";
    }
    接下来分析decodeData函数


    继续打个断点测试


    在控制台就可以看到s就是解密值
    // t:密文
    function decodeData(t){
        var decodeKey = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl";
        var decodeIv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4";
         if (!t) return null;
              const a = e.alloc(16, y(decodeKey)),
              i = e.alloc(16, y(decodeIv)),
              r = CryptJs.createDecipheriv('aes-128-cbc', a, i);
              let s = r.update(t, 'base64', 'utf-8');
              return s += r.final('utf-8'),
              s
    }
    紧接着继续分析

    注意:
    1、alloc是node下的Buffer模块下的
    2、在代码中还有一个y函数



    原来也是md5加密
    最后的JS代码
    function y(e) {
              return CryptJs.createHash('md5').update(e).digest()
            }
    function decodeData(t){
        var decodeKey = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl";
        var decodeIv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4";
         if (!t) return null;
              const a = Buffer.alloc(16, y(decodeKey)),
              i = Buffer.alloc(16, y(decodeIv)),
              r = CryptJs.createDecipheriv('aes-128-cbc', a, i);
              let s = r.update(t, 'base64', 'utf-8');
              return s += r.final('utf-8'),
              s
    }

    终于测试成功!
    最后代码(Javascript)
    const CryptJs = require("crypto")
    const u = 'fanyideskweb',
            d = 'webfanyi',
            m = 'client,mysticTime,product',
            p = '1.0.0',
            g = 'web',
            b = 'fanyi.web',
            A = 1,
            h = 1,
            f = 1,
            v = 'wifi'
            O = 0;
    function j(e) {
              return CryptJs.createHash('md5').update(e.toString()).digest('hex')
            }
    function k(e, t) {
        return j(`client=${ u }&mysticTime=${ e }&product=${ d }&key=${ t }`)
    }
    function y(e) {
              return CryptJs.createHash('md5').update(e).digest()
            }
    function E(word, t) {
        let e = "fsdsogkndfokasodnaso"
        const o = (new Date).getTime();
        return {
            sign: k(o, e),
            client: u,
            product: d,
            appVersion: p,
            vendor: g,
            pointParam: m,
            mysticTime: o,
            keyfrom: b,
            mid: A,
            screen: h,
            model: f,
            network: v,
            abtest: O,
            yduuid: t ||
            'abcdefg',
            i: word,
            from: "auto",
            to: "",
            domain: "0",
            dictResult: true,
            keyid: "webfanyi"
        }
    }
    function decodeData(t){
        var decodeKey = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl";
        var decodeIv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4";
         if (!t) return null;
              const a = Buffer.alloc(16, y(decodeKey)),
              i = Buffer.alloc(16, y(decodeIv)),
              r = CryptJs.createDecipheriv('aes-128-cbc', a, i);
              let s = r.update(t, 'base64', 'utf-8');
              return s += r.final('utf-8'),
              s
    }
    python代码
    import requests
    import execjs
    import json
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
        'Referer': '',
        'Cookie': ''
    }
    url = 'https://dict.youdao.com/webtranslate'
    with open('xzyoudao.js', 'r', encoding='utf8') as f:
        js = f.read()
    word = input('请输入你要翻译的单词:')
    data = execjs.compile(js).call('E', word)
    # print(data)
    response = requests.post(url, headers=headers, data=data)
    # 加密数据
    encrypt_data = response.text
    # # 解密数据
    decrypt_data = json.loads(execjs.compile(js).call('decodeData', encrypt_data))
    print(decrypt_data.get('dictResult').get('ec').get('word').get('trs')[0].get('tran'))

    函数, 参数

  • baliao   

    感谢详细地分享,我是菜鸟也能跟着做了. 我以前是用
    使用这个接口去翻译,啥解密都不用,音标,短语,中文,造句也有.
    http://dict.youdao.com/w/eng/
    zwp-peter   

    运行代码出现了以下错误是什么意思咧:
    Traceback (most recent call last):
      File "C:\Users\Administrator\Desktop\ptj\youdao\main.py", line 14, in
        data = execjs.compile(js).call('E', word)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "D:\Program Files\Python312\Lib\site-packages\execjs\_abstract_runtime_context.py", line 37, in call
        return self._call(name, *args)
               ^^^^^^^^^^^^^^^^^^^^^^^
      File "D:\Program Files\Python312\Lib\site-packages\execjs\_external_runtime.py", line 92, in _call
        return self._eval("{identifier}.apply(this, {args})".format(identifier=identifier, args=args))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "D:\Program Files\Python312\Lib\site-packages\execjs\_external_runtime.py", line 78, in _eval
        return self.exec_(code)
               ^^^^^^^^^^^^^^^^
      File "D:\Program Files\Python312\Lib\site-packages\execjs\_abstract_runtime_context.py", line 18, in exec_
        return self._exec_(source)
               ^^^^^^^^^^^^^^^^^^^
      File "D:\Program Files\Python312\Lib\site-packages\execjs\_external_runtime.py", line 88, in _exec_
        return self._extract_result(output)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "D:\Program Files\Python312\Lib\site-packages\execjs\_external_runtime.py", line 167, in _extract_result
        raise ProgramError(value)
    execjs._exceptions.ProgramError: SyntaxError: 语法错误
    LinkSun   

    很棒很棒,很详细
    janken   

    感谢分享!!
    MakoStar   

    感谢分享逆向过程!
    zcs2024   

    感谢分享逆向过程!
    fuchen197   

    谢谢分享
    moruye   

    感谢感谢,学习了学习了
    hao6988456   

    感谢大佬分享
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部