使用AI完全还原京东h5st协议还原-如何用AI还原多态VM
我一直使用 AI 辅助 JS 逆向分析。从简单的 OB 混淆、sojson,到单 VM 保护,AI 都能很好地完成任务。多态虚拟机 (Polymorphic VM)保护。与单 VM 不同,多态 VM 有30+ 个独立的 dispatcher,同一个 opcode 在不同 VM 中执行完全不同的操作。如果不使用 AI,这种多态 VM 几乎无法静态分析——光是建立 30 套 opcode 映射表就是噩
前言
我一直使用 AI 辅助 JS 逆向分析。从简单的 OB 混淆、sojson,到单 VM 保护,AI 都能很好地完成任务。
但这次遇到了一个新挑战:多态虚拟机 (Polymorphic VM) 保护。
与单 VM 不同,多态 VM 有 30+ 个独立的 dispatcher,同一个 opcode 在不同 VM 中执行完全不同的操作。如果不使用 AI,这种多态 VM 几乎无法静态分析——光是建立 30 套 opcode 映射表就是噩梦。
但 AI 让多 VM 的静态还原成为可能。本文分享三个核心干货:
- 多态 VM 的分析方法
- AI + 人工的协作模式
- 隐蔽的 SHA256 魔改如何发现
一、挑战:多态 VM
1.1 什么是多态 VM?
普通 VM 保护:
|
1 2 |
|
多态 VM 保护:
|
1 2 3 |
|
同一个数字,在不同 VM 中含义完全不同。
1.2 难度对比
| 保护类型 | 分析复杂度 | 工具复用性 |
|---|---|---|
| 字符串混淆 | 低 | 高 |
| 控制流平坦化 | 中 | 中 |
| 单 VM 保护 | 中高 | 高 |
| 多态 VM | 极高 | 极低 |
1.3 具体挑战
- 30+ 套 opcode 映射:每个 VM 都需要单独分析
- 嵌套调用链:签名逻辑分散在多个 VM 中
- 隐式参数传递:通过栈操作传递,难以追踪
- 魔改标准算法:SHA256、Base64 都经过定制修改
二、方法:AI 辅助分析
2.1 整体流程
|
1 |
|
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 | 变量语义重命名 | _2n8l4 → Bytecode |
| 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 分析代码,找到断点位置
- AI 阅读解混淆后的代码
- 识别关键函数入口、可疑的魔改点
- 告诉人工:「在第 X 行设置断点,观察变量 Y 的值」
-
人工在浏览器中操作
- 打开 DevTools,设置断点
- 触发签名请求
- 记录断点处的变量值、调用栈
-
把结果反馈给 AI
- 「断点触发了,变量 X = "abc123",调用栈是 A → B → C」
- AI 根据实际值分析逻辑
-
AI 调整分析方向
- 如果结果符合预期,继续下一个断点
- 如果不符合,AI 重新分析,找新的断点位置
为什么这种模式有效?
- AI 擅长:阅读大量代码、识别模式、推理逻辑
- 人工擅长:操作浏览器、获取运行时数据
- 互补:AI 无法执行 JS,人工不想读混淆代码
实际案例:
发现 _append 魔改就是这样找到的:
|
1 2 3 4 5 6 |
|
这种「AI 指挥 + 人工执行」的模式,比纯静态分析效率高很多。
三、干货:SHA256 魔改还原
在所有算法还原中,魔改 SHA256 是最难的部分。不是因为 SHA256 本身复杂,而是魔改点极其隐蔽。
3.1 发现过程
最初,我以为只需要找到 SHA256 的调用点,直接用标准库就行。但对比浏览器输出后发现结果不一致:
|
1 2 |
|
于是开始追踪 SHA256 的实现。
3.2 第一层魔改:_seData1 预处理
输入数据在进入 SHA256 前,会先经过 _seData1 函数处理,追加 10 个字符的动态后缀:
|
1 |
|
这 10 个字符是根据输入内容计算的,算法是将输入分成 10 块,每块计算字符码累加值,映射到自定义字符表。
3.3 第二层魔改:_eData 预处理(最隐蔽)
找到 _seData1 后,我以为已经完成了。但验证时发现结果还是不对。
问题出在 _append 函数被魔改了。
在标准 SHA256 实现中,_append 只是简单地追加数据。但京东的实现中,_append 内部调用了 _eData,会额外追加一个固定后缀:
|
1 2 3 4 5 6 7 8 9 |
|
这个魔改非常隐蔽,因为:
- 函数名没变,看起来是标准实现
- 魔改逻辑在另一个 VM 中(
eDataVM) - 不仔细追踪调用链根本发现不了
3.4 第三层魔改:字节交换
最后,SHA256 的输出还会进行字节交换——交换第 0 字节和第 2 字节:
|
1 |
|
3.5 完整调用链
|
1 2 3 4 5 6 7 8 9 10 11 |
|
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 |
|
4.2 VM 分类
分析了 17 个核心 VM,按功能分类:
签名核心 VM(6 个):
| VM | 功能 | 说明 |
|---|---|---|
| l34 | 签名入口 | signSync() 的实际实现 |
| l31 | h5st 组装 | 协调各子 VM,组装最终结果 |
| l24 | 密钥生成 | 根据 token、fingerprint 等生成 key |
| l28 | 签名计算 | 调用 jd_sha256 计算签名 |
| l30 | 签名数据 | 生成 signData 字段 |
| l27 | 最终组装 | 拼接 10 个字段生成 h5st |
加密算法 VM(6 个):
| VM | 功能 | 说明 |
|---|---|---|
| wm_encode | 自定义 Base64 | 5 步变换的编码算法 |
| seData1 | SHA256 预处理 | 追加 10 字符动态后缀 |
| eData | 数据编码 | 追加固定后缀(隐藏在 _append 中) |
| sha256_seData | SHA256 专用预处理 | 配合 seData1 使用 |
| append | 数据追加 | 被魔改的辅助函数 |
| eKey | HMAC 密钥处理 | 密钥相关处理 |
辅助 VM(5 个):l25、l29、l32、l33、l23(环境检测)
4.3 多态 opcode 示例
同一个 opcode 数字在不同 VM 中的含义:
| Opcode | l34 (签名入口) | l31 (h5st组装) | l23 (环境检测) |
|---|---|---|---|
| 2 | PUSH_IMM | - | STORE_wK |
| 5 | DUP_GET_PROP | PUSH_IMM | NE |
| 7 | STORE_wR | SET_PROP_CHAIN | PUSH_STR |
| 12 | POP | PUSH_OBJ | STORE_wF |
这就是为什么叫「多态」——同一个数字,在不同 VM 中执行完全不同的操作。
五、环境检测分析
5.1 检测目的
签名过程中会收集大量浏览器指纹,用于:
- 识别自动化工具(Selenium、Puppeteer 等)
- 生成设备唯一标识
- 检测运行环境是否正常
5.2 检测项目
通过分析 l23(环境检测 VM),发现以下检测项:
运行时检测:
| 检测项 | 目标 | 检测方式 |
|---|---|---|
typeof Deno |
Deno 运行时 | 类型检查 |
typeof Bun |
Bun 运行时 | 类型检查 |
typeof process |
Node.js | 类型检查 |
navigator.webdriver |
Selenium/Puppeteer | 属性检查 |
window.phantom |
PhantomJS | 属性检查 |
window._phantom |
PhantomJS | 属性检查 |
window.callPhantom |
PhantomJS | 属性检查 |
浏览器指纹:
| 类别 | 收集项 |
|---|---|
| Navigator | userAgent, platform, language, languages, hardwareConcurrency |
| Screen | width, height, availWidth, availHeight, colorDepth, pixelRatio |
| Canvas | toDataURL 指纹 |
| WebGL | renderer, vendor |
| Audio | AudioContext 指纹 |
| 字体 | 通过文本宽度测量检测已安装字体 |
5.3 envData 结构
这些检测结果会被编码到 h5st 的第 8 个字段(envData)中:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
5.4 绕过策略
如果要在非浏览器环境运行,需要:
- 模拟完整的浏览器环境(navigator、screen、document 等)
- 确保
webdriver等检测项返回正常值 - 生成合理的 canvas/webgl 指纹
或者更简单的方式:直接固定 envData,因为这个字段主要用于风控,不影响签名验证。
六、总结
6.1 AI 辅助逆向的优势
- 处理重复模式:30+ 个 VM 的 opcode 映射,AI 可以快速建立
- 代码生成:还原后的算法,AI 可以直接生成目标语言实现
- 调用链追踪:隐蔽的魔改点,AI 可以帮助快速定位
6.2 方法论
|
1 2 3 |
|
6.3 AI 的局限性
AI 不是万能的,以下场景仍需人工介入:
- 动态值提取:盐值、模板字符串等需要从字节码中定位
- 最终验证:确保还原结果与原始实现一致
结语
多态 VM 是目前 JS 保护的高级形态,传统工具和方法很难应对。但借助 AI 的模式识别和代码生成能力,可以大幅提升分析效率。
核心还是逆向工程的方法论:理解保护机制的原理,找到正确的切入点,持续验证和迭代。
希望本文对研究 VM 保护的朋友有所启发。
更多推荐



所有评论(0)