逆向有道翻译接口参数,本文仅供研究与学习使用,切勿用于各种非法用途。
打开有道翻译页面,输入你好,无需提交数据,翻译结果便自行生成了,要想实现这种逻辑,推断 Ajax 加载。
打开开发者工具,选择 Fetch/XHR 过滤请求,可以看到有两个 url:webtranslate 和 key。
key 中为提交的中文数据,不做分析,仅看接口 webtranslate。
都 23 年了,就知道没那么简单,问题不大,有加密必定有解密!
一个 sign 值需要分析,mysticTime 为时间戳。
在 XHR/提取断点中下断:webtranslate
m 中的值即为经过 url 编码的负载数据:
i=%E4%BD%A0%E5%A5%BD&from=auto&to=&dictResult=true&keyid=webfanyi&sign=0d43622e3f5b6fc6578cdc1b849f9d28&client=fanyideskweb&product=webfanyi&appVersion=1.0.0&vendor=web&pointParam=client%2CmysticTime%2Cproduct&mysticTime=1687161867680&keyfrom=fanyi.web
跟踪堆栈数据,可定位到如下位置:
1
2
3
4
5
6
|
const j
=
(e,t)
=
>
Object
(a[
"a"
])(
"https://dict.youdao.com/webtranslate/key"
,
Object
(n[
"a"
])(
Object
(n[
"a"
])({}, e), O(t)))
, y
=
(e,t)
=
>
Object
(a[
"d"
])(
"https://dict.youdao.com/webtranslate"
,
Object
(n[
"a"
])(
Object
(n[
"a"
])({}, e), O(t)), {
headers: {
"Content-Type"
:
"application/x-www-form-urlencoded"
}
})
|
主要看 y 的生成,在该段代码执行后 sign 值便生成了,调试跟踪定位到在其上方的 O 函数:
e 为固定值:fsdsogkndfokasodnaso
d 为固定值:fanyideskweb
u 为固定值:webfanyi
p 为固定值:1.0.0
b 为固定值:web
m 为固定值:client,mysticTime,product
t 为时间戳
f 为固定值:fanyi.web
1
2
3
|
function h(e, t) {
return
g(`client
=
${d}&mysticTime
=
${e}&product
=
${u}&key
=
${t}`)
}
|
入参 e 为时间戳,t 为固定字符串:fsdsogkndfokasodnaso,将参数格式化后,又调用 g 函数,进入分析:
1
2
3
|
function g(e) {
return
r.a.createHash(
"md5"
).update(e.toString()).digest(
"hex"
)
}
|
入参 e 为:
client=fanyideskweb&mysticTime=1687163549482&product=webfanyi&key=fsdsogkndfokasodnaso
入参后,创建 md5 函数进行加密,得到结果:
785914f401740b9bad913162ba4091ee
对照分析无误!
1
2
|
import
time
mysticTime
=
str
(
int
(time.time()
*
1000
))
|
1
2
3
|
import
hashlib
data
=
"client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso"
.
format
(mysticTime)
# 组包
sign
=
hashlib.md5(data.encode(encoding
=
'utf-8'
)).hexdigest()
# MD5 加密
|
固定时间戳后,对照网页 sign 值结果无误。
python实现,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
import
hashlib
import
time
import
requests
class
Youdao():
def
__init__(
self
):
self
.url
=
'https://dict.youdao.com/webtranslate'
def
get_enmes(
self
, input_data):
mysticTime
=
str
(
int
(time.time()
*
1000
))
print
(mysticTime)
data
=
"client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso"
.
format
(mysticTime)
sign
=
hashlib.md5(data.encode(encoding
=
'utf-8'
)).hexdigest()
# python中的MD5 加密
print
(sign)
data
=
{
'i'
: input_data,
'from'
:
'auto'
,
'to'
: '',
'dictResult'
:
'true'
,
'keyid'
:
'webfanyi'
,
'sign'
: sign,
'client'
:
'fanyideskweb'
,
'product'
:
'webfanyi'
,
'appVersion'
:
'1.0.0'
,
'vendor'
:
'web'
,
'pointParam'
:
'client,mysticTime,product'
,
'mysticTime'
: mysticTime,
'keyfrom'
:
'fanyi.web'
}
headers
=
{
'Referer'
:
"https://fanyi.youdao.com"
,
"User-agent"
:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/113.0.0.0 Safari/537.36"
,
}
cookies
=
{
"OUTFOX_SEARCH_USER_ID"
:
"-390346570@10.110.96.160"
,
"OUTFOX_SEARCH_USER_ID_NCOO"
:
"1229562063.3960004"
}
response
=
requests.post(
self
.url, headers
=
headers, data
=
data, cookies
=
cookies).text
print
(response)
def
start():
input_data
=
input
(
'请输入要翻译内容:'
)
op.get_enmes(input_data)
if
__name__
=
=
'__main__'
:
op
=
Youdao()
start()
|
请求后得到密文数据,下一步的目标就清楚了,找到解密函数。
可定位的方式有很多,关键字搜索、单步调试分析、堆栈分析等等。
第一次分析时单步调试无疑能让你了解整个执行流程,但真的费时间和精力,对我就是这么干的......
建议还是通过关键字搜索如:decode,其次通过调用堆栈定位!
逐步调试最终定位到如下位置:
1
2
3
4
5
6
|
.then(o
=
>{
an[
"a"
].cancelLastGpt();
const n
=
an[
"a"
].decodeData(o, sn[
"a"
].state.text.decodeKey, sn[
"a"
].state.text.decodeIv)
, a
=
n ? JSON.parse(n) : {};
0
=
=
=
a.code ? e.success && t(e.success)(a) : e.fail && t(e.fail)(a)
})
|
其中 o 为密文,执行完该局代码后生成明文数据 n。
直接看代码:
1
|
const n
=
an[
"a"
].decodeData(o, sn[
"a"
].state.text.decodeKey, sn[
"a"
].state.text.decodeIv)
|
全局搜索 decodeKey 可得:
通过控制台打印输出得:
key 和 iv 对照无误。
哪一种加密函数需要传入密文数据还有 key 值和 iv 向量?
其实大家心中已经有一些答案了,考考 ChatGpt:
跟进代码中看看是不是:
进入后发现:
aes-128-cbc 加密,有 key,有 iv,执行完后生成明文数据,下一步就是还原该解密函数了。
1
2
3
4
5
6
7
8
9
10
|
T
=
(t,o,n)
=
>{
if
(!t)
return
null;
const a
=
e.alloc(
16
, v(o))
, c
=
e.alloc(
16
, v(n))
, i
=
r.a.createDecipheriv(
"aes-128-cbc"
, a, c);
let s
=
i.update(t,
"base64"
,
"utf-8"
);
return
s
+
=
i.final(
"utf-8"
),
s
}
|
首先判断t即传入的加密参数是否为空,为空则返回 null,不为空则继续执行下面的代码。
v 函数为:
1
2
3
|
function v(e) {
return
r.a.createHash(
"md5"
).update(e).digest()
}
|
对密钥和向量进行 MD5 加密,并返回字节数据。
接下来就是正常的 AES 算法了,不懂的可自行百度理解。
直接上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import
base64
from
Crypto.Cipher
import
AES
from
Crypto.
Hash
import
MD5
def
decrypt_data(encrypted_data):
key
=
b
"ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
iv
=
b
"ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
cryptor
=
AES.new(
MD5.new(key).digest()[:
16
], AES.MODE_CBC, MD5.new(iv).digest()[:
16
]
)
decode_mes
=
cryptor.decrypt(base64.urlsafe_b64decode(encrypted_data)).decode(
"utf-8"
)
return
decode_mes
encrypted_data
=
'Z21kD9ZK1ke6ugku2ccWu4n6eLnvoDT0YgGi0y3g-v0B9sYqg8L9D6UERNozYOHqnYdl2efZNyM6Trc_xS-zKtfTK4hb6JP8XwCzNh0avc8qItQUiIU_4wKKXJlIpvMvfKvJaaZzaX6VEtpkr2FdkfoT_Jgbm2GRSVj3r40autIdlImENG8hC0ZH4ww7utwuTt3Oo_ZpXg0BSq9wePSAB75-ChkiGKF9HTIPeCl2bl84SBD1XDfFCZpkKQhecYSs0JLoXOqP2ltavxRrg58Hp1q5uIgZZ_Oo2-Jmd-t1r4es40drcAq5bjmS62M2VJF8D6ojtOh9JTfNwgzD3CxYn-Pd7-TgHMyNEJEkFXTAyxzpjlFqtrCYDE3SZUYlENkqsL8Wrra1hM-1nTfiB-BLcWAdRBynNpP5_54aq_-GBsq8bB_9yEX5ovzDB4_Ry_spVVuUnb39iplMHCdCnjOD3ngiIDbl9SUz-9npjBX05ZYRdPmFPAl424qdoaxeVqnVoH8jQFPZVqaHMzu4mJg0SICDWFH7GP1zqGRbXd3ESjT_iBInl3gICt2XVuhh_nubcELkTEC6xbqEDRQkPUNMpzXJHjcvsLHtcmSW0S9F0445ho9kT2qZYdMBC3Fs0OaHpUtFu77gZpQn7sGiqh8VliXIcUtfvvop-1c-Vu5QjfUbLn2-s5POR9fGYG6rt6ioe_PGmwWj-Cc00zUM7FybfarKTr4D3Rk57R72qpXN4Ja86ZsCAMmDG-m5z31RQh_V7echJ8Kna3Go3yWKCK4vtSwOWrFhiS5RTz6EkrGc3SkFKbb5vp8Wop_84myBtgnBmj4CczhTq2HcOxrJf4def6yDt2uBxyv4bTVGx9Yx3uB4Gx0iK5kYvfma6B_LnkRWk331wjuXKQtBGYIuWkR8J5QtvBmIRVaa7AA19Z4xMIEAqbcuQ5p4I9FCElthBrJd9YOcouHK4U27xxYWJJXcJoTvzG7zWtiV76fHDeQLgAWvJJ7ww4NFgjhqc6AKA_2afxa4c_lAvVZgFuKL3XSCL7PfKxp6GhjcGKeSRr80PT1gfFw2xi8X4ejjNm_prsUZ'
print
(decrypt_data(encrypted_data))
|
执行得到数据:
{"code":0,"dictResult":{"ce":{"word":{"trs":[{"voice":"hello&type=2","#text":"hello","#tran":"喂,你好(用于问候或打招呼);喂,你好(打电话时的招呼语);喂,你好(引起别人注意的招呼语);<非正式>喂,嘿 (认为别人说了蠢话或分心);<英,旧>嘿(表示惊讶);招呼,问候;(Hello)(法、印、美、俄)埃洛(人名);说(或大声说)“喂”;打招呼;"},{"voice":"hi&type=2","#text":"hi","#tran":"嗨!(表示问候或用以唤起注意);(Hi)人名;(柬)希;"},{"voice":"how+do+you+do&type=2","#text":"how do you do","#tran":"你好;"}],"phone":"nǐ hǎo","return-phrase":"你好"}}},"translateResult":[[{"tgt":"hello","src":"你好","srcPronounce":"nĭhăo"}]],"type":"zh-CHS2en"}
对照网页代码生成数据无误:
直接上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
import
hashlib
import
time
import
requests
import
base64
from
Crypto.Cipher
import
AES
from
Crypto.
Hash
import
MD5
class
Youdao():
def
__init__(
self
):
self
.url
=
'https://dict.youdao.com/webtranslate'
def
get_enmes(
self
, input_data):
mysticTime
=
str
(
int
(time.time()
*
1000
))
data
=
"client=fanyideskweb&mysticTime={}&product=webfanyi&key=fsdsogkndfokasodnaso"
.
format
(mysticTime)
sign
=
hashlib.md5(data.encode(encoding
=
'utf-8'
)).hexdigest()
# python中的MD5 加密
data
=
{
'i'
: input_data,
'from'
:
'auto'
,
'to'
: '',
'dictResult'
:
'true'
,
'keyid'
:
'webfanyi'
,
'sign'
: sign,
'client'
:
'fanyideskweb'
,
'product'
:
'webfanyi'
,
'appVersion'
:
'1.0.0'
,
'vendor'
:
'web'
,
'pointParam'
:
'client,mysticTime,product'
,
'mysticTime'
: mysticTime,
'keyfrom'
:
'fanyi.web'
}
headers
=
{
'Referer'
:
"https://fanyi.youdao.com"
,
"User-agent"
:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/113.0.0.0 Safari/537.36"
,
}
cookies
=
{
"OUTFOX_SEARCH_USER_ID"
:
"-390346570@10.110.96.160"
,
"OUTFOX_SEARCH_USER_ID_NCOO"
:
"1229562063.3960004"
}
response
=
requests.post(
self
.url, headers
=
headers, data
=
data, cookies
=
cookies).text
decode_mes
=
self
.decrypt_data(response)
print
(decode_mes)
def
decrypt_data(
self
, encrypted_data):
key
=
b
"ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
iv
=
b
"ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
cryptor
=
AES.new(
MD5.new(key).digest()[:
16
], AES.MODE_CBC, MD5.new(iv).digest()[:
16
]
)
decode_mes
=
cryptor.decrypt(base64.urlsafe_b64decode(encrypted_data)).decode(
"utf-8"
)
return
decode_mes
def
start():
input_data
=
input
(
'请输入要翻译内容:'
)
op.get_enmes(input_data)
if
__name__
=
=
'__main__'
:
op
=
Youdao()
start()
|
输入黑丝,执行结果:
任务完成。
更多【某道翻译接口参数还原调用】相关视频教程:www.yxfzedu.com