有道翻译逆向
- 人工智能
- 2025-08-22 19:36:02

今天突然使用了有道翻译,发现有道在线版本更新了,添加了很多功能,看了看规则,有些改变。所以我又更新了响应的文档,仅供学习交流,如有不妥之处,联系我删之。
目录
一、整体目标
二、流程步骤
1. 分析请求
2. 明确逆向目标
三、定位与调试手段
四、第一层:逆向sign签名
流程描述:
五、第二层:返回值的AES解密
流程描述:
六、整合与转换
七、总结
一、整体目标
目标对象:有道翻译(fanyi.youdao )
逆向目的:获取接口加密算法的核心逻辑,即
获取用于加密请求参数的 sign 值;利用返回结果中的 aesKey 与 aesIv 对返回的加密数据进行解密。工具链:
Chrome浏览器(F12调试工具)PyCharmNode.js(用于运行和验证JS代码)ChatGPT(用于补全缺失的第三方库及转换代码到Python) 二、流程步骤 1. 分析请求 观察网络请求: 使用Chrome的F12开发者工具,监控有道翻译发起的XHR请求,发现主要有三个请求: 第一个请求(拿到密钥: dict.youdao /webtranslate/key): 入参中包含固定参数和动态变化的参数,如 sign、mysticTime(时间戳)。接口返回数据包含 secretKey、aesKey、aesIv,这些参数用于后续解密。 第二个请求(拿到翻译单词的加密数据: dict.youdao /webtranslate): 此接口是真正用于翻译的核心接口,入参同样包含动态变化的 sign 与时间戳。返回数据是一段加密后的字符串,必须用前一个请求返回的 aesKey 和 aesIv 解密才能获得翻译结果。 第三(翻译加密数据): 2. 明确逆向目标 第一层:破解 sign 生成算法 目的:理解并提取JS代码中负责生成请求签名 sign 的函数。 第二层:解析返回数据 目的:找到JS中用于解密返回数据的核心函数,并提取其加密算法(AES解密)的关键步骤。 三、定位与调试手段文章总结了几种常用的定位和调试手段,帮助定位核心代码的位置和调用链:
事件断点:监控页面中的点击事件,确定用户触发翻译时发送的请求。全局搜索:利用Chrome开发者工具的全局搜索功能,搜索关键词 sign,快速定位相关代码。XHR断点:在网络面板中对XHR请求设定断点,捕获请求发出时的调用栈信息。栈分析:利用调试工具查看调用栈,追踪加密/解密函数的调用链,确定核心函数所在位置。 四、第一层:逆向sign签名 流程描述:利用XHR断点捕获请求: 设定断点,触发翻译请求,观察JS中与 sign 相关的调用链。
全局搜索 sign: 直接搜索 sign: 关键字,快速定位到包含签名计算逻辑的函数。
分析调用栈:
通过栈断点查看调用链,找到类似函数名 S 或其他混淆名称函数,其中包含了对 md5 加密和时间戳参数的处理。识别第三方加密库调用:
注意到部分代码调用了常见的加密函数(例如 md5、base64、AES),这些通常是调用外部库的函数。遇到第三方库时,通过经验判断其功能,避免深入无用代码。提取并整合代码:
将核心的签名生成代码(如 function S(e, t) { return md5("client=" + 固定值 + "&mysticTime=" + e + "&product=" + 固定值 + "&key=" + t) })扣出来。补全缺失的变量和常量,必要时利用ChatGPT帮助添加第三方库引用(例如Node.js的 crypto 模块)。调试验证:
运行提取后的代码(例如在Node.js中测试 console.log(S(...))),确认签名计算正确。 五、第二层:返回值的AES解密 流程描述: 定位解密函数: 利用XHR断点或栈断点,观察返回的加密数据和解密函数的调用,找到类似 decodeData 的函数(在混淆代码中可能名字也被混淆)。 避免直接单步调试进入混淆代码: 由于混淆严重,直接单步调试可能会进入大量无用或复杂的代码,建议通过查看对象属性和弹窗提示,定位到真正负责解密的对象或函数。 定位关键对象: 选中某个对象(如 _a)后,弹出其内容,找出其中负责AES解密的函数 decodeData 或相关函数。 提取AES解密核心代码: 扣出核心代码部分,主要涉及以下步骤: 使用 Buffer.alloc(16, T(key)) 创建密钥和偏移量,函数 T 通常是对输入字符串进行 md5 处理。调用 crypto.createDecipheriv("aes-128-cbc", keyBuffer, ivBuffer) 初始化解密器。调用 decipher.update(encryptedData, "base64", "utf-8") 解密数据,并接续 decipher.final("utf-8") 完成解密。 补全第三方库依赖: 对于诸如 crypto、md5 等函数,使用 Node.js 的 crypto 模块或相应的Python第三方库来替换。 调试验证: 扣出完整的解密代码,并在提取的独立文件中测试,确保能够正确解密返回的加密数据字符串。 六、整合与转换 变量补全与清理: 补全扣出的代码中缺失的变量定义,去掉冗余代码,确保独立代码可以运行。 替换第三方库引用: 例如,将JS中的 crypto 模块调用替换成 Node.js 的方式,或者利用ChatGPT将JS代码转为Python代码,调用Python的 hashlib、pycryptodome 等库来完成同样的功能。 统一测试: 将第一层生成签名和第二层解密的代码整合起来,在模拟真实请求时能正确获取到解密后的数据。 转换为Python(可选): 使用ChatGPT快速将整合后的JS代码转为Python代码,达到同样的加解密效果,便于后续调用API。 python实现流程:拿到KEY
tm = str(int(time.time() * 1000)) def md5_hash(text: str) -> str: """对输入字符串计算 MD5 并返回32位小写十六进制字符串""" return hashlib.md5(text.encode('utf-8')).hexdigest() def _my_md5(): fixed_client = "fanyideskweb" fixed_product = "webfanyi" mystic_time = tm key_value = "这个值需要你去跟栈拿到" string_to_sign = "client=" + fixed_client + "&mysticTime=" + mystic_time + "&product=" + fixed_product + "&key=" + key_value return md5_hash(string_to_sign) def get_key(): '''获取密钥''' params = { "sign": _my_md5(),", "mysticTime":tm, # 其他参数在请求中复制 } headers = { # 请求头中复制 } url = " dict.youdao /webtranslate/key" response = requests.get(url=url, headers=headers, params=params) key_json = json.loads(response.text) aes_iv = key_json['data']['aesIv'] aes_key = key_json['data']['aesKey'] return aes_key,aes_iv拿到加密后的单词
def my_md5(): fixed_client = "fanyideskweb" fixed_product = "webfanyi" mystic_time = str(int(time.time() * 1000)) # key_value = "fsdsogkndfokasodnaso" key_value = "需要你去跟栈拿到参数" string_to_sign = "client=" + fixed_client + "&mysticTime=" + mystic_time + "&product=" + fixed_product + "&key=" + key_value return md5_hash(string_to_sign) def get_jm_word(word_list): '''获取单词加密后的密文''' for item in word_list: data = { "i": item, "sign": my_md5(), "mysticTime":str(int(time.time() * 1000)), } headers = { # 请求头复制 } url = " dict.youdao /webtranslate" response = requests.post(url=url, headers=headers, data=data) print(response) print(response.text) return response.text解密单词
def T(e: str) -> bytes: """ 对输入字符串 e 进行 MD5 运算,返回 16 字节的二进制摘要 """ return hashlib.md5(e.encode('utf-8')).digest() def get_words(encrypted_data: str, t: str, o: str) -> str: '''解密单词''' if not encrypted_data: return None # 清理字符串,去除所有空白字符 encrypted_data_clean = re.sub(r'\s+', '', encrypted_data) # 自动补齐 base64 填充(确保长度为4的倍数) missing_padding = len(encrypted_data_clean) % 4 if missing_padding: encrypted_data_clean += '=' * (4 - missing_padding) key = T(t) # 16字节密钥 iv = T(o) # 16字节IV # 使用 URL-safe base64 解码 try: encrypted_bytes = base64.urlsafe_b64decode(encrypted_data_clean) except Exception as decode_error: raise ValueError("Base64 解码失败: " + str(decode_error)) # 检查解码后的数据长度必须为 16 字节的倍数 if len(encrypted_bytes) % 16 != 0: raise ValueError("解码后的数据长度不是16字节的倍数!当前长度:{}".format(len(encrypted_bytes))) cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = cipher.decrypt(encrypted_bytes) pad_len = decrypted[-1] # 移除 PKCS#7 填充:最后一个字节表示填充字节数 print(decrypted[:-pad_len].decode('utf-8')) word_c = json.loads(decrypted[:-pad_len].decode('utf-8')) word_t = word_c['translateResult'][0][0]['tgt'] return word_t # 参数解释 # encrypted_data : 单词加密的密文 # t: key # o: iv最终效果
七、总结
整个逆向流程主要分为:
步骤1:观察和捕获网络请求 了解有道翻译发起的各个请求,确认需要逆向的接口参数和返回加密数据。步骤2:定位签名生成(sign)函数 利用XHR断点、全局搜索、栈断点等手段,定位生成 sign 的核心代码,并扣出关键部分,补全缺失内容。步骤3:定位AES解密函数 通过调试定位返回数据的解密逻辑,扣出核心代码,整理出利用AES-128-CBC方式解密返回数据的流程。步骤4:整合与优化代码 将扣出的代码进行变量补全、删除冗余,替换第三方库引用,确保代码能够独立运行。步骤5:转换和验证 利用Node.js或Python对整合后的代码进行测试验证,确保生成的 sign 值和解密结果正确无误。