话不多说,直接切入正题
目录
一、百度翻译--------“https://fanyi.baidu.com/”
二、有道翻译------------‘https://fanyi.youdao.com/index.html’
一、百度翻译--------“https://fanyi.baidu.com/”
1、分析参数
点击此网站进入,右键检查->network-->xhr,尝试输入翻译的句子或词语,抓取相关的包,抓取的包如图

点击Headers查看后得出该包发送的是post请求,poyload是携带的参数。
接下来分析参数的变化 ,输入多个要翻译的词语,进行对比后得知,如下图
在这里看到了token,虽然这两次的抓包token的值没有发生变化,但是在很多的案例当中token是我们着重破解的参数,在后面寻找构造方法时,顺便也找一下token的构造方法是否为定值。
因此我们只剩下一个参数需要需要寻找。全局搜索ctrl+shift+f 输入sign,出现如下的包,如下图

打开进入 局部搜索ctrl+f,输入sign,找出疑似的地方,如图
此处出现了我们发送post请求包时的参数,我们打上断点尝试运行一下

这边并没有直接显示,鼠标悬停才会显示,为了方便观察参数的变化,我们在右侧watch面板输入各个参数。

根据各个值的显示,这就是我们想要找的地方。注意到sign值是w(e)生成的,我们选中w,鼠标悬停该处。
该方法给我们带到了这个地方,我们再次输入测试的数据,看是否经过该函数,并看该方法的运行过程是怎样的

这个函数确实是我们想要找的,输入的数据经过这个函数进行了处理。
在pycharm当中创建js文件,将这段代码扣下到pycharm当中,更改函数名为result
此处需要node.js环境,搜索一下有很多的教程安装一下就好了

尝试运行一下

发现报错,r 没有定义,点击路径到报错的位置。回到网页,选中 r 鼠标悬停,发现是一个具体的
值,测试一下,我们输入多个数据查看是不是固定的值。

测试过后发现,我们怎么改变测试的值,该值都不会发生变化,因此直接写死 r

再次运行

n 没有定义,点击报错的路径,回到浏览器的该位置,选中n, 鼠标悬停,打开该方法的位置。


发现该方法就在上面,我们抠出来放到pycharm当中 ,再次运行

运行成功,这个值跟我们在抓包时遇到的sign值非常的相似。因此,sign的构造方法找到并获取完毕。
我们顺便再看看token,是不是一个写死的值。老方法全局搜索token。找到相关的包

点击进入,发现token就是一个写死的字符串。到此所有的参数都已经分析完毕!

剩下的就是敲代码啦!!!
2、代码奉上!!!
# ! /usr/bin/nev python
# -*-coding:utf8-*-
import time
import requests
import execjs
class BaiduTranslate(object):
def __init__(self):
input_key = input("请输入你想要翻译的词语或句子:")
# input_key = '你会回到我身边的'
self.headers = {
'Host': 'fanyi.baidu.com',
'Cookie': 'BIDUPSID=BDAACC6CD97DC76CC40D1EFA3841FBC8; PSTM=1686281380; BAIDUID=EDEE1C25592CA16B843963B0DE282004:FG=1; H_PS_PSSID=; BDORZ=FFFB88E999055A3F8A630C64834BD6D0; BAIDUID_BFESS=EDEE1C25592CA16B843963B0DE282004:FG=1; delPer=0; PSINO=6; BCLID=8204938500369048893; BCLID_BFESS=8204938500369048893; BDSFRCVID=xnFOJexroG0ZmSbf7dgZuOnU0uweG7bTDYrEOwXPsp3LGJLVFakFEG0Pts1-dEu-S2OOogKKLmOTHpKF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; BDSFRCVID_BFESS=xnFOJexroG0ZmSbf7dgZuOnU0uweG7bTDYrEOwXPsp3LGJLVFakFEG0Pts1-dEu-S2OOogKKLmOTHpKF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; H_BDCLCKID_SF=tRAOoC_-tDvDqTrP-trf5DCShUFshP6lB2Q-XPoO3KJADfOPb6JfbpI0XUTPJnQf5mkf3fbgy4op8P3y0bb2DUA1y4vp0toW3eTxoUJ2-KDVeh5Gqq-KXU4ebPRiWPr9QgbjahQ7tt5W8ncFbT7l5hKpbt-q0x-jLTnhVn0MBCK0HPonHjKBj5cQ3e; H_BDCLCKID_SF_BFESS=tRAOoC_-tDvDqTrP-trf5DCShUFshP6lB2Q-XPoO3KJADfOPb6JfbpI0XUTPJnQf5mkf3fbgy4op8P3y0bb2DUA1y4vp0toW3eTxoUJ2-KDVeh5Gqq-KXU4ebPRiWPr9QgbjahQ7tt5W8ncFbT7l5hKpbt-q0x-jLTnhVn0MBCK0HPonHjKBj5cQ3e; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1686628272; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1686645641; ab_sr=1.0.1_MTU3NmVhMDY0MDJhODQ2ODI0M2Q5NWYzNTNmMGFkNGZhYjRkOGM3ZjQxNWViNzFhY2QxNmI3ZGUzYjlhMWNkZDc5MTcxNWY3YjUxM2M2N2MzNWE1NmRiYjlmMzIyMmEwZDhkYzZjOGRiZTNmYWNjMDY4ZWZmOWYwY2JlZTEzOWNkZmMyYzgxMjIzNGFmMDAwYmUxNjhkMjNmYTgwNjJmMw==',
'Origin': 'https://fanyi.baidu.com',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Referer': 'https://fanyi.baidu.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'
}
self.data = {
'from': 'zh',
'to': 'en',
'query': input_key,
'transtype': 'realtime',
'simple_means_flag': '3',
'sign': self.get_sign(input_key),
'token': '724b81baa4603c1cdd08df6a5ba0e35f',
'domain': 'common',
'ts': str(int(time.time()*1000))
}
self.url = 'https://fanyi.baidu.com/v2transapi?from=zh&to=en'
def get_sign(self, input_key):
with open("../js/youdao_translation_js.js", "r", encoding="utf-8")as f:
sign_js = f.read()
sign = execjs.compile(sign_js).call("result", input_key)
return sign
def translate(self):
is_right = True
while is_right:
try:
response = requests.post(url=self.url, headers=self.headers, data=self.data).json()
print(response['trans_result']['data'][0]['dst'])
is_right = False
except:
continue
def main(self):
self.translate()
if __name__ == '__main__':
tra = BaiduTranslate()
tra.main()
js代码部分
var r = '320305.131321201'
function result(t) {
var o, i = t.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
if (null === i) {
var a = t.length;
a > 30 && (t = "".concat(t.substr(0, 10)).concat(t.substr(Math.floor(a / 2) - 5, 10)).concat(t.substr(-10, 10)))
} else {
for (var s = t.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), c = 0, l = s.length, u = []; c < l; c++)
"" !== s[c] && u.push.apply(u, function(t) {
if (Array.isArray(t))
return e(t)
}(o = s[c].split("")) || function(t) {
if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"])
return Array.from(t)
}(o) || function(t, n) {
if (t) {
if ("string" == typeof t)
return e(t, n);
var r = Object.prototype.toString.call(t).slice(8, -1);
return "Object" === r && t.constructor && (r = t.constructor.name),
"Map" === r || "Set" === r ? Array.from(t) : "Arguments" === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) ? e(t, n) : void 0
}
}(o) || function() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")
}()),
c !== l - 1 && u.push(i[c]);
var p = u.length;
p > 30 && (t = u.slice(0, 10).join("") + u.slice(Math.floor(p / 2) - 5, Math.floor(p / 2) + 5).join("") + u.slice(-10).join(""))
}
for (var d = "".concat(String.fromCharCode(103)).concat(String.fromCharCode(116)).concat(String.fromCharCode(107)), h = (null !== r ? r : (r = window[d] || "") || "").split("."), f = Number(h[0]) || 0, m = Number(h[1]) || 0, g = [], y = 0, v = 0; v < t.length; v++) {
var _ = t.charCodeAt(v);
_ < 128 ? g[y++] = _ : (_ < 2048 ? g[y++] = _ >> 6 | 192 : (55296 == (64512 & _) && v + 1 < t.length && 56320 == (64512 & t.charCodeAt(v + 1)) ? (_ = 65536 + ((1023 & _) << 10) + (1023 & t.charCodeAt(++v)),
g[y++] = _ >> 18 | 240,
g[y++] = _ >> 12 & 63 | 128) : g[y++] = _ >> 12 | 224,
g[y++] = _ >> 6 & 63 | 128),
g[y++] = 63 & _ | 128)
}
for (var b = f, w = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(97)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(54)), k = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(51)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(98)) + "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(102)), x = 0; x < g.length; x++)
b = n(b += g[x], w);
return b = n(b, k),
(b ^= m) < 0 && (b = 2147483648 + (2147483647 & b)),
"".concat((b %= 1e6).toString(), ".").concat(b ^ f)
}
function n(t, e) {
for (var n = 0; n < e.length - 2; n += 3) {
var r = e.charAt(n + 2);
r = "a" <= r ? r.charCodeAt(0) - 87 : Number(r),
r = "+" === e.charAt(n + 1) ? t >>> r : t << r,
t = "+" === e.charAt(n) ? t + r & 4294967295 : t ^ r
}
return t
}
二、有道翻译------------‘https://fanyi.youdao.com/index.html’
照样话不多说,直接入题!!!
右键检查,network--->xhr, 输入几个测试的数据,得到如下几个包。

点击响应,看返回的数据判断是否是我们需要的包
啊。这。。。对于返回的数据进行了加密处理。。那就只好一步一步的来。
1、先分析参数的变化
老样子,全局搜索sign,发现该js文件

点击进入,找到疑似构造方法的位置
打上断点测试一下,看看测试的数据显示是怎么样的

我们发现,e 这个参数的值直接显示出来了,跟我们输入的数据的变化没有关系。是否代表着这是个定值呢,我们多测试几遍,看每次输出的值是否一样

我们回过头来看生成sign值的方法,老样子选中 h 方法,鼠标悬停,点击该方法位置路径

方法就在上面,发现,该方法只有一个返回值,并且返回值又调用了一个方法 g ,并且携带了一些参数。
顺着找到,g 方法就紧挨着,并且观察得知,进行了 md5 加密,是一个连环的嵌套,
这一下就疏通了,各个参数都已经解决,剩下复原代码就变得很简单了

调试一下看是不是这样的

确实能获取到正确的值
这次不用复制该 js 代码,python自带的加密工具就可以实现,试试看
import time
import hashlib
def get_sign():
d = 'fanyideskweb'
ee = 'fsdsogkndfokasodnaso'
u = 'webfanyi'
ti = str(int(time.time()*1000))
def g(e):
return hashlib.md5(e.encode("utf-8")).hexdigest()
def h(e, t):
md5_str = f'client={d}&mysticTime={e}&product={u}&key={t}'
return g(md5_str)
return h(ti, ee)
data = {
'i': '我找到你了',
'from': 'auto',
'to': '',
'dictResult': True,
'keyid': 'webfanyi',
'sign': get_sign(),
'client': 'fanyideskweb',
'product': 'webfanyi',
'appVersion': '1.0.0',
'vendor': 'web',
'pointParam': 'client,mysticTime,product',
'mysticTime': str(int(time.time()*1000)),
'keyfrom': 'fanyi.web'
}
headers = {
'Cookie': 'OUTFOX_SEARCH_USER_ID=-197651357@10.112.57.88; OUTFOX_SEARCH_USER_ID_NCOO=2126674871.429606',
'Host': 'dict.youdao.com',
'Origin': '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',
'Referer': 'https://fanyi.youdao.com/',
}
url = 'https://dict.youdao.com/webtranslate'
response = requests.post(url, data=data, headers=headers).text
print(response)
也能够输出正确的结果,但是人家加密了,看不到,气不气。

从这一步开始,破解响应数据。
2、破解响应加密
首先我们在最开始找到sign打断点的位置,我们点击step over next function按钮,或者直接F10也行,一步一步的往下执行,看能否在某个方法当中找到生成的这个值,找出疑似构造方法的位置
点击点击点击,这个方法很闷,也许有的案例中间有很多的步骤,遇上个成百上千的循环岂不是噶屁了,但是本次案例当中可以使用该方法。
运行到这里,出现了response当中的值。。。emmm。。。但是测试了一下好像不太对

继续点,找到了这个位置,好像是构造方法,打上断点测试一下

发现 n 的值上面有解析的内容,是我们想要找的东西。因此足以说明,加密方法就在这里。回过头来,查看该构造方法,发现有decodekey,和decodeiv这两个玩意儿,那是不是AES加密呢。有可能

再回个头,n 的生成是首先调用了decodeData,再传入了三个参数之后生成的。
那就找一下decodeData。全局搜索decodeData,找到如图

这个值又是T生成的,再找 T, 发现T的构造就在上面,我们观察一下过程

实锤了,妥妥的AES加密,但是注意一点,a,c 两个值又是 v 函数处理过才传进去的。v 函数怎么感觉好像在哪见过呢

回头找找,发现了在找sign生成的时候,这个方法写在了旁边。不过也可以在右侧watch面板上写入 v 方法找到函数位置, 或者直接选中 v 方法查看,任选其一即可,如图

发现是MD5加密,
好了。decodeData函数分析完毕,但是回过头来,还有两个传入参数decodeKey和decodeIv的值是不知的。

老样子,那既然有这两个值,就一定有构造该值的方法
直接全局搜索decodekey,和decodeiv,发现是两个写死的字符串,如下

全部的参数都已经分析完毕。
3、直接上代码
# ! /usr/bin/nev python
# -*-coding:utf8-*-
import requests,base64
from Crypto.Cipher import AES
import time
import hashlib
def get_sign():
d = 'fanyideskweb'
ee = 'fsdsogkndfokasodnaso'
u = 'webfanyi'
ti = str(int(time.time()*1000))
def g(e):
return hashlib.md5(e.encode("utf-8")).hexdigest()
def h(e, t):
md5_str = f'client={d}&mysticTime={e}&product={u}&key={t}'
return g(md5_str)
return h(ti, ee)
data = {
'i': input("输入想要翻译的词语或者句子:"),
'from': 'auto',
'to': '',
'dictResult': True,
'keyid': 'webfanyi',
'sign': get_sign(),
'client': 'fanyideskweb',
'product': 'webfanyi',
'appVersion': '1.0.0',
'vendor': 'web',
'pointParam': 'client,mysticTime,product',
'mysticTime': str(int(time.time()*1000)),
'keyfrom': 'fanyi.web'
}
headers = {
'Cookie': 'OUTFOX_SEARCH_USER_ID=-197651357@10.112.57.88; OUTFOX_SEARCH_USER_ID_NCOO=2126674871.429606',
'Host': 'dict.youdao.com',
'Origin': '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',
'Referer': 'https://fanyi.youdao.com/',
}
url = 'https://dict.youdao.com/webtranslate'
response = requests.post(url, data=data, headers=headers).text
# 偏移量
decodeiv = "ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4"
# 秘钥
decodekey = "ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl"
# 在本案例中,先是对两个参数进行了参数加密
key = hashlib.md5(decodekey.encode(encoding='utf-8')).digest()
iv = hashlib.md5(decodeiv.encode(encoding='utf-8')).digest()
# AES解密
aes_en = AES.new(key, AES.MODE_CBC, iv)
# 将已经加密的数据放进该方法
data_new = base64.urlsafe_b64decode(response)
# 参数准备完毕后,进行解密
result = aes_en.decrypt(data_new).decode('utf8')
print(result)
总结
这是我在学习js逆向当中,作为新手的练习项目。在完成后记录一下。如果有兴趣的小伙伴,有错误的地方或者有更好的方法,欢迎指正分享!!!
9783

被折叠的 条评论
为什么被折叠?



