记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

记一次YD翻译接口逆向 

YD翻译接口由于其耐造,无混淆的特点,是一个很好的入门js逆向的练手目标.

js逆向相较于c/c++等编译为机器码的语言,可以较为容易的获得源码级的信息,所以逆向的重点就在于在调用堆栈中寻找到关键的加密,解密代码.将代码copy到本地后,进行补环境,就可运行.

逆向步骤

首先在翻译栏中进行输入 在网络下的xhr中可以看到发送的所有数据包 查看数据包内容 确定webtranslate为关键数据包

图片[1]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

再次在翻译栏中输入内容 对比两次的webtranslate 发现只有sign值进行变化 确定此次逆向的字段为sign

图片[2]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

图片[3]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

对数据包请求网址进行搜索

图片[4]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

定位到关键代码

图片[5]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

n.H为发送网络请求函数

  1. 请求URL:https://dict.youdao.com/webtranslate

  2. 请求参数:动态合并的对象

  3. 请求头:application/x-www-form-urlencoded

运行到断点处

图片[6]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

点进k找到加密逻辑

图片[7]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

将JS代码拿到本地 使用相同数据在浏览器和本地进行测试 得到相同的加密结果

图片[8]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

图片[9]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

编写python和js代码 成功获得返回后的数据 但是还需要解密

图片[10]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

在调用栈中找到解密相关代码

图片[11]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

看到返回的密文 确定此处为解密代码

decodeData(需要解密的密文, Key, Iv) 可知此处为aes cbc解密

图片[12]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

进入decodeData 可知Key, Iv为固定值

图片[13]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

编写解密代码 发现解密后的内容为json

优化js py代码 增加输入 增加json解析 

成果展示

图片[14]-记一次YD翻译接口逆向 -软件安全逆向社区论坛-技术社区-学技术网

视频:

视频托管在国外视频床 需要科学上网查看~

源码展示

reverse_youdao.py

import time
import requests
import execjs
import json


def get_youdao_translate(input_text):
    r = 'fsdsogkndfokasodnaso'
    t = str(int(time.time() * 1000))

    with open('get_sign.js', encoding='utf-8') as f:
        js_code = f.read()
    ctx = execjs.compile(js_code)

    sign = ctx.call('k', t, r)

    # Cookies和Headers
    cookies = {
        'OUTFOX_SEARCH_USER_ID': '-2092448371@223.72.91.242',
        'OUTFOX_SEARCH_USER_ID_NCOO': '1775499115.7853804',
        'UM_distinctid': '1931f3f545862d-0988056a045645-26011951-190140-1931f3f54592b79',
        '_ga': 'GA1.2.1829450822.1731688769',
        '_ga_ZSH399DT89': 'GS1.2.1731688769.1.0.1731688769.60.0.0',
        '_uetsid': 'ad128800b4c611ef9c0425bbcf0ddd4c',
        '_uetvid': 'a881d850a0c611ef8faca1ec8a80ae8c',
        'DICT_DOCTRANS_SESSION_ID': 'Y2M5YWQxYTQtOGM2Ny00OGZjLWFiNmItN2JjOTRhMzhiNWI5',
    }

    headers = {
        'Accept': 'application/json, text/plain, */*',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Connection': 'keep-alive',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Origin': 'https://fanyi.youdao.com',
        'Referer': 'https://fanyi.youdao.com/',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-site',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
        'sec-ch-ua': '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
    }

    data = {
        'i': input_text,
        'from': 'auto',
        'to': '',
        'useTerm': 'false',
        'domain': '0',
        'dictResult': 'true',
        'keyid': 'webfanyi',
        'sign': sign,
        'client': 'fanyideskweb',
        'product': 'webfanyi',
        'appVersion': '1.0.0',
        'vendor': 'web',
        'pointParam': 'client,mysticTime,product',
        'mysticTime': t,
        'keyfrom': 'fanyi.web',
        'mid': '1',
        'screen': '1',
        'model': '1',
        'network': 'wifi',
        'abtest': '0',
        'yduuid': 'abcdefg',
    }

    response = requests.post('https://dict.youdao.com/webtranslate', cookies=cookies, headers=headers, data=data)

    t = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
    a = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'

    with open('decry.js', encoding='utf-8') as f:
        js_code = f.read()
    ctx = execjs.compile(js_code)

    back = ctx.call('de', response.text, t, a)

    result = json.loads(back)

    if 'dictResult' in result and 'ec' in result['dictResult'] and 'word' in result['dictResult']['ec']:
        translations = result['dictResult']['ec']['word']['trs']
        translations_text = [tran['tran'] for tran in translations]
        return translations_text

    if 'translateResult' in result:
        return [item[0]['tgt'] for item in result['translateResult']]

    return ["未找到翻译"]


def main():
    while True:
        input_text = input("请输入要翻译的单词/句子(输入'q'退出):")

        if input_text.lower() == 'q':
            break

        try:
            translations = get_youdao_translate(input_text)
            print("\n翻译结果:")
            for idx, trans in enumerate(translations, 1):
                print(f"{idx}. {trans}")
            print()

        except Exception as e:
            print(f"翻译出错:{e}")


if __name__ == "__main__":
    main()

get_sign.js

const cryptoJs = require("crypto");

const d = 'fanyideskweb';
const u = 'webfanyi';

function y(e) {
    return cryptoJs.createHash("md5").update(e.toString()).digest("hex");
}

function S(e, t) {
    return y(`client=${d}&mysticTime=${e}&product=${u}&key=${t}`);
}

function k(t, r) {
    return S(t, r);
}

decry.js

const cryptoJs = require("crypto");
const Buffer = require('buffer').Buffer;


function T(e) {
    return cryptoJs.createHash("md5").update(e).digest()
}

de = (e, t, a) => {
    if (!e) return null;
    const o = Buffer.alloc(16, T(t)), n = Buffer.alloc(16, T(a)), r = cryptoJs.createDecipheriv("aes-128-cbc", o, n);
    let s = r.update(e, "base64", "utf-8");
    s += r.final("utf-8");
    return s;
}

 

 

请登录后发表评论

    没有回复内容