注意事项
注意:本文章仅供学习交流、如有侵权、联系我立马删除!!
今天学习逆向一下某道翻译
分析请求链接
第一个请求响应的是一个:
{
"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关键字(我技术菜看了半天眼瞎了也没找到)
- 通过全局关键字查找
找一会会发现这个位置非常有嫌疑、打个断点。重新输入一个 你好
、发现断住了。
控制台打印一下
S(a, e)
'cc12c7be50feb68fc96f38978262481f'
可以条多对比一下、可以发现此处断点被调用了两次。第一次的sign的生成的参数e为固定的asdjnjfenknafdfsdfsd用来生成第一次请求的sign。
主要通过拼接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:签字加密(目前不知道怎么来的)
- 时间戳:自己可以生成
根据第一个请求的经验、我们知道断点处S(a,e)函数一共被调用了两次、那么第一次的sign时请求AES解密用的、那么第二次生成的呢?
这里可以看到e的结果改变了、它的值刚好时第一个请求获得的secretKey的值然后对其进行加密,a的值仍然时一个时间戳。
那么第二个请求的sign逻辑我们也清楚了。
原理分析
js先生成根据固定值e以及时间差来生成sign、发送第一个请求-> 获取到 secretKey, aesKey, aesIv三个值 ->再根据secretKey和时间戳来生成第二个请求的sign参数来获得密文->对密文解密
那么解密的位置在哪呢?通过第一个断点、我们来到第二次sign生成的地方。然后一点点找(由于技术原因、我是一点点调试找的)
最后找到一个地方非常有嫌疑。多打几个断点
可以看到o为我们的密文、通过da.A.decodeData()方法解密为了我们需要的结果,它的第一个参数时密文,第二个参数时aeskey、第三个参数时aesiv(这不就是我们第一个请求获得的结果吗!)。那么解密的函数我们就确定了。
进入这个文件、找到这个方法。
其中的T()函数你打个断点,点进去可以发现就在我们找到的生成sign断点的上分
至此分析结束、因为加密的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帮你补