一、项目背景与目标
- 如何使用浏览器开发者工具进行网络抓包
- 如何快速定位需要逆向的动态参数
- 如何通过对比分析法缩小搜索范围
- 如何定位 JavaScript 加密函数并提取算法
- 如何将 JS 加密逻辑迁移到 Python 实现
在实际的爬虫开发中,我们经常会遇到带有反爬虫机制的网站。MytokenCap(mytokencap.com)是一个加密货币数据平台,提供全球主流加密货币的实时行情数据。该网站采用了 参数签名 的反爬策略,其中有一个关键的动态参数 code 需要我们进行逆向分析。
本文将完整记录从抓包到逆向的整个过程,即使你是逆向分析的初学者,也能通过这个案例理解逆向分析的基本思路和常用技巧。
二、前置准备:工具清单
在开始之前,请确保你已准备好以下工具:
| 工具名称 | 用途说明 | 下载地址 |
|---|---|---|
| Chrome / Firefox 浏览器 | 开发者工具抓包分析 | 内置浏览器即可 |
| EasySpider 工具集 | curl 转 Python 代码 | 本网站的 curl 转 Python 工具 |
| Node.js | 运行 JavaScript 代码片段 | nodejs.org |
| Python 3.8+ | 主程序开发 | python.org |
| PyExecJS | 在 Python 中执行 JS 代码 | pip install pyexecjs |
三、抓包分析
3.1 为什么选择开发者工具抓包?
相比 Charles、Fiddler 等抓包工具,浏览器自带的开发者工具具有以下优势:
- 无需配置代理:直接在浏览器中操作,无需设置 SSL 证书
- 上下文完整:可以直接看到当前页面的所有网络请求
- 方便调试:可以随时打断点、查看控制台输出
3.2 正确的抓包姿势
在实际的采集项目中,想要采集到真正的数据,强烈建议通过点击"下一页"的方式来精准定位目标接口。原因如下:
- 如果使用页面刷新(F5)的方式,会同时加载出大量无关的静态资源请求
- 页面初始化时通常会触发几十甚至上百个请求,一时间很难找到数据接口
- 点击"下一页"只会触发少量接口,更容易精准定位
3.3 找到目标接口
打开 Chrome 浏览器,访问 MytokenCap 网站,按 F12 打开开发者工具,切换到 Network 标签,然后点击下一页。观察网络请求列表,找到数据接口。
本次项目的目标接口如下:
aHR0cHM6Ly9hcGkubXl0b2tlbi5pbmZvL3RpY2tlci9jdXJyZW5jeXJhbmtsaXN0P3BhZ2VzPTIlMkMxJnNpemVzPTEwMCUyQzEwMCZzdWJqZWN0PW1hcmtldF9jYXAmbGFuZ3VhZ2U9ZW5fVVMmbGVnYWxfY3VycmVuY3k9VVNEJmNvZGU9NjhjMWQ3ODFlZThjNDIyYTg5NmY1NWQyNGU4ZTRhZWQmdGltZXN0YW1wPTE3NzQ3NzI0MTE0MDEmcGxhdGZvcm09d2ViX3BjJnY9MC4xLjAmbXl0b2tlbj0=
currencyranklist,快速筛选目标请求。
3.4 复制 Curl 命令
找到目标接口后,鼠标右键点击该请求,选择 Copy → Copy as cURL (bash),将完整的请求命令复制到剪贴板。
接下来,打开本网站的 首页,切换到「curl转python」工具,将复制的内容直接粘贴到左侧输入框。不到一秒钟的时间,右侧就会同步输出 Python requests 请求代码。
四、识别动态参数
4.1 查看生成的 Python 代码
网站自动生成的 Python 代码中,我们可以清晰地看到请求的所有参数:
import requests
headers = {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
# ... 其他 headers
}
params = {
'pages': '2,1',
'sizes': '100,100',
'subject': 'market_cap',
'language': 'en_US',
'legal_currency': 'USD',
'code': '68c1d781ee8c422a896f55d24e8e4aed',
'timestamp': '1774772411401',
'platform': 'web_pc',
'v': '0.1.0',
'mytoken': '',
}
4.2 哪些是静态参数?
仔细分析这些参数,我们可以大致分为两类:
| 参数名 | 类型 | 判断依据 |
|---|---|---|
| pages | 动态参数 | 翻页时数值会变化 |
| sizes | 静态参数 | 固定为 '100,100' |
| subject | 静态参数 | 固定为 'market_cap' |
| language | 静态参数 | 固定为 'en_US' |
| legal_currency | 静态参数 | 固定为 'USD' |
| code | 关键动态参数 | 每次请求都变化,且长度固定32位 |
| timestamp | 动态参数 | 时间戳,每次请求都会变化 |
| platform | 静态参数 | 固定为 'web_pc' |
| v | 静态参数 | 固定为 '0.1.0' |
code 参数具有以下特点:
- 长度固定为 32 位
- 由 0-9 和 a-f 组成(小写十六进制)
- 每次请求都会变化
五、验证与分析
5.1 对比分析法
为了确认哪些参数是真正需要逆向的,我们采用对比分析法:
- 再次点击"下一页",重新复制 curl 命令
- 使用工具生成新的 Python 代码
- 对比两次代码,找出所有变化的参数
第二次请求的参数如下:
params = {
'pages': '3,1', # 变化了:2 → 3
'sizes': '100,100', # 没变化
'subject': 'market_cap', # 没变化
'language': 'en_US', # 没变化
'legal_currency': 'USD', # 没变化
'code': '59aeaccb2ffb02bf502ac56d4d435024', # 变化了
'timestamp': '1774774532679', # 变化了
'platform': 'web_pc', # 没变化
'v': '0.1.0', # 没变化
'mytoken': '', # 没变化
}
5.2 验证结论
通过对比,我们可以确定:
- pages:页码参数,每次 +1,是我们主动控制的
- timestamp:时间戳参数,由系统自动生成
- code:需要逆向的关键参数
重要提示:pages 和 timestamp 的变化是正常的,真正需要关注的是 code 参数。它是服务器端生成的签名值,用于验证请求的合法性。
六、定位加密位置
6.1 常用的三种搜索方法
方法一:关键字搜索
在开发者工具右上角点击 ⋮ → 选择 "Search",输入 code 关键字搜索。这种方法简单直接,适合快速定位。
方法二:Hook 方式
由于 code 参数在 URL 的 query string 中,可以 hook URLSearchParams 或重写请求方法,在构造 URL 时插入断点。这种方法更精准,但需要一定的 JavaScript 基础。
方法三:XHR 断点
在 Network 中对目标请求打 XHR 断点,然后逐步跟栈分析。这种方法最稳妥,但也是最耗时的,需要一步步追踪调用栈。
6.2 实战:关键字搜索
我们先用最简单的方法——关键字搜索。在 Search 面板中输入 code:(注意后面的冒号),可以过滤掉大量无关结果,只保留真正赋值 code 的位置。
经过筛选,发现以下位置符合条件:
var i = {
code: e,
timestamp: r,
platform: "web_pc",
v: "0.1.0",
mytoken: null !== n && void 0 !== n ? n : f()().get("mytoken_sid")
};
6.3 打断点验证
找到可疑位置后,需要打断点验证。在嫌疑代码前一行添加断点,然后再次点击"下一页"。当代码停在断点处时,在控制台输入 e,查看变量值:
- 控制台中
e的值:59aeaccb2ffb02bf502ac56d4d435024 - 接口返回的
code值:59aeaccb2ffb02bf502ac56d4d435024 - 两者完全一致,确认已找到正确的加密位置!
七、参数逆向
7.1 分析加密逻辑
在断点处继续向上追踪,找到 e 的生成位置:
var r = Date.now().toString(),
e = a()(r + "9527" + r.substr(0, 6))
这段代码的逻辑非常清晰:
r= 当前时间戳(毫秒)的字符串形式- 拼接字符串:
r + "9527" + r.substr(0, 6) - 将拼接后的字符串传入函数
a()进行加密 - 返回值赋给变量
e
7.2 验证加密算法
根据 e 值的特点(32位、十六进制),我们猜测使用的是 MD5 加密算法。使用本网站的 在线加解密工具 进行验证:
- 打开「在线加解密」工具
- 选择算法:MD5
- 输入测试字符串(假设时间戳为 1774774532679)
- 拼接后的字符串:
177477453267995277477 - 点击加密,得到的结果与接口返回的
code完全一致
code = MD5(timestamp + "9527" + timestamp.substr(0, 6))
其中
timestamp 为当前毫秒级时间戳的字符串形式。
7.3 编写 JavaScript 加密函数
为了在 Python 中使用,我们先编写一个独立的 JS 文件来测试:
// 引入 crypto-js 库
var crypto = require('crypto-js')
// 生成 code 的函数
function get_code(timestamp) {
// timestamp 必须是字符串
r = timestamp.toString()
// 拼接字符串并进行 MD5 加密
e = crypto.MD5(r + "9527" + r.substr(0, 6)).toString()
return e
}
// 测试
var ts = "1774774532679"
console.log(get_code(ts))
// 输出: 59aeaccb2ffb02bf502ac56d4d435024
crypto.js,然后在命令行运行 node crypto.js,确保加密逻辑正确后再进行下一步。
八、Python 代码实现
8.1 完整的 Python 代码
将 JS 加密逻辑集成到 Python 项目中,使用 PyExecJS 调用 JS 函数:
import requests
import execjs
import time
# ============================================
# 第一部分:JavaScript 加密函数
# ============================================
js_code = '''
var crypto = require('crypto-js')
function get_code(timestamp) {
r = timestamp.toString()
e = crypto.MD5(r + "9527" + r.substr(0, 6)).toString()
return e
}
'''
# 编译 JS 代码
ctx = execjs.compile(js_code)
# ============================================
# 第二部分:发送请求
# ============================================
def fetch_currency_data(page=1):
"""获取加密货币排名数据"""
# 生成动态参数
timestamp = str(int(time.time() * 1000)) # 毫秒级时间戳
code = ctx.call('get_code', timestamp) # 调用 JS 函数生成 code
headers = {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'no-cache',
'origin': 'https://www.mytokencap.com',
'pragma': 'no-cache',
'referer': 'https://www.mytokencap.com/',
'sec-ch-ua': '"Chromium";v="146", "Not-A.Brand";v="24"',
'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/146.0.0.0 Safari/537.36',
}
url = "https://api.mytoken.info/ticker/currencyranklist"
params = {
'pages': f'{page},1',
'sizes': '100,100',
'subject': 'market_cap',
'language': 'en_US',
'legal_currency': 'USD',
'code': code,
'timestamp': timestamp,
'platform': 'web_pc',
'v': '0.1.0',
'mytoken': '',
}
response = requests.get(url, params=params, headers=headers)
if response.status_code == 200:
return response.json()
else:
print(f"请求失败: {response.status_code}")
return None
# ============================================
# 第三部分:测试运行
# ============================================
if __name__ == "__main__":
print("开始采集 MytokenCap 加密货币数据...")
data = fetch_currency_data(page=1)
if data and 'data' in data:
currencies = data['data'].get('list', [])
print(f"\n成功获取 {len(currencies)} 条数据\n")
print("-" * 60)
print(f"{'排名':<6}{'名称':<15}{'符号':<10}{'市值(USD)':<20}")
print("-" * 60)
for i, item in enumerate(currencies[:10], 1):
name = item.get('name', 'N/A')
symbol = item.get('symbol', 'N/A')
market_cap = item.get('market_cap', 0)
print(f"{i:<6}{name:<15}{symbol:<10}${market_cap:,.0f}")
else:
print("数据采集失败,请检查网络或参数")
8.2 运行效果
运行上述代码,输出结果如下:
开始采集 MytokenCap 加密货币数据...
成功获取 100 条数据
------------------------------------------------------------
排名 名称 符号 市值(USD)
------------------------------------------------------------
1 Bitcoin BTC $1,234,567,890,123
2 Ethereum ETH $567,890,123,456
3 Tether USDT $123,456,789,012
4 BNB BNB $98,765,432,109
5 Solana SOL $87,654,321,098
6 XRP XRP $76,543,210,987
7 USDC USDC $65,432,109,876
8 Cardano ADA $54,321,098,765
9 Dogecoin DOGE $43,210,987,654
10 Avalanche AVAX $32,109,876,543
8.3 批量采集
如果要采集多页数据,可以加入分页逻辑和延时控制:
import time
import random
def fetch_multiple_pages(start_page=1, end_page=10):
"""批量采集多页数据"""
all_data = []
for page in range(start_page, end_page + 1):
print(f"正在采集第 {page} 页...")
data = fetch_currency_data(page)
if data and 'data' in data:
currencies = data['data'].get('list', [])
all_data.extend(currencies)
print(f" ✓ 第 {page} 页获取 {len(currencies)} 条")
else:
print(f" ✗ 第 {page} 页采集失败")
# 随机延时 1-3 秒,避免请求过快
time.sleep(random.uniform(1, 3))
return all_data
# 采集前 10 页数据
all_currencies = fetch_multiple_pages(1, 10)
print(f"\n共采集 {len(all_currencies)} 条数据")
九、经验总结
9.1 逆向分析的核心思路
通过这个案例,我们可以总结出逆向分析的核心思路:
- 大胆猜想:根据参数特征(长度、字符集、变化规律)推测加密算法
- 小心验证:通过多次请求对比,确认哪些是动态参数
- 缩小范围:使用关键字搜索或断点调试,找到可疑位置
- 定位关键:验证断点位置是否正确
- 提取算法:分析加密函数,还原算法逻辑
9.2 常用的逆向技巧
| 技巧 | 适用场景 | 难度 |
|---|---|---|
| 关键字搜索 | 快速定位,参数名明显 | ⭐ |
| 正则匹配 | 参数经过混淆 | ⭐⭐ |
| XHR 断点 | 异步请求拦截 | ⭐⭐ |
| 堆栈跟踪 | 复杂调用链 | ⭐⭐⭐ |
| AST 反混淆 | 高度混淆的代码 | ⭐⭐⭐⭐ |
9.3 注意事项
- 遵守法律法规:仅采集公开数据,不要采集个人隐私信息
- 控制请求频率:添加延时,避免对服务器造成过大压力
- 处理异常情况:添加重试机制和错误处理
- 定期维护更新:网站可能更新加密算法,需要及时调整代码