Post

使用AI完全还原某东h5st协议——如何用AI还原多态VM

使用AI完全还原某东h5st协议——如何用AI还原多态VM

本文仅供安全研究和学习交流,请勿用于非法用途。

前言

我一直使用 AI 辅助 JS 逆向分析。从简单的 OB 混淆、sojson,到单 VM 保护,AI 都能很好地完成任务。

但这次遇到了一个新挑战:多态虚拟机 (Polymorphic VM) 保护。

与单 VM 不同,多态 VM 有 30+ 个独立的 dispatcher,同一个 opcode 在不同 VM 中执行完全不同的操作。如果不使用 AI,这种多态 VM 几乎无法静态分析——光是建立 30 套 opcode 映射表就是噩梦。

但 AI 让多 VM 的静态还原成为可能。本文分享三个核心干货:

  1. 多态 VM 的分析方法
  2. AI + 人工的协作模式
  3. 隐蔽的 SHA256 魔改如何发现

一、挑战:多态 VM

1.1 什么是多态 VM?

普通 VM 保护:

1
2
case 2 → 永远是 PUSH 操作
case 5 → 永远是 POP 操作

多态 VM 保护:

1
2
3
VM_A: case 2 → PUSH
VM_B: case 2 → STORE
VM_C: case 2 → CALL

同一个数字,在不同 VM 中含义完全不同。

1.2 难度对比

保护类型分析复杂度工具复用性
字符串混淆
控制流平坦化
单 VM 保护中高
多态 VM极高极低

1.3 具体挑战

  1. 30+ 套 opcode 映射:每个 VM 都需要单独分析
  2. 嵌套调用链:签名逻辑分散在多个 VM 中
  3. 隐式参数传递:通过栈操作传递,难以追踪
  4. 魔改标准算法:SHA256、Base64 都经过定制修改

二、方法:AI 辅助分析

2.1 整体流程

1
AST 解混淆 → 提取结构化数据 → AI 分析模式 → 人工验证 → AI 生成代码

2.2 AST 解混淆

工作流程:Gemini 识别混淆类型 → Claude Code 编写 AST 脚本 → 迭代优化

拿到 450KB 的混淆代码后,先让 Gemini 识别混淆类型,然后让 Claude Code 编写 Babel AST 脚本。整个脚本分 9 个 Pass:

Pass功能说明
1字符串解密函数还原_4o5l4("xvvw")"test"
2字符串数组还原_1w8l4[42]"appid"
3常量折叠0x1a2b + 0x3c4d → 数值
4逗号表达式展开(a=1, b=2, c()) → 多行
5变量语义重命名_2n8l4Bytecode
6对象字典展开_$wR.add(x,y)x + y
7-9清理死代码移除、未使用变量删除

调试技巧:分 Pass 执行,每个 Pass 单独测试,报错信息直接发给 AI 修复。

2.3 AI 分析 VM

解混淆后,用 AST 工具提取:字节码数组、字符串表、VM dispatcher 结构。

然后让 AI 识别:

  • 每个 VM 的 opcode 语义
  • VM 之间的调用关系
  • 关键算法的位置

关键点:用浏览器实际执行结果验证 AI 的分析,发现不一致时针对性重新分析。

2.4 AI + 人工协作模式(重要干货)

这是我在分析过程中摸索出的高效协作模式:

1
2
3
4
5
┌─────────────┐     分析代码,定位关键点     ┌─────────────┐
│             │ ──────────────────────────→ │             │
│     AI      │                             │    人工     │
│             │ ←────────────────────────── │             │
└─────────────┘     执行结果、变量值         └─────────────┘

具体流程

  1. AI 分析代码,找到断点位置
    • AI 阅读解混淆后的代码
    • 识别关键函数入口、可疑的魔改点
    • 告诉人工:「在第 X 行设置断点,观察变量 Y 的值」
  2. 人工在浏览器中操作
    • 打开 DevTools,设置断点
    • 触发签名请求
    • 记录断点处的变量值、调用栈
  3. 把结果反馈给 AI
    • 「断点触发了,变量 X = “abc123”,调用栈是 A → B → C」
    • AI 根据实际值分析逻辑
  4. AI 调整分析方向
    • 如果结果符合预期,继续下一个断点
    • 如果不符合,AI 重新分析,找新的断点位置

为什么这种模式有效?

  • AI 擅长:阅读大量代码、识别模式、推理逻辑
  • 人工擅长:操作浏览器、获取运行时数据
  • 互补:AI 无法执行 JS,人工不想读混淆代码

实际案例

发现 _append 魔改就是这样找到的:

1
2
3
4
5
6
AI: 「SHA256 结果不对,在 _append 函数入口设断点,看看传入的参数」
人: 「参数是 "test",但 this.buffer 变成了 "testXXXXXX"」
AI: 「说明 _append 内部有额外处理,让我分析这个函数...」
AI: 「找到了,_append 调用了 _eData,在 _eData 设断点」
人: 「_eData 返回值是原字符串 + 固定后缀」
AI: 「这就是第二层魔改!」

这种「AI 指挥 + 人工执行」的模式,比纯静态分析效率高很多。


三、干货:SHA256 魔改还原

在所有算法还原中,魔改 SHA256 是最难的部分。不是因为 SHA256 本身复杂,而是魔改点极其隐蔽。

3.1 发现过程

最初,我以为只需要找到 SHA256 的调用点,直接用标准库就行。但对比浏览器输出后发现结果不一致:

1
2
标准 SHA256("test") = 9f86d08...
京东 SHA256("test") = 完全不同的值

于是开始追踪 SHA256 的实现。

3.2 第一层魔改:_seData1 预处理

输入数据在进入 SHA256 前,会先经过 _seData1 函数处理,追加 10 个字符的动态后缀:

1
"test" → "test" + [10字符动态后缀]

这 10 个字符是根据输入内容计算的,算法是将输入分成 10 块,每块计算字符码累加值,映射到自定义字符表。

3.3 第二层魔改:_eData 预处理(最隐蔽)

找到 _seData1 后,我以为已经完成了。但验证时发现结果还是不对。

问题出在 _append 函数被魔改了

在标准 SHA256 实现中,_append 只是简单地追加数据。但京东的实现中,_append 内部调用了 _eData,会额外追加一个固定后缀:

1
2
3
4
5
6
7
8
9
// 标准实现
_append(data) {
    this.buffer += data;
}

// 京东魔改
_append(data) {
    this.buffer += _eData(data);  // _eData 会追加固定后缀
}

这个魔改非常隐蔽,因为:

  1. 函数名没变,看起来是标准实现
  2. 魔改逻辑在另一个 VM 中(eData VM)
  3. 不仔细追踪调用链根本发现不了

3.4 第三层魔改:字节交换

最后,SHA256 的输出还会进行字节交换——交换第 0 字节和第 2 字节:

1
[a, b, c, d, ...] → [c, b, a, d, ...]

3.5 完整调用链

1
2
3
4
5
6
7
8
9
10
11
jd_sha256(input)
    ↓
_seData1(input)           ← 追加 10 字符动态后缀
    ↓
_eData(result)            ← 追加固定后缀 (隐藏在 _append 中)
    ↓
标准 SHA256
    ↓
swap(byte[0], byte[2])    ← 字节交换
    ↓
输出 64 位 hex

3.6 教训

这个案例说明:魔改可能发生在任何地方,包括看起来无害的辅助函数

如果不是 AI 帮我快速分析了所有相关 VM 的字节码,手动追踪这个调用链可能需要很长时间。


四、VM 架构分析

4.1 整体架构

通过 AI 分析,我梳理出了签名的完整 VM 调用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
signSync() 入口
    ↓
┌─────────────────────────────────────────────────────────┐
│  l34 (签名入口 VM)                                       │
│      ↓                                                  │
│  l31 (h5st 组装 VM) ─────────────────────────────────┐  │
│      │                                               │  │
│      ├─→ l24 (密钥生成 VM)                            │  │
│      │       └─→ l25 (辅助函数)                       │  │
│      │                                               │  │
│      ├─→ l28 (签名计算 VM) ──→ jd_sha256             │  │
│      │       └─→ l29 (辅助函数)                       │  │
│      │                                               │  │
│      ├─→ l30 (签名数据 VM)                            │  │
│      │                                               │  │
│      ├─→ wm_encode (自定义 Base64 VM)                 │  │
│      │                                               │  │
│      └─→ l27 (最终组装 VM) ←─────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

4.2 VM 分类

分析了 17 个核心 VM,按功能分类:

签名核心 VM(6 个)

VM功能说明
l34签名入口signSync() 的实际实现
l31h5st 组装协调各子 VM,组装最终结果
l24密钥生成根据 token、fingerprint 等生成 key
l28签名计算调用 jd_sha256 计算签名
l30签名数据生成 signData 字段
l27最终组装拼接 10 个字段生成 h5st

加密算法 VM(6 个)

VM功能说明
wm_encode自定义 Base645 步变换的编码算法
seData1SHA256 预处理追加 10 字符动态后缀
eData数据编码追加固定后缀(隐藏在 _append 中)
sha256_seDataSHA256 专用预处理配合 seData1 使用
append数据追加被魔改的辅助函数
eKeyHMAC 密钥处理密钥相关处理

辅助 VM(5 个):l25、l29、l32、l33、l23(环境检测)

4.3 多态 opcode 示例

同一个 opcode 数字在不同 VM 中的含义:

Opcodel34 (签名入口)l31 (h5st组装)l23 (环境检测)
2PUSH_IMM-STORE_wK
5DUP_GET_PROPPUSH_IMMNE
7STORE_wRSET_PROP_CHAINPUSH_STR
12POPPUSH_OBJSTORE_wF

这就是为什么叫「多态」——同一个数字,在不同 VM 中执行完全不同的操作


五、环境检测分析

5.1 检测目的

签名过程中会收集大量浏览器指纹,用于:

  1. 识别自动化工具(Selenium、Puppeteer 等)
  2. 生成设备唯一标识
  3. 检测运行环境是否正常

5.2 检测项目

通过分析 l23(环境检测 VM),发现以下检测项:

运行时检测

检测项目标检测方式
typeof DenoDeno 运行时类型检查
typeof BunBun 运行时类型检查
typeof processNode.js类型检查
navigator.webdriverSelenium/Puppeteer属性检查
window.phantomPhantomJS属性检查
window._phantomPhantomJS属性检查
window.callPhantomPhantomJS属性检查

浏览器指纹

类别收集项
NavigatoruserAgent, platform, language, languages, hardwareConcurrency
Screenwidth, height, availWidth, availHeight, colorDepth, pixelRatio
CanvastoDataURL 指纹
WebGLrenderer, vendor
AudioAudioContext 指纹
字体通过文本宽度测量检测已安装字体

5.3 envData 结构

这些检测结果会被编码到 h5st 的第 8 个字段(envData)中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "sua": "Mozilla/5.0...",      // userAgent
    "pp": {},                      // 插件信息
    "extend": {
        "wd": false,               // webdriver
        "l": 0,                    // 语言数量
        "ls": "zh-CN",             // 语言
        "ml": 8,                   // 最大触摸点
        "pl": 5,                   // 插件数量
        "av": "5.0...",            // appVersion
        "ua": "Mozilla/5.0...",    // userAgent
        "pf": "MacIntel",          // platform
        "canvas": "...",           // canvas 指纹
        "webglFp": "..."           // webgl 指纹
    },
    "random": "xxxxxxxx"           // 随机值
}

5.4 绕过策略

如果要在非浏览器环境运行,需要:

  1. 模拟完整的浏览器环境(navigator、screen、document 等)
  2. 确保 webdriver 等检测项返回正常值
  3. 生成合理的 canvas/webgl 指纹

或者更简单的方式:直接固定 envData,因为这个字段主要用于风控,不影响签名验证。


六、总结

6.1 AI 辅助逆向的优势

  1. 处理重复模式:30+ 个 VM 的 opcode 映射,AI 可以快速建立
  2. 代码生成:还原后的算法,AI 可以直接生成目标语言实现
  3. 调用链追踪:隐蔽的魔改点,AI 可以帮助快速定位

6.2 方法论

1
2
3
1. 先整体后局部:理解架构再深入细节
2. 先静态后动态:AST 分析 + 运行时验证
3. 持续验证:每还原一部分就验证一部分

6.3 AI 的局限性

AI 不是万能的,以下场景仍需人工介入:

  • 动态值提取:盐值、模板字符串等需要从字节码中定位
  • 最终验证:确保还原结果与原始实现一致

结语

多态 VM 是目前 JS 保护的高级形态,传统工具和方法很难应对。但借助 AI 的模式识别和代码生成能力,可以大幅提升分析效率。

核心还是逆向工程的方法论:理解保护机制的原理,找到正确的切入点,持续验证和迭代。

希望本文对研究 VM 保护的朋友有所启发。


免责声明:本文仅用于安全研究和技术交流,请遵守相关法律法规。

This post is licensed under CC BY 4.0 by the author.