关键词驱动的 WASM / Web 风控签名逆向实战手册
核心思路:不要分析代码,分析关键词之间的关系。 真正重要的不是“这个函数怎么实现”,而是“timestamp 从哪来、nonce 从哪来、deviceId 从哪来,它们最后进了哪个 hash”。
总流程图
入口
↓
提取关键词
↓
建立实体表
↓
grep 反向扩散
↓
构建调用树 / 依赖树
↓
标记关键节点 + 打权重
↓
结合执行日志过滤
↓
裁剪垃圾分支
↓
形成最小执行路径
第一层:入口驱动,提取第一批关键词
原则:先不要看内部实现,先记录入口附近的“名字”。
假设你在网络请求或 JS 代码中发现:
sign(payload)
先不要点进去看 sign 内部。先记录它周围出现的关键词:
sign
payload
token
verify
encrypt
这就是你的 第一批关键词。
用 grep 反向扩散:
# 在解包后的 JS / WASM 文本格式 / sourcemap 中搜索
grep -rn "sign" ./dist/
grep -rn "payload" ./dist/
grep -rn "token" ./dist/
grep -rn "verify" ./dist/
grep -rn "encrypt" ./dist/
目标:找到三类东西
| 类型 | 含义 | 示例 |
|---|---|---|
| 调用者 | 谁调用了 sign | requestInterceptor、xhr.send |
| 被调用者 | sign 内部调用了谁 | sha256、base64 |
| 构造位置 | 参数在哪里被组装 | buildPayload、makeParams |
第二层:关键词持续扩张
原则:每追进一层,就把新出现的“名字”加入关键词集合,继续 grep。
追进 sign(payload) 后发现:
buildPayload()
于是得到新关键词:
buildPayload
timestamp
nonce
deviceId
继续扩散:
grep -rn "buildPayload" ./dist/
grep -rn "timestamp" ./dist/
grep -rn "nonce" ./dist/
grep -rn "deviceId" ./dist/
经过两到三轮,你通常会得到类似这样的结构:
sign
├── buildPayload
│ ├── timestamp
│ ├── nonce
│ └── deviceId
│
├── sha256
│
└── encodeBase64
这时候已经接近真相了。
第三层:画依赖树,而不是调用树
关键区别:
| 调用树 | 依赖树 |
|---|---|
| A 调用 B,B 调用 C | Token 由哪些数据构成 |
| 关注 函数顺序 | 关注 输入来源 |
| 对复刻价值有限 | 对复刻直接有用 |
推荐画法:
Token
│
├── Timestamp
├── Nonce
├── DeviceID
├── UserAgent
└── SHA256
为什么依赖树更有价值:
复刻时你需要的不是“函数按什么顺序调用”,而是:
- 输入从哪来
- 每个输入如何预处理
- 它们最终如何组合、哈希、编码
实操建议: 用 Markdown 或 Mermaid 画,不要用大型 UML 工具。依赖树应该小到能贴在一屏内。
graph TD
T[Token] --> TS[Timestamp]
T --> N[Nonce]
T --> D[DeviceID]
T --> UA[UserAgent]
T --> H[SHA256]
第四层:关键词分类,过滤噪声
给关键词分 A / B / C 三类,避免树被噪声撑爆。
A 类:核心(必须追)
sign
token
encrypt
verify
hmac
sha
aes
md5
rsa
这些词一旦出现,必须追到实现或数据来源。
B 类:参数来源(重点追)
timestamp
nonce
device
fingerprint
random
session
cookie
referer
userAgent
这些决定了签名的 输入空间,是复刻成功的关键。
C 类:噪声(通常先忽略)
log
debug
error
warn
metrics
analytics
track
report
这些词出现时,先标记但不深入。除非它们 直接参与 签名输入,否则不进依赖树。
效果: 树会干净很多,grep 结果从几百条降到几十条。
第五层:给节点打权重,优先分析高价值路径
原则:不是所有关键词都值得同等精力。给节点赋权,按权重排序分析。
示例权重表:
| 关键词 | 权重 | 说明 |
|---|---|---|
sha256 | 10 | 核心哈希,几乎必看 |
timestamp | 9 | 高频输入,决定时效性 |
nonce | 9 | 随机源,复刻难点 |
deviceId | 8 | 设备绑定,影响风控 |
encodeBase64 | 6 | 编码层,通常简单 |
logger | 1 | 噪声 |
debug | 1 | 噪声 |
metrics | 1 | 噪声 |
使用方式:
- 把依赖树中所有节点按权重排序
- 从权重最高的路径开始分析
- 权重 ≤ 2 的节点,除非被高权重节点直接引用,否则跳过
这相当于 自动过滤垃圾代码,把精力集中在真正影响签名结果的节点上。
第六层:结合执行日志,区分“引用”与“执行”
静态 grep 的根本问题:引用 ≠ 执行。
一个函数被 import 或被调用,不代表它在你的目标业务路径中真正运行。
方法一:console.trace() 插桩
在关键函数入口插入:
// 在 sign / buildPayload / sha256 等 A 类函数入口
function sign(payload) {
console.trace('[TRACE] sign called');
// ... 原逻辑
}
方法二:performance.now() 打点
const t0 = performance.now();
// ... 可疑逻辑
const t1 = performance.now();
console.log('[TIMING]', t1 - t0, 'ms');
方法三:Proxy 包装(适用于对象/模块)
const traced = new Proxy(wasmExports, {
get(target, prop) {
const val = target[prop];
if (typeof val === 'function') {
return (...args) => {
console.log('[CALL]', prop, args);
return val.apply(target, args);
};
}
return val;
}
});
执行后得到真实运行路径:
sign
↓
buildPayload
↓
nonce
↓
sha256
↓
base64
而以下函数 从未执行:
encryptV2
encryptV3
legacySign
结论:直接排除未执行分支。 不要试图理解它们,不要删除它们,直接从分析范围中划掉。
第七层:形成最小执行路径
最终目标不是理解整个项目,而是得到一条可复刻的最小路径。
HTTP Request
↓
参数构造
↓
timestamp
↓
nonce
↓
payload
↓
sha256
↓
base64
↓
token
这条路径已经足够:
- Node.js 复刻
- Rust 复刻
- Go 复刻
- Python 复刻
复刻检查清单:
timestamp来源:Date.now()?performance.now()?服务器时间?nonce来源:Math.random()?crypto.getRandomValues()?计数器?deviceId来源:localStorage?canvas 指纹?WebGL 指纹?payload拼接顺序:key=value&key=value?JSON?自定义分隔符?- 哈希算法:SHA-256?MD5?HMAC?
- 编码:Base64?Hex?URL-safe Base64?
- 是否有密钥/盐:硬编码?动态下发?
只要这些问题的答案确定了,哪怕代码还有 90% 没看懂,签名复刻通常已经能完成。
实战命令速查
# 1. 入口关键词搜索
grep -rn "sign\|token\|encrypt" ./dist/ --include="*.js"
# 2. 反向扩散(找调用者)
grep -rn "buildPayload\|makeParams" ./dist/ --include="*.js" -B2 -A2
# 3. 在 WASM 文本格式中搜索
wasm2wat app.wasm -o app.wat
grep -n "sign\|sha\|hmac" app.wat
# 4. 在反汇编输出中搜索
wasm-objdump -d app.wasm | grep -i "call.*sign"
# 5. 提取字符串常量(常含关键词)
strings app.wasm | grep -iE "sign|token|nonce|timestamp|device"
一句话总结
不要分析代码,分析关键词之间的关系。
把 timestamp、nonce、deviceId 这些关键词串成一棵 数据依赖树,找到它们最终进入哪个 hash、如何编码成 token。这比逐函数阅读汇编高效得多,也直接对应复刻所需的最小信息集。
与上一份《WASM 逆向实战手册》的配合关系
| 场景 | 用哪份手册 |
|---|---|
| 已经拿到 WASM,想知道哪些代码在跑 | 上一份:Coverage + Hook Export + wasm-opt |
| 面对混淆严重的 JS/WASM,想快速定位签名逻辑 | 本份:关键词扩散 + 依赖树 + 执行日志 |
| 需要复刻签名 | 本份为主,上一份的 Hook 手段为辅 |
| 需要理解 VM 混淆或控制流平坦化 | 上一份的第七、八、九层 |
原则:先证明,再动手;先找输入输出,再理解实现。