初步准备
在准备对之前文章中内容复现时,发现快捷键无法打开开发人员工具,手动打开发现网页被关闭并退回到历史页面中,疑是使用了disable-dev-tools,需要参考之前文章解决。
参考: disable-devtool绕过
本次目标主要针对的是普通翻译接口,如下所示,输入任意文字请求一次翻译结果:
观察到接口:
[POST]https://dict.youdao.com/webtranslate
因为翻译无需登录等直接可用,先不关注Cookies。先来看表单和响应:
表单:
data = { "i": "test", "from": "auto", "to": "", "useTerm": "false", "dictResult": "true", "keyid": "webfanyi", "sign": "e5a63b21f58ca6e683cbc1653220cdfe", "client": "fanyideskweb", "product": "webfanyi", "appVersion": "1.0.0", "vendor": "web", "pointParam": "client,mysticTime,product", "mysticTime": "1728357715844", "keyfrom": "fanyi.web", "mid": "1", "screen": "1", "model": "1", "network": "wifi", "abtest": "0", "yduuid": "abcdefg" }
|
关注到以下关键点:
i
:待翻译的内容
sign
:一个待逆向的加密参数
mysticTime
:时间戳
因此重心放在逆向sign
参数上。
响应结果:
Z21kD9ZK1ke6ugku2ccWu-MeDWh3z252xRTQv-wZ6jddVo3tJLe7gIXz4PyxGl73nSfLAADyElSjjvrYdCvEP4pfohVVEX1DxoI0yhm36ytQNvu-WLU94qULZQ72aml6QXrDrL2UC7W-I47X2SWjwq-lU7m2G5m3XGZymhNnxcR6vxjEfz4Kh8Wg7ANIC_O-BAvm8qErp4bS9BvQAx1Rcw-axcijJpsZU1WHG31O5s9V0-TXs13v3ipN96G3KeBL_11kPUWn99CVj4rUDHayVBhP0Hza1r8hyJV0O7oC2hYmJmjhl59yDkmigpVl9Rplx2qqJfsXjz_OadIHQFInaBuqTJGXhZJ-ixXvzNX2U16BWkIXXxsIAER0O9MuH6yUHemG-HbfmiMSyt1KjIHTeWMt-EyTg38pvAT8cwTrsAb_mI7FyuwB_RUT1UF_Q4PtlTHhusWKqIzEz2npf69lOiy_Kkv81uVOmAtsk7EuIzngXEvQBX1wkuJxT9LUHXCEMiZnKUJfzIpe-HbYwgSQR-Mua67Ekiqfiihn1mMSPiVDb-BfW5oSKgz87i0kWbqXguddIlrWK9UgOAPbyYk5bAI_qm9TDOGdU46YnYdwD03ydI-Hv3O-DZ4FhUQwZ-GLqr0-OVszZpyvMhV2Rjoy25U250BB5sbJadmuSeCqtNwAiH9KbWs2upNiBXiv4oeGmTMaF3y6rf8I3cCfWvLl4J2GUJdZAAlK_Yxh5BrKr7B1ELb8om2a1vvmQumeUs7JVUImuaq8qCC47oyR-HlYr082QGbqdyALjn1RL9qKgb9nNKMWzd7LkVMBR5ld9r3EIEaEwMagYcq5hqFUF3lQATBNZlr3dMQxmP6_m_TB9LkFPtmdYsCq_ZVll-jSUmVWVE2WGfvwOMyE_nxzUXtDmHq65mos5ZdYB5qyxsh2twMHjWmEx06U_U9_JwKMqQZjjn4MH7aWpjqP8cysdcImQ0PWAgAaSiVoCT0WIRo_bGAz4Zs1GkSmhqSI-S5GmR9A-DWLISkhSULWS1QinJpF7BEwgjyWLitz_VUtAM27LaWvXG8Vgykcq-2 NEyE6-ojt49OZxo-xIGKzaNh1mYAkria-4 tAfcDhGzzI9XwO0NZErlfgkO_MNelgnCxgDubQW_ciVC0zFgTXLpHBwxcIbVZSiTHVI96EOHdBTPDDqPXuMLL7e4KDtS8IFAE1xGnKnho6doVpaSYyB1sw0UdOPBjJi0cIhvsvxVrNnlFTW_Lt_GZIrVdxmV71hIONuqp3lhhlRX7S6RVUUyWANF7YQh-5_2 cbjABqW8LD6xEx5LbY3-I8qDv-99 fEN07OrM9cqJnAqXJtUSx_bnmadqGmc2gvQwN6SNwGugcfTLAMB--RKifTomuXgOGf1fDTlCcwDXtv2m5e4MDYZl13zHEH8sRnEPiod0o7D-Yl2Ngj2JcOqhCTaGkyHAlRuv6eyjF4c3a0CuiR8tg_vjZkb9l5qKEqjj7_sRqft6zXQ2KGg7pRFXpjlgN8NexpwOqFmUqtk9-5 sSyvLKJ3d_VJ942KKAAUZ-0 fJVF17zxSkIxhxmSgrgsUTNz-oZZVrYsKqCqyuxqM38tXst8Vr1foe6-QoWQ==
|
同样是加密的结果。
现在确定逆向的目标:
sign参数逆向
为了方便使用XHR断点,断点sign
,在此处找到疑似加密的地方:
成功断住:
需要两个参数a
和e
,其中a
就在上面,是时间戳。
根据堆栈追溯e
,可以在此处找到:
但事情可能没有这么简单。尝试在下面断住,重新请求一次,会发现它并没有直接请求翻译接口,而是请求了https://dict.youdao.com/webtranslate/key
该接口的表单:
响应结果:
继续一轮请求后,才是翻译接口,从前接口的表单结构以及下面代码也能发现,请求接口需要上面的接口中的响应结果:
并且发现e
来自前一接口的响应结果。因为时间戳已固定a
,所以可以手动生成下加密值,便于对比观察:
可以直接放掉断点执行,观察请求表单对比,也可以解析下这里:
上面的代码简单梳理下就是:
(0,n.H)( "https://dict.youdao.com/webtranslate", (0,o.A)((0,o.A)({}, e), k(t)), { headers: { "Content-Type": "application/x-www-form-urlencoded" } })
|
很明显(0,n.H)
是用于发送请求的函数,括号后跟随了三个参数,为请求地址,请求表单,请求头参数。
可以在控制台中输入(0,n.H)
执行并转到函数定义处,下断点:
如图,点击即可跳转到函数定义处,下断点,执行一下,可以看到表单值即为t
,输出t
最终全部放行,验证下:
可以看到是完全一致的。整理下思路:
首先需要获取KEY,生成SIGN获取新的KEY,而生成这个SIGN的KEY值是固定写死的。
获得新KEY值后,用该KEY值生成SIGN值,请求翻译接口获得翻译结果。
那么接下里就是关注生成SIGN值的问题了。用于生成SIGN值的语句为S(a,e)
,a
为时间戳,e
为KEY,而S
函数的定义就在上面:
其相关变量值也在上方定义:
所以上面的代码基本可以视为对client=fanyideskweb&mysticTime=[时间戳]product=webfanyi&key=a[KEY值]
进行MD5加密。
现在来验证下是否为标准的MD5加密:
根据上面的a
和e
拼接得到client=fanyideskweb&mysticTime=1728362863068&product=webfanyi&key=asdjnjfenknafdfsdfsd
,进行MD5加密:
可以发现加密的结果一致。
所以SIGN值还是与之前一样,是MD5加密的结果。
响应结果解密
这里之前下的一处断点就有了用处:
根据代码,可以判断响应结果是交给了o
函数处理,打个断点,尝试进入o
。但实际上无法进入,只好继续执行代码,直到来到此处:
很明显,解密的代码就在此处了。
现在可以得出,解密函数即da.A.decodeData
,其中o
是原始响应结果,ha.A.state.text.decodeKey
和ha.A.state.text.decodeIv
同样来自之前请求KEY值的请求响应结果之中。
进入解密函数内,可以看到有AES-128-CBC
的字样
交给AI解释下:
其中T
函数定义:
最终利用AI加修改后的脚本如下:
const crypto = require('crypto'); function T(e) { return crypto.createHash("md5").update(e).digest(); } const info = { data:'Z21kD9ZK1ke6ugku2ccWuwRmpItPkRr5XcmzOgAKD0GcaHTZL9kyNKkN2aYY6yiOyWcBwBY7DpCnYHrwYC8hpzl_rnikm7i3xAZF3Wex6szm_MFVxOF_V3QHOtl2Ekhn-TomGU1MCLLXXF3HpNNmAGnLLd7PqJP6C1Ru5SO6US0=', key:'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl', iv:'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4' }
const key = Buffer.from(T(info.key)); const iv = Buffer.from(T(info.iv));
function decrypt(encryptedData) { const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); let decrypted = decipher.update(encryptedData, 'base64', 'utf-8'); decrypted += decipher.final('utf-8'); return decrypted; }
try { const decryptedData = decrypt(info.data); console.log('解密后的数据:', decryptedData); } catch (error) { console.error('解密失败:', error.message); }
|
执行结果:
至此解密部分的逆向也结束了。
结语
本篇文章最终关注在了两处的加解密上,并没有进行实际的模拟请求,所以自然就忽略了Cookies可能带来的反爬问题,但篇幅有限,请自行测试并解决。