关键词驱动的 WASM / Web 风控签名逆向实战

关键词驱动的 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/

目标:找到三类东西

类型含义示例
调用者谁调用了 signrequestInterceptor、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 调用 CToken 由哪些数据构成
关注 函数顺序关注 输入来源
对复刻价值有限对复刻直接有用

推荐画法:

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 结果从几百条降到几十条。

第五层:给节点打权重,优先分析高价值路径

原则:不是所有关键词都值得同等精力。给节点赋权,按权重排序分析。

示例权重表:

关键词权重说明
sha25610核心哈希,几乎必看
timestamp9高频输入,决定时效性
nonce9随机源,复刻难点
deviceId8设备绑定,影响风控
encodeBase646编码层,通常简单
logger1噪声
debug1噪声
metrics1噪声

使用方式:

  1. 把依赖树中所有节点按权重排序
  2. 从权重最高的路径开始分析
  3. 权重 ≤ 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 混淆或控制流平坦化上一份的第七、八、九层

原则:先证明,再动手;先找输入输出,再理解实现。

No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
Previous
Next