工具已开源(地址见文末),支持 IDA 8.4+,本文基于 IDA 9.2 + ds v4 pro 实测(欸平坦化的例子太好找了, 不过样本很单一找机会多测几个而且这个算是半成品吧, 有疑问评论区友好交流谢谢)。
提醒: 压缩汇编不是为了取代伪c的, 而是尽可能减少输入和推理所用的token
大模型辅助逆向现在是很多人的日常,但真正把反汇编喂给模型时,账单会很诚实:
问题本质不是模型读不懂汇编,而是反汇编文本的信息密度太低。
mov w8, #0x1 / str w8, [sp, #0x1c] 这种指令,LLM不需要 30 个字符来理解。
于是有了这套东西:IDA-MCP(把 IDA 的分析能力暴露成 MCP 工具)+ SEMZ(一套为 LLM
设计的反汇编语义压缩格式,把反汇编压到原文的 1/3 甚至更低再喂给模型)。
架构一句话:每个 IDA 实例内跑一个 FastMCP HTTP server,外面再架一个独立网关,
把多个 IDA 实例聚合成一个稳定的 MCP 端点。(不是啥好设计懒得折腾了就这样吧反正能用)
几个工程要点:
(这里的话其实设计成那种get tool的会更好)
这是本文的主角。核心目录 ida_mcp/compress/ 是纯 Python、零 IDA 依赖的压缩管线,
parse → normalize → cfg → liveness → 五层压缩 → dsl,可以脱离 IDA 单测。
外加保守死代码消除(基于 CFG 可达性的 liveness 分析,删除计数写入头部 e=n,不偷偷丢东西)。
safe(仅字典)→ full(+DCE)→ max(+nop 消除、cmp+jcc 融合)→ ultra(默认)。
ultra 的关键决策是全局固定表:助记符/寄存器映射不再随函数生成(省掉 #MN/#RG 头部开销),
所有函数共享同一套编码——m=mov、l=ldr、f=cmp、j=b……模型只需要学一遍。
配套措施:
一个 flatten 混淆样本(122 指令 ARM64),压缩后全文:
头部 #SZ1 自带统计:原始 122 指令、压缩后 92 行、删 1 条死代码、字符比 18.7%。
分发器的 cascade 结构(L1~L5 连续 geq 比较)在压缩文本里一目了然——这正是它被设计出来的目的。
压缩是有损呈现(地址被标签替换),工程上必须留好"还原通道":
工程验证:压缩核心 95 项纯 Python 单测(语料覆盖序言/分支/循环、VM handler 宏、死代码、
x64dbg trace),另有需活 IDA 的集成测试套件对照基线样本跑回归。
大输出的三种活法:line_offset/line_limit 按行分页、out_file 落盘(绕过 120KB 内联上限)、
expand_macros=true 把 #P 宏全部内联展开(更长但零心算,模型读不动宏时的逃生口)。
这套压缩格式最初就是为"LLM 做控制流去平坦化"设计的。我们用它对某商业 .so 中的
一个 OLLVM 混淆函数完成了半自动 deflat(需要手动认证所以是半自动),全程数据如下:
样本规模 (绝对真实)
还原结果
整个流程由 agent 框架编排(recon 分发器分析 → 五不变量校验门 → 分片块分析 →
串联伪 C → 出边机械解析 → patch),压缩文本是唯一喂给模型的汇编形态——
没有 SEMZ 的密度,这种规模的多轮分析在上下文窗口里根本转不开。
框架的具体设计(多 cascade 建模、过渡块、校验门、resolve_edges 解析器)值得单独写一篇。还能再水一篇
LLM 逆向的瓶颈正在从"模型能力"转向"喂料效率"。SEMZ 的思路很朴素:汇编文本里
60~80% 的字符对理解语义没有贡献,那就别喂了。配合 IDA-MCP 的工具链,模型可以把
有限的上下文花在真正的分析上。
项目结构(欢迎更正):
开源地址:1e1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6o6j5i4c8y4k6h3!0%4i4K6u0V1x3U0x3K6i4K6u0r3d9f1c8m8i4K6u0V1e0f1y4b7i4K6u0r3
readme已著编写方法

| 层 |
内容 |
效果 |
| L1 |
助记符字典 #MN(mov→m, ldr→l, str→s, cmp→f...) |
指令名 1~2 字符 |
| L2 |
寄存器字典 #RG(x0..x25→a..z, sp→S, x29→D) |
操作数大幅缩短 |
| L3 |
常量池 #K:重复 ≥2 的立即数/地址/符号池化 |
重复常量一次定义 |
| L4 |
序列宏 #P:挖掘重复 ≥2 的指令序列(3~8 长度 n-gram) |
重复骨架一次定义 |
| L5 |
基本块标签化:CFG 重建,跳转目标全部渲染为 Ln |
控制流可读 |
| 样本 |
原始 |
压缩后 |
字符比 |
| flatten 教学样本(ARM64,122 指令) |
122 insn / 全量 disasm |
92 行 |
18.7% |
| OLLVM 混淆真实函数 sub_1C79D4(359 指令/111 基本块) |
359 insn |
356 行 |
45.5% |
| 工具 |
用途 |
compress_function(query, level, with_map, ...) |
按地址/符号压缩整个函数(默认 ultra) |
compress_range(start, end, ...) |
压缩任意地址范围(线性反汇编) |
compress_text(asm_text, ...) |
纯文本压缩,不触 IDA——IDA 导出文本、x64dbg trace 都能压 |
extract_block(query, labels) |
按 Ln 标签切出指定基本块(+头部),分片神器 |
| 指标 |
数值 |
| 函数 |
sub_1C79D4(ARM64) |
| 指令数 / 基本块数 |
359 / 111(压缩后 104 个标签) |
| 压缩文本 |
356 行,字符比 45.5% |
| 分发器结构 |
双 cascade(二叉搜索树)+ hub 重入点 + 块内嵌 mini-cascade |
| case 常量 |
9 个随机 32 位值(有符号比较) |
| 过渡块 |
5 个(真实块与分发器之间的"写状态+跳转"链路) |
| 指标 |
数值 |
| 真实块出边 |
15 条 |
| 静态解析成功 |
13 条(含 2 条 csel 条件二选一边) |
| 动态不可解析 |
2 条(写值运行时决定,属静态还原理论上限) |
| 出边还原率 |
86.7% |
| 最终产物 |
完全解密伪 C:无 state 变量、无 goto、无分发器残留,原始控制流完整重建 |
LLM 客户端 ──► 网关 127.0.0.1:11338/mcp ──► IDA 实例
──► IDA 实例
──► ...
L0:
m wi,K0
s wi,[S+K1]
j L1
L1:
l wi,[S+K1]
geq L7: f wi,K0
L2:
geq L10: f wi,2
...
IDA-MCP/
├── instructor/LLM_INSTRUCTOR.md
├── ida_mcp.py
├── ida_mcp/
│ └── compress/
├── install.py
├── test/
└── API.md
工具已开源(地址见文末),支持 IDA 8.4+,本文基于 IDA 9.2 + ds v4 pro 实测(欸平坦化的例子太好找了, 不过样本很单一找机会多测几个而且这个算是半成品吧, 有疑问评论区友好交流谢谢)。
提醒: 压缩汇编不是为了取代伪c的, 而是尽可能减少输入和推理所用的token
[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。