有道翻译逆向学习

注意事项

注意:本文章仅供学习交流、如有侵权、联系我立马删除!!

今天学习逆向一下某道翻译

分析请求链接

image.png

第一个请求响应的是一个:

{
    "data": {
        "secretKey": "Vy4EQ1uwPkUoqvcP1nIu6WiAjxFeA3Y3",
        "aesKey": "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl",
        "aesIv": "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
    },
    "code": 0,
    "msg": "OK"
}

响应的是一个json数据、里面包含secretKey,aesKey,aesIv参数、反正像AES解密的参数、暂时不知道干嘛用的。

接着第二个请求:

Z21kD9ZK1ke6ugku2ccWu4n6eLnvoDT0YgGi0y3g-v0B9sYqg8L9D6UERNozYOHqwLvGBijzFn4XfJcW_Slfa_-x9ir7-ZOFYJFsv1J2XAKgdx4mcNNyw09yAq6q0wimNiYyk52D-ucQcWoH-BnmcyEfWnxRq0XN0GIfC_OdzYZTMIHXkcI2IP9QD6CqwFIU4LqnXa-4 wmNj7GqMfhlCsFS4lS0yI9YhgWYfxEv6ZmjG1o24wpMRwkcP-PMlzJl7qdq-J8m_-CRo52DB7XtGfJJ5DtI8wgPRRhRiUaNFjClN3R41b6-K-1 aBGFRN9pYDA5Pq8-j1RMezivjBySLA346j1I0FTTox_ab8r_VlU8x94FgHrI65Vm6nX7FCv85liaMZwwZexOVul17x-w55-Lp3HxgMbYPzGweDIWZYNQFWUBsrGrjhWk9oKYlrOVh_xTnxZVLgRMGl7-J_VMuj5NxjFFH-H8pwobJKcGU7hTeOp0mkTSuLcCPRgyMI5sZFt30NyhQkpjg-s7EFCzFvD-s7ZqzQtz1-IxqLzYWdRFqdX3fmJoY_st-6 cmotDl2WbvxelWUNh8xk2Ovfr3pL20CTDoDo58yfBrzxqgeQ-Z3XRjcdYZuwexz8QBzjM3lDKhvDSo_wszkt-mUqLUdeww4PgNKTrSxTU7wUByEY0sBdRr0owkhYaaJv0aInM-2 Jo0Elvox7UbzaZZ3qoX5DB0S50-NKDcm86vlSr0Sr0Rn0mNiVAc7r4X6FyP2G3c14LRokfSrLjBrRjIYhDcz500YVensrqdYDpdPH0kdb_SPAReMWMgvw8HXwoAw_m0qlF5cTd7_SKeWUawm79Qf6hRXltfUeIfd9z-gBPooB5izmyfU2gGVKx8hn4_MWCPwzyRKRko5MOR3Ys638BEQmXYpklyV0FFV9Z_wV128EV5wGGCG3fGeFFyOYea6ZDKhRt3kZScMAHA0tuzws07rXtw==

返回的好像加密信息

第三个请求链接

{"code":0,"message":"SUCCESS","data":[]}

看完所有xhr请求也没看到 的翻译 you 在哪呢。

先分析第一个请求。多次实验、只有sign和时间戳变了。所以第一个请求主要分析sign是怎么生成的。

  • XHR断点添加url关键字(我技术菜看了半天眼瞎了也没找到)
  • 通过全局关键字查找
    image.png

找一会会发现这个位置非常有嫌疑、打个断点。重新输入一个 你好、发现断住了。
控制台打印一下

S(a, e)
'cc12c7be50feb68fc96f38978262481f'

可以条多对比一下、可以发现此处断点被调用了两次。第一次的sign的生成的参数e为固定的asdjnjfenknafdfsdfsd用来生成第一次请求的sign。

image.png

主要通过拼接client= ${d}&mysticTime=$ {e}&product= ${u}&key=$ {t}并进行md5加密好像。作为小白这里算法不难、直接扣下来让AI补一下环境即可。
最终第一个请求的sign代码如下:

const crypto = require('crypto');  
const d = "fanyideskweb";  
const u = "webfanyi";  
  
function g(e) {  
    return crypto.createHash("md5").update(e).digest()  
}  
  
function _(e) {  
    return crypto.createHash("md5").update(e.toString()).digest("hex")  
}  
  
function S(e, t) {  
    return _(`client=${d}&mysticTime=${e}&product=${u}&key=${t}`)  
}  
  
function k(e, t) {  
  
    console.log(t)  
    sign = S(t, e)  
    console.log(sign)  
    return sign  
}  
  
  
// k("asdjnjfenknafdfsdfsd", 1744871548974)

把它封装为一个js函数方便Python调用。

密文请求逆向(第二个请求)

通过对url的研究发现、表单变化的还是只有三处、其中

  • i:要翻译的文本
  • sign:签字加密(目前不知道怎么来的)
  • 时间戳:自己可以生成

image.png

根据第一个请求的经验、我们知道断点处S(a,e)函数一共被调用了两次、那么第一次的sign时请求AES解密用的、那么第二次生成的呢?

image.png

这里可以看到e的结果改变了、它的值刚好时第一个请求获得的secretKey的值然后对其进行加密,a的值仍然时一个时间戳。
那么第二个请求的sign逻辑我们也清楚了。

原理分析

js先生成根据固定值e以及时间差来生成sign、发送第一个请求-> 获取到 secretKey, aesKey, aesIv三个值 ->再根据secretKey和时间戳来生成第二个请求的sign参数来获得密文->对密文解密

那么解密的位置在哪呢?通过第一个断点、我们来到第二次sign生成的地方。然后一点点找(由于技术原因、我是一点点调试找的)

最后找到一个地方非常有嫌疑。多打几个断点

image.png

可以看到o为我们的密文、通过da.A.decodeData()方法解密为了我们需要的结果,它的第一个参数时密文,第二个参数时aeskey、第三个参数时aesiv(这不就是我们第一个请求获得的结果吗!)。那么解密的函数我们就确定了。

image.png

进入这个文件、找到这个方法。

image.png

其中的T()函数你打个断点,点进去可以发现就在我们找到的生成sign断点的上分

image.png

至此分析结束、因为加密的js代码不多、可以单独把他们抠出来。
最终代码如下:

const crypto = require('crypto');  
const {Buffer} = require('buffer'); // 对应 l  
const d = "fanyideskweb";  
const u = "webfanyi";  
  
function g(e) {  
    return crypto.createHash("md5").update(e).digest()  
}  
  
function _(e) {  
    return crypto.createHash("md5").update(e.toString()).digest("hex")  
}  
  
function S(e, t) {  
    return _(`client=${d}&mysticTime=${e}&product=${u}&key=${t}`)  
}  
  
function k(e, t) {  
  
    console.log(t)  
    sign = S(t, e)  
    console.log(sign)  
    return sign  
}  
  
  
// k("asdjnjfenknafdfsdfsd", 1744871548974)  
  
  
const o = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl';  
const n = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4';  
text = ''  
  
function data(t,o,n) {  
    // const a = e.alloc(16, g(o))  
    //     , c = e.alloc(16, g(n))    const a = Buffer.alloc(16, g(o))  
        , c = Buffer.alloc(16, g(n))  
        , i = crypto.createDecipheriv("aes-128-cbc", a, c);  
    //, i = r.a.createDecipheriv("aes-128-cbc", a, c);  
    let s = i.update(t, "base64", "utf-8");  
    return s += i.final("utf-8"),  
        s  
}  
  
// console.log(data(text,o,n))
import json  
import time  
  
import execjs  
import requests  
  
headers = {  
    "host": "dict.youdao.com",  
    "origin": "https://fanyi.youdao.com",  
    "pragma": "no-cache",  
    "referer": "https://fanyi.youdao.com/",  
    "sec-ch-ua": "\"Google Chrome\";v=\"135\", \"Not-A.Brand\";v=\"8\", \"Chromium\";v=\"135\"",  
    "sec-ch-ua-mobile": "?0",  
    "sec-ch-ua-platform": "\"Windows\"",  
    "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/135.0.0.0 Safari/537.36"  
}  
# e = "asdjnjfenknafdfsdfsd"  
# t = int(round(time.time() * 1000))  
  
  
def obj_js():  
    with open('test.js', 'r', encoding='utf-8') as f:  
        js_code = f.read()  
        ctx = execjs.compile(js_code)  
        return ctx  
  
  
def get_jiemi_txt(e, key, iv):  
    ctx = obj_js()  
    res = ctx.call('data', e, key, iv)  
    return res  
  
  
def get_sign(e, t):  
    ctx = obj_js()  
    sign = ctx.call('k', e, t)  
    # print("sign:", sign)  
    return sign  
  
  
def get_time_now():  
    return int(round(time.time() * 1000))  
  
  
def getKey():  
    e = "asdjnjfenknafdfsdfsd"  
    t = get_time_now()  
    sign = get_sign(e, t)  
    try:  
        response = requests.get(  
            f"https://dict.youdao.com/webtranslate/key?keyid=webfanyi-key-getter&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",  
            headers=headers)  
        response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)  
        data = response.json()  
        secretKey = data['data']['secretKey']  
        aesKey = data['data']['aesKey']  
        aesIv = data['data']['aesIv']  
        return secretKey, aesKey, aesIv  
    except requests.exceptions.RequestException as err:  
        print(f"获取密钥失败: {err}")  
        return None  
    except (json.JSONDecodeError, KeyError) as err:  
        print(f"解析密钥响应失败: {err}")  
        return None  
  
  
def getMitext():  
    key_info = getKey()  
    if key_info is None:  
        raise "key_ingo 为空"# 或者抛出异常  
    e, aeskey, aesiv = key_info  
    t = get_time_now()  
    sign = get_sign(e, t)  
    txt = input(">>> ")  
  
    data = {  
        'i': f'{txt}',  
        'from': 'auto',  
        'to': '',  
        'useTerm': 'false',  
        'domain': '0',  
        'dictResult': 'true',  
        'keyid': 'webfanyi',  
        'sign': f'{sign}',  
        'client': 'fanyideskweb',  
        'product': 'webfanyi',  
        'appVersion': '1.0.0',  
        'vendor': 'web',  
        'pointParam': 'client,mysticTime,product',  
        'mysticTime': f'{t}',  
        'keyfrom': 'fanyi.web',  
        'mid': '1',  
        'screen': '1',  
        'model': '1',  
        'network': 'wifi',  
        'abtest': '0',  
        'yduuid': 'abcdefg',  
    }  
    url = "https://dict.youdao.com/webtranslate"  
    try:  
        aestxt_response = requests.post(url, headers=headers, data=data)  
        aestxt_response.raise_for_status()  
        return aestxt_response, aeskey, aesiv  
    except requests.exceptions.RequestException as err:  
        print(f"获取翻译结果失败: {err}")  
        return None, None, None  
  
  
def out_txt():  
    result = getMitext()  
    if result is None or any(item is None for item in result):  
        print("获取加密文本或密钥信息失败,无法进行解密。")  
        return None  
  
    aestxt_response, aeskey, aesiv = result  
    print("加密文本响应:", aestxt_response)  
    aestxt = aestxt_response.text  
    res = get_jiemi_txt(aestxt, aeskey, aesiv)  
    if res:  
        try:  
            res_json = json.loads(res)  
            print("解密结果:", res_json)  
            return res_json  
        except json.JSONDecodeError as e:  
            print(f"JSON解码错误: {e}")  
            print(f"原始解密文本: {res}")  
            return None  
    else:  
        print("解密文本为空")  
        return None  
  
  
if __name__ == '__main__':  
    while True:  
        out_txt()

演示实例:

注意事项

pyexecjs库

pyexecjs库总是提示编码错误

下载pyexecjs2问题解决

js文件规范

js代码中有打印代码时报错。去掉打印恢复正常(没对比不确定pyexecjs2解决没)

捷径

由于不是特别懂js代码、所以补环境什么的可以让AI帮你补

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值