JS逆向

JS逆向

Yiuhang Chan

JS逆向简要介绍案例:模拟百度翻译请求

检查百度翻译请求

首先,对百度翻译的请求进行了检查。通过分析发现,百度翻译使用的是POST请求方法。在翻译过程中的请求载荷内容进行了详细观察。

请求参数分析

在请求的脚本参数中,发现了fromto字段。通过模拟操作,可以推断出这些参数代表着翻译的源语言和目标语言,例如从中文翻译到英文。进一步观察表单数据,可以看到请求所携带的所有参数。

请求头

对于请求的表头参数也进行了检查,以便在模拟请求时能够更准确地伪装。

Sign参数

首先ctrl+shift+f 全局搜索sign参数所在源代码

一个个查看这些sign参数,对符合js模式的进行断点

重新执行翻译过程,可以发现断点停留在sign: b(e),即js方法调用处

在控制台输入参数可以看到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
import time
import jsdata
import requests

# 设置请求URL和头部信息
url = 'https://fanyi.baidu.com/v2transapi?from=zh&to=en'
headers = {
'User-Agent': 'Mozilla/5.0 ...',
'Cookie': 'PSTM=1694260469; BIDUPSID=...; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1703248315;',
'Acs-Token': '1703217664841_1703248346365_aQyvrnWWYIouJv2VqASm...'
}

# 获取用户输入的翻译内容
a = input('输入你要翻译的内容:')

# 调用jsdata模块生成签名
sign = jsdata.make_js_data(a)

# 构建表单数据
form_data = {
"from": "zh",
"to": "en",
"query": a,
"transtype": "realtime",
"simple_means_flag": "3",
"sign": sign,
"token": "2977c992c92eb0731d89f23d17b6edd7",
"domain": "common",
"ts": str(int(time.time()*1000)),
}

# 发送POST请求并输出结果
res = requests.post(url, headers=headers, data=form_data)
print(res.json().get("trans_result").get("data")[0].get('dst'))

以上代码首先导入了必要的模块,设置了请求的URL和头部信息。通过用户输入获取翻译内容,然后使用jsdata模块来生成签名。随后构建表单数据,包括语言类型、查询词、时间戳等,并发送POST请求。最后,从返回的JSON数据中提取并打印翻译结果。

信息

"ts": str(int(time.time()*1000)) 用于生成一个时间戳,这个时间戳是表单数据(form_data)的一部分,发送给百度翻译的服务器。下面详细解释这个时间戳的生成过程:

  1. time.time(): 这个函数来自Python的time模块,用于获取当前时间。它返回的是一个浮点数,代表自1970年1月1日(称为Unix纪元或Epoch时间)以来的秒数。
  2. time.time() * 1000: 由于time.time()返回的是秒数,而通常服务器端需要的时间戳是以毫秒为单位的。因此,将其乘以1000,将秒转换成毫秒。
  3. int(...): 这个函数将浮点数转换为整数。在这里,它将乘以1000后的结果转换成整数形式,因为时间戳通常是整数形式的毫秒值。
  4. str(...): 最后,int类型的时间戳被转换成字符串(str),因为在构建表单数据时,需要的是字符串形式的时间戳。

综合来看,"ts": str(int(time.time()*1000)) 这一行代码生成了一个表示当前时间的毫秒级时间戳,并将其转换为字符串格式,以便作为HTTP请求的一部分发送。

JS逆向代码

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
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
}

var r = null;
const gtk = '320305.131321201';
function test_JS_re_code(t,gtk) {
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, u = s.length, l = []; c < u; c++)
"" !== s[c] && l.push.apply(l, 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 !== u - 1 && l.push(i[c]);
var p = l.length;
p > 30 && (t = l.slice(0, 10).join("") + l.slice(Math.floor(p / 2) - 5, Math.floor(p / 2) + 5).join("") + l.slice(-10).join(""))
}
for (var d = "".concat(String.fromCharCode(103)).concat(String.fromCharCode(116)).concat(String.fromCharCode(107)), h = (null !== r ? r : (r = gtk || "") || "").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)
}

对复制过来的调用JavaScript源代码进行适配修改:

  1. 去掉t.exports属性,不需要赋值,重新命名函数为test_JS_re_code

n 函数

这个函数对给定的整数t和字符串e进行一系列位操作。

  • t: 初始整数值。
  • e: 用于操作的字符串,其字符用于决定如何变换t
  • 函数通过遍历字符串e并根据其字符执行位移和位异或操作来转换t
  • 最后,返回转换后的整数t

test_JS_re_code函数

这个函数是主要的函数,用于生成百度翻译的sign值。

  • t: 要翻译的文本。
  • gtk: 百度翻译API的一个关键变量,用于生成签名的固定密钥之一。

这个函数的工作流程大致如下:

  1. 处理输入文本: 如果文本过长或包含特殊字符(比如表情符号),它会被适当地截断或转换。
  2. 字符编码处理: 文本t中的每个字符被转换为其ASCII或Unicode编码。
  3. 生成初始值: 通过某些固定字符和可能是密钥gtk的变量r组合,生成一个初始整数值b
  4. 迭代处理: 对上一步得到的整数值b和文本的每个字符编码进行迭代处理,使用n函数和特定的字符串(在变量wk中定义)进行变换。
  5. 生成最终sign: 最终的b值经过一系列操作后,与gtk的某个部分进行操作,生成最终的sign值。

执行测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
import execjs


def make_js_data(que):
with open('translate.js', 'r') as f:
translate = f.read()
# 生成js操作对象
js_data = execjs.compile(translate)


sign = js_data.call('test_JS_re_code',que)

return sign
  • 导入了execjs模块,这个模块允许Python运行JavaScript代码。
  • 定义了一个名为make_js_data的函数,这个函数接受一个参数que,这个参数是用户想要翻译的文本。
  • 这段代码从translate.js文件中读取JavaScript代码。translate.js包含了先前分析的那段JavaScript代码(包括n函数和test_JS_re_code函数)。execjs.compile方法编译这段JavaScript代码,使其可以在Python环境中执行。
  • 通过execjs对象的call方法调用translate.js中的test_JS_re_code函数,传入要翻译的文本que作为参数,生成并返回sign值。
  • 函数返回计算得到的sign值。

注意

  • gtktoken 的实际值通常是动态生成的,可能会随着百度翻译网站的更新而变化。在实际应用中,这些值需要从网站的某些部分(如JavaScript代码或隐藏的表单字段)中动态获取。
  • 硬编码的值只用于示例和概念验证。如果要实现一个可靠和长期有效的解决方案,就需要实现一个方法来动态获取这些值。
  • 使用硬编码的值可能会导致在百度翻译更新其验证机制后,代码无法正常工作。
  • gtk: 这通常是一个固定的字符串,用于生成翻译请求中的sign参数。sign参数是基于要翻译的文本和gtk值计算得出的,用于验证请求的合法性。
  • token: 这是另一个重要的验证参数,通常也是在发送翻译请求时必须包含的。它可能用于识别用户或会话,或者作为另一层的安全验证。
  • gtk'320305.131321201'token2977c992c92eb0731d89f23d17b6edd7
  • 标题: JS逆向
  • 作者: Yiuhang Chan
  • 创建于 : 2022-01-08 18:12:46
  • 更新于 : 2024-02-28 18:35:10
  • 链接: https://www.yiuhangblog.com/2022/01/08/20220108JS逆向/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论