首页
社区
课程
招聘
[原创]AI 辅助还原自定义 VMP 保护方案
发表于: 10小时前 470

[原创]AI 辅助还原自定义 VMP 保护方案

10小时前
470

VMP(Virtual Machine Protection)是当前移动端 SO 保护的主流方案。核心思路是将关键算法编译为自定义字节码,由嵌入 SO 中的解释器运行时执行。传统静态分析在这种保护下几乎失效——你看到的不是算法本身,而是一个通用解释器在逐条执行你看不懂的字节码。

本文分享一套 AI 辅助的 VMP 逆向方法论。不针对任何具体目标,目前已经在某红色书籍app验证可行,只谈为什么这么做怎么做

整体流程:

VMP 保护的 SO 通常叠加控制流平坦化(CFF)等混淆。IDA 反编译后看到的是大量 switch-case dispatcher,真实逻辑被打散。不去混淆的话,即使是 VM 解释器之外的辅助函数(参数编组、内存分配、类型分发)也完全不可读,严重阻碍整体理解。

angr 可以自动识别 CFF 的 dispatcher 结构,通过符号执行恢复真实控制流:

AI 可以辅助编写 angr 去混淆脚本,分析失败 case 并调参。

angr 并非万能。对于失败的函数,可以在动态调试框架中执行目标函数,记录实际走过的基本块序列,然后根据 trace 直接 patch 条件跳转。

两种方法互补:angr 批量处理大部分函数,trace 修补解决剩余。

ida-mcp 允许 AI 直接查询 IDA 数据库,理论上很方便。但 VMP 场景下有个现实问题:函数太大了

混淆后的函数动辄几千到几万字节,反编译后上千行伪代码。通过 ida-mcp 交互式查看,两三个函数就把 AI 的上下文窗口塞满,后续分析无法继续。

编写 IDA 脚本,一次性把所有函数的伪代码、调用关系、交叉引用导出到本地文件。AI 按需读取单个文件,不浪费上下文,同一份数据可以反复查阅,支持多轮迭代分析。

Web 端逆向相对简单:浏览器自带开发者工具,JavaScript 代码直接可见,可以断点、修改变量、实时调试。

移动端完全不同:

unidbgUnicornQiling 等框架解决的核心问题是:不需要真机,在 PC 上模拟执行 ARM 代码

它们的本质都是 CPU 指令级模拟器,区别在于上层封装:

对于 VMP 逆向,我们需要的核心能力是:

unidbg 在 Android SO 逆向场景下最方便——JNI 环境已经搭好,加载 SO 后直接调用 native 函数。以下以 unidbg 为例说明,但同样的思路适用于其他框架。

模拟执行 SO 时需要补全运行环境。这是一个"缺什么补什么"的过程——执行到某个 JNI 调用报错了,就补一个返回值;读某个文件缺失了,就模拟一个。

AI 在这一步可以帮忙查找常见 Android API 的返回值格式,加速补桩。

对 VM 解释器区域开启指令 trace,产生百万行级别的执行记录。这是后续所有分析的基础数据

百万行 trace 人工根本无法处理。但对 AI 来说,这正是它的强项:

所有字节码 VM 的核心都是一个 fetch-decode-execute 循环。在 ARM64 层面表现为一组固定的寄存器模式。通过在 dispatcher 处下断点,可以建立 ARM 寄存器到 VM 角色的映射——字节码 PC、求值栈指针、当前 opcode 等。

AI 可以通过分析 dispatcher 附近的指令模式自动推断这些映射。

核心方法:在 dispatcher 断点处 dump 求值栈,观察每条字节码指令前后的栈变化。

需要注意的是,第一直觉经常是错的。比如某个 opcode 看起来像 XOR(因为在地址计算中使用),但实际验证后发现是 ADD——地址加法和 XOR 在某些特定值下结果恰好相同,容易误判。

这就是为什么需要多组数据交叉验证,而不是看一组数据就下结论。AI 可以批量分析大量栈变化数据对,穷举可能的运算并交叉验证。

循环是 VM 中最关键的控制流结构。通过统计字节码 PC 的向后跳转(backjump),可以精确识别循环的嵌套结构和迭代次数。

迭代次数往往直接暗示密码学参数——比如某个三层嵌套循环的迭代次数恰好是 3 × 9 × 4 = 108,每次迭代做 16 次 GF 乘法,总计 1728 次。这就强烈暗示 AES-128 的 3 个块、9 轮(加上最终轮)、4 列 MixColumns。

通过 hook,在 VM 执行前 dump 两类数据:

有了 opcode 语义表,编写反汇编器是机械工作——AI 可以直接生成。关键是要处理好变长指令编码和操作数的解析。

反汇编后可以看到字节码的宏观结构:函数边界、循环骨架、查表模式、外部调用分布。这是后续白盒分析的起点。

黑盒分析(只看输入输出)能解决一部分问题,但对于复杂的密码学算法,必须深入内部结构才能还原。

白盒分析的核心是:在字节码和 trace 中识别已知的密码学原语

常见的可识别特征:

AI 熟悉标准密码学原语的特征,能从 trace 中的常量快速定位相关构件。

白盒密码的核心手法是把密钥融合进查找表。标准 AES 的 SBOX[x ^ key] 变成一张预计算表,不再有显式的密钥。

还原方法——碰撞验证:如果两张表来自同一个 S-box,那么存在一个常量 delta 使得 T1[x] == T2[x ^ delta] 对所有 x 成立。delta 就是轮密钥的差值。

通过这种方法,可以从大量查表中还原出全部轮密钥,无需逆向密钥调度算法。

白盒分析的核心工作模式:

AI 的价值在于加速这个循环——它可以同时生成多个候选假说,并为每个假说编写独立的验证脚本。一轮下来几分钟,人工可能要一整天。

白盒分析依赖能读懂字节码。但有些情况下:

这时切换到黑盒视角:只观察输入输出关系,不关心内部实现。

核心思想:固定其他输入,只改变一个字节/nibble/dword,观察输出变化。

分层策略:

把所有探针结果汇总,可以直接推断算法结构:

对于未知的子函数,可以枚举候选算法(如 CRC32、XOR fold、S-box 变换等),用多组探针数据逐一验证。只有全部命中的候选才是正确的。

AI 生成候选假说列表、编写验证脚本、分析结果——这种"批量穷举 + 自动验证"的工作模式,是 AI 最高效的应用场景之一。

VMP 中通常有随机数(时间种子、/dev/urandom 等)参与。要做端到端验证,必须先 hook 这些熵源返回固定值。

固定熵源后,相同输入必须产生完全相同的输出。任何不一致都说明还原有误。

将全部算法用纯 Python 实现,完全独立于动态调试框架。对比 Python 输出和框架输出:

VMP 逆向的复杂度决定了中间过程一定会有错误假设。常见的错误模式:

核心原则:只信事实,不信结论。 每个结论都需要独立的实验数据支撑。文档中明确标注"已验证"和"待验证"。

VMP 逆向通常是一个多周的持续过程。AI 没有跨 session 记忆,人类的记忆同样不可靠。如果不做文档记录:

每份文档中区分三类信息:

AI 可以自动维护这些文档——每次新发现自动归类更新。

让 AI 生成验证代码,而非直接要结论——"帮我验证这组数据是否符合 AES-128 CBC" 远好于 "这个算法是什么"

小步迭代——每次只攻克一个子问题,验证通过后再推进。不要让 AI 一次性还原整个算法

人机分工明确——人类决定方向、设计实验、判断关键节点;AI 执行数据处理、编写脚本、维护文档

文档即记忆——所有发现写入文档,AI 每次 session 开始时加载文档继续工作

CLAUDE.md 中写清楚分析原则,比如"不得运行生成的脚本,由用户自行运行"、"遇到大函数立刻停下"等。这些规则会在每次对话中自动加载。

SO 去混淆 → IDA 导出 → 动态调试环境 + Trace → Opcode 分析 → 字节码提取
→ 黑盒/白盒分析 → 文档记录 → 参数还原 → 端到端验证
SO 去混淆 → IDA 导出 → 动态调试环境 + Trace → Opcode 分析 → 字节码提取
→ 黑盒/白盒分析 → 文档记录 → 参数还原 → 端到端验证
框架 定位 特点
Unicorn 纯 CPU 模拟 需要自己处理所有系统调用、内存映射
unidbg Android/iOS 模拟 内置 JNI 环境、系统调用、文件系统模拟
Qiling 通用系统模拟 支持多平台,介于两者之间
Frida 真机注入 需要真机/模拟器,但能拿到最真实的运行环境
执行前栈: [..., 0x02, 0x05]
执行后栈: [..., 0x14]
5 << 2 = 0x14 → LSL(左移)
执行前栈: [..., 0x02, 0x05]
执行后栈: [..., 0x14]
5 << 2 = 0x14 → LSL(左移)
def find_key_delta(table1, table2):
    for delta in range(256):
        if all(table1[x] == table2[x ^ delta] for x in range(256)):
            return delta
    return None
def find_key_delta(table1, table2):
    for delta in range(256):
        if all(table1[x] == table2[x ^ delta] for x in range(256)):
            return delta
    return None
观察数据 → 提出假说 → 编写验证脚本 → 运行验证
    ↑                                      |
    └── 假说失败则修正 ←──────────────────┘
观察数据 → 提出假说 → 编写验证脚本 → 运行验证
    ↑                                      |
    └── 假说失败则修正 ←──────────────────┘
错误类型 典型例子 发现方法
混淆因果 把动态参数当成固定常量 多次运行对比,发现每次不同
过度泛化 观察到一种映射关系就认为是全局规则 更多数据集验证时不匹配
误判结构 把 N 轮独立密码误认为 M×K 轮 CBC 对中间块做交叉验证失败
AI 幻觉 AI 自信地推断了一个不存在的模式 编写验证脚本后立即证伪
docs/
├── ANALYSIS.md          # 总体进度:什么已完成、什么待做
├── <module-A>.md        # 模块级分析文档
├── <module-B>.md
└── archive/             # 过程文档(含已证伪的假说,保留备查)
docs/
├── ANALYSIS.md          # 总体进度:什么已完成、什么待做
├── <module-A>.md        # 模块级分析文档
├── <module-B>.md
└── archive/             # 过程文档(含已证伪的假说,保留备查)
项目根目录/
├── CLAUDE.md            # 项目级指令(工作流规则、分析原则、禁止事项)
├── docs/                # 分析文档(AI 的"长期记忆")
├── IDA 导出/            # 结构化伪代码(AI 的"眼睛")
├── trace/               # 运行时数据(AI 的"原始素材")
└── scripts/             # 验证脚本(AI 的"实验室")
项目根目录/
├── CLAUDE.md            # 项目级指令(工作流规则、分析原则、禁止事项)
├── docs/                # 分析文档(AI 的"长期记忆")
├── IDA 导出/            # 结构化伪代码(AI 的"眼睛")
├── trace/               # 运行时数据(AI 的"原始素材")
└── scripts/             # 验证脚本(AI 的"实验室")
def infer_opcode(pre_stack, post_stack):
    """从栈变化推断二元运算语义"""
    if len(pre_stack) >= 2 and len(post_stack) == len(pre_stack) - 1:
        a, b = pre_stack[-2], pre_stack[-1]
        r = post_stack[-1]
        ops = {
            'ADD': (a + b) & 0xFFFFFFFF,
            'SUB': (a - b) & 0xFFFFFFFF,
            'XOR': a ^ b,
            'AND': a & b,
            'OR':  a | b,
            'MUL': (a * b) & 0xFFFFFFFF,
            'LSL': (a << (b & 31)) & 0xFFFFFFFF,
            'LSR': (a >> (b & 31)) & 0xFFFFFFFF,
        }
        return [name for name, val in ops.items() if val == r]
    return []
def infer_opcode(pre_stack, post_stack):
    """从栈变化推断二元运算语义"""
    if len(pre_stack) >= 2 and len(post_stack) == len(pre_stack) - 1:
        a, b = pre_stack[-2], pre_stack[-1]
        r = post_stack[-1]
        ops = {
            'ADD': (a + b) & 0xFFFFFFFF,
            'SUB': (a - b) & 0xFFFFFFFF,
            'XOR': a ^ b,
            'AND': a & b,
            'OR':  a | b,
            'MUL': (a * b) & 0xFFFFFFFF,
            'LSL': (a << (b & 31)) & 0xFFFFFFFF,
            'LSR': (a >> (b & 31)) & 0xFFFFFFFF,
        }
        return [name for name, val in ops.items() if val == r]
    return []

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

最后于 10小时前 被执着的猫编辑 ,原因:
收藏
免费 45
支持
分享
最新回复 (10)
雪    币: 212
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
6
10小时前
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
10小时前
0
雪    币: 0
活跃值: (3916)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4

好文,正缺这方面知识

最后于 10小时前 被doduhuang编辑 ,原因:
10小时前
0
雪    币: 2254
活跃值: (6779)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
5
分析验证自闭环
10小时前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
66666
9小时前
0
雪    币: 260
活跃值: (455)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
111
8小时前
0
雪    币: 260
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
11
8小时前
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
感谢分享
6小时前
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
6
2小时前
0
雪    币: 221
活跃值: (612)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
感谢分享
2小时前
0
游客
登录 | 注册 方可回帖
返回