前言
终于,经过半个月的js学习,现在开始了JS逆向之路,按照计划本周也就是9月7号前需要完成一个JS逆向案例报告(接口分析),当然哈,这个计划可能会根据我的学习进度改变。在b站找了个小课看看,虽然老师花了15分钟卖课,但质量感觉还行,至少我可以跟着上手,后续如何尽情期待~
定义
学习之前,首先了解了一下JS逆向是什么,总结一下就是分析前端JavaScript代码,破解网站核心逻辑(数据加密、接口签名、动态渲染等)这样爬虫就可以模拟合法请求获取数据。 具体来说,一般学习的重点是接口参数加密,数据动态渲染,环境监测对抗,验证码逻辑破解,如果可以的话,将在之后的学习中针对上述几个点都进行练习(虽然目标是安卓逆向!)
学习重点
- 通信方式
- 签名生成
- 消息摘要加密算法
- 定位加密+算法还原
- 实战
通信方式
本次实验的网站采用wss => websocket 动态 ajax 渲染 => 实时更新页面内容
- 通信层:浏览器和服务器用 wss (WebSocket secure) 建立长连接,持续收发数据。
- 展示层:前端用 动态 AJAX 渲染把这些实时数据更新到网页,不用整页刷新。
Websocket / wss
一种浏览器和服务器之间的全双工通信协议。
- HTTP 通信是「一次请求对应一次响应」,完了就断开。
- WebSocket 建立连接后,客户端和服务端可以互相主动发送消息,保持实时通信。
ws:// 与 wss://
- ws://:明文的 WebSocket(类似 http://)。
- wss://:加密的 WebSocket,基于 TLS/SSL(类似 https://)。
动态 ajax 渲染
AJAX(Asynchronous JavaScript and XML):浏览器端用来向服务器异步请求数据的方式。 页面不用整页刷新,只把新数据更新到页面上。
签名生成
比较典型的两类签名核心差异:
参数签名(每次都需要更新):随请求参数变化而变。用于保证完整性(参数未被篡改)与身份/权限(握有密钥的一方才算合法)。
时效性 code(基于时间戳):随时间窗口变化而变。用于保证时效性与防重放。
参数签名
(还没学待更新)
时效性 code(基于时间戳)
这类 code 只依赖时间相关信息(有时再加 nonce/deviceId),在一个时间窗口内有效,超时失效。
通用流程
- 取当前时间戳 ts(10 位秒或 13 位毫秒)。
- 按约定拼接原串(常见:ts + 固定串 + ts子串,或 method+path+ts+nonce 等)。
- 选择摘要算法(MD5/SHA256/HMAC-*)生成 code。
- 携带 ts(和 nonce)一起发给后端。
- 服务端校验:
now - ts <= 窗口(例如 30s/60s) - 选用相同算法重算比对
- 可选:校验 nonce 未重复(防重放)
消息摘要加密算法
如何快速判断加密算法,可以根据算法特性判断:
md5 => 输出 32 位
sha1 => 输出 40 位
sha256 => 输出 64 位
hmacMD5 => 输出 32 位,需要 秘钥
实战定位加密+算法还原
先根据payload查找加密参数,这里用code举例
关键词搜索
可以搜索 code:,code=
调用堆栈分析
- 调试面板Initiator栈堆列
- 分区,断点
- 点击匿名堆栈断点,查看作用域,注意特殊控制流结构,查看相关函数和参数
- 还可以通过堆栈的文件地址
- 排除参数消失(加密)的地方
- 通过控制台逐个定位
hook技术
问题:一个数据在被加密之前,会做什么处理?
常见操作:
- 转化为JSON字符串
- JSON.stringify() => 加密前处理
- JSON.parse() => 解密后处理
(AI)Mytoken实战
本次实战以MYtoken网站作为操作对象
网站是实时更新的,所以打开检查就可以到数据会刷新
点击网络可以看到面板有很多请求,点进去预览发现有很多请求
定位核心请求
- 预览(Preview)里能直接看到页面上的列表数据:currency_id / name / symbol / rank …,而不是图片、css 或静态 aboutus.json。
- URL 里有 currency_last_price、currencyranklist、pages=1、sizes=100、subject=market_cap 等明显的业务参数;
- 滚动或切换页签后,这个请求才发出(瀑布图上时间点对应用户操作),符合“数据拉取”的触发时机。
点击发现确实里面有当前页面的100条数据(截图略)
右键这个请求->复制->https://curlconverter.com/->生成请求代码->复制到pycharm->运行代码->成功获取
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
import requests
headers = {
'accept': '*/*',
'accept-language': 'zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6',
'cache-control': 'no-cache',
'origin': 'https://mytokencap.com',
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': 'https://mytokencap.com/',
'sec-ch-ua': '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
}
params = {
'pages': '3,1',
'sizes': '100,100',
'subject': 'market_cap',
'language': 'en_US',
'legal_currency': 'USD',
'code': '0133da2e2af4f7085250f993d311ad75',
'timestamp': '1756799034781',
'platform': 'web_pc',
'v': '0.1.0',
'mytoken': '',
}
response = requests.get('https://api.mytoken.info/ticker/currencyranklist', params=params, headers=headers)
查看请求的载荷,发现code是一个32位的加密数,根据之前的技巧,推测可能是由MD5算法加密完成
定位code加密函数,搜索法得到的結果太多不易筛查,选择堆栈和hook方法
通过堆栈分区,debugger断点调试和加密参数分析得到结果,具体如图:
- 断点作用域发现控制流语句(截图略)
- 该堆栈之前断点查看发现code加密,查看密文什么时候变成明文,或者有单独的进行赋值
- 将分析出来
code加密语句发送给ai让其分析:code: s()(e + “9527” + e.substr(0, 6)) - 让ai实现这个加密算法在windows11环境里有python3.9实现这个加密
- 放入之前写好的请求代码里
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
60
61
62
63
64
65
66
#算法还原
# Wss =>websocket
# 动态ajax渲染 =>货币网站 => 实时更新签名:
# 签名:
# 时效性
# 每次都是需要新的 密文 =>明文(请求参数(分页参数))=>对应的JS代码进行加密
# code:密文 =>明文(时间戳)=>对应的JS代码进行加密
import requests
import hashlib
import time
import argparse
from typing import Optional
def gen_code(e_str) :
"""
生成签名 code:
code = MD5( timestamp + "9527" + timestamp[:6] )
:param ts_ms: 13位毫秒时间戳(字符串)。不传则用当前时间。
:return: 32位小写hex MD5
"""
raw = e_str + "9527" + e_str[:6]
return hashlib.md5(raw.encode("utf-8")).hexdigest()
headers = {
'accept': '*/*',
'accept-language': 'zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6',
'cache-control': 'no-cache',
'origin': 'https://mytokencap.com',
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': 'https://mytokencap.com/',
'sec-ch-ua': '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
}
# 获取当前13位时间按戳
e = int(time.time()*1000)
e_str = str(e)
code = gen_code(e_str)
params = {
'pages': '4,1',#分页参数
'sizes': '100,100',
'subject': 'market_cap',
'language': 'en_US',
'legal_currency': 'USD',
# 因为code在变化,所以现在要看一下code怎么来的
'code': code,#变化密文 =>明文(时间戳)=>对应JS代码进行加密 (因为是时效性,所以只包含时间戳)
'timestamp': '1756798889200',#13位时间戳
'platform': 'web_pc',
'v': '0.1.0',
'mytoken': '',
}
print(params)
# response = requests.get('https://api.mytoken.info/ticker/currencyranklist', params=params, headers=headers)
# print(response.json())
hook方法使用了其他网站做示范,这里通过分析发现加密需要进行JSON.stringify转变,所以和ai对话:在浏览器控制台中编写一个hook脚本,当进行JSON。stringify转变就debugger 然后将代码复制到控制台中运行,就可以得到加密位置。