分析网站
分析payload
从下图可以看出,sign99%是需要逆向的,mysticTime98%也是需要逆向的(推测结合生成sign),不然传过去这个参数干嘛。其他的参数都很随意,像yduuid这种99%是不需逆向
分析请求头
都是很常规的,不需逆向
分析返回数据
这种东西一看也是需要逆向的
因此得出结论,本次我们逆向的目标是sign和返回数据
寻找加密入口
url https://dict.youdao.com/webtranslate 搜索translate。只发现有一个js文件里面有,因此点进去。
看上去都是在声明函数,给这些都打上断点。⚠️这里断点打到里面去,不然断不下来
1:发现是在I这个函数断下来的,这里把后面的函数运行一下,发现是一个promise对象,这个时候需要注意。有promise一定有发送请求和接收请求,发送请求就在这个函数里面。这个函数里面可以查看函数
接收数据是在后面的then里面。❗这个对我们后面数据解密找解密入口非常有帮助
进入这个I这个函数看一下,打个断点观察参数值,一共有三个参数,分别是e为url,t为发送的数据,o为加的请求头。再观察sign什么的都已经加上去了,因此不用想了,肯定是I这个箭头函数对这个数据进行了加密。接下来分析代码
2:划上去都运行一下,发现sign是在w函数加密的,点进去w应该是找到了加密入口
3:点击进去w,获取了一个时间戳,然后把e和时间戳传给k
4:继续找k,其实这三个函数挨着的,因此k再找j,发现是一个md5
e10开头,标准md5,把这个k函数的逻辑扣下来去python里面复现就可以了
寻找解密入口
还记得我们刚刚说的promise对象有大用吗?现在找解密入口就派上了用场。解密的寻找有几种思路:
- 对于网站使用promise,首先找interceptors拦截器
- 对于ajax请求,找success函数
- 上面都没有,找他调用这个axios发送请求的地方,then就是处理的成功的回调。看看哪个地方用到了这个声明promise的函数。
这里我们采用的就是第三个方法。
需要注意的是,上面的仅仅是声明这个返回promise对象的一个函数I,并没有调用,那么去哪里找调用呢?直接在当前js文件搜索(ctrl+f)I,找到这个地方。这里应该是暴露出去供外部调用(猜想)
因此全局搜这个getTextTranslateResult,一共有两处,点进去
1:看到这个then的话,解密入口也已经找到了。接下来就是打断点,单步调试。看到这个decodeData函数,key,iv莫名开始兴奋。
2:点进去decodeData这个函数。像是一个aes加密。
这个y和alloc函数对我们传进来的key和iv做了一个处理。运行完y发现数据被变成数字数组了,但是alloc运行完是不变的(因此不用管它)。这个数字数组一般是字符串。点进去看看怎么转化的
3:发现就是求了个md5,然后变成字节串(就变成我们的数字数组),记住这个逻辑,等下也要复现
4:接着就是很简单了。继续走decodeData那个逻辑,注意这个i.a.createDecipheriv是Nodejs一个标准库,假设我们不知道,可以先假设它是标准的,然后复现,看看解密的结果一不一样。不一样再去js代码扣逻辑
然后就是解密完毕转成base64。
📝 注意这个return是先赋值再return s,就是做一个拼接。最后得到了最终的数据。这是前端需要干的,在py里面decrypt方法会自动处理最后一个数据块。我们不需要做拼接
Python代码复现
import base64
import requests
import time
from hashlib import md5
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
url = "https://dict.youdao.com/webtranslate"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Referer": "https://fanyi.youdao.com/",
"Cookie": "OUTFOX_SEARCH_USER_ID=1138325494@10.105.137.204; OUTFOX_SEARCH_USER_ID_NCOO=46378145.29139559",
"Origin": "https://fanyi.youdao.com"
}
# 伪造时间戳,py里面是秒,转成毫秒
times = int(time.time() * 1000)
# 伪造sign,直接复现md5
# 准备数据
d = "fanyideskweb"
e = times
u = "webfanyi"
t = "fsdsogkndfokasodnaso"
str = f"client={d}&mysticTime={e}&product={u}&key={t}"
# 加密得到sign
sign = md5(str.encode("utf-8")).hexdigest()
data = {
"i": "like", # 要翻译的值
"from": "auto",
"to": "",
"dictResult": "true",
"keyid": "webfanyi",
"sign": sign,
"client": "fanyideskweb",
"product": "webfanyi",
"appVersion": "1.0.0",
"vendor": "web",
"pointParam": "client,mysticTime,product",
"mysticTime": e,
"keyfrom": "fanyi.web",
"mid": "1",
"screen": "1",
"model": "1",
"network": "wifi",
"abtest": "0",
"yduuid": "abcdefg"
}
# 发送请求
res = requests.post(url, headers=headers, data=data)
print(res.text)
# 对数据进行解密
key = 'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
iv = 'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'
# 复现对key和iv处理的逻辑
key=md5(key.encode('utf-8')).digest()
iv=md5(iv.encode('utf-8')).digest()
data = res.text.replace(
"_", "/").replace("-", "+") # 处理一下传输的base64字符串
# 创建一个AES对象
aes = AES.new(key=key, IV=iv, mode=AES.MODE_CBC)
result= unpad(aes.decrypt(base64.b64decode(data)), 16).decode("utf-8") # 解密数据,先转成字节串,再解码,最后去掉填充
print(result)
总结
网易有道翻译算是逆向的入门案例了,没有混淆,对新手很友好,还用到md5,base64,aes,promise,数字数组这些知识点。有了这个接口直接调这个就不用调api了,当然我们这里只是学习使用。普通翻译好像是几千个字符有限制。
这个从头写到尾我的收获也很大,之前还迷迷糊糊的找加密入口,现在又过了一遍,对一些思路更清晰了。每一步都有详细的截图,可以说是保姆式手把手教学了
文章评论