前言
本人使用AI逆向和AI开发已经两年时间(目前是在做Agent开发),从纯粹网页时代的GPT辅助逆向到Claude3.5,到后面的AI爆发时代的GLM5.1,Claude4.7等等。看到很多人用ai逆向的思路和手法还是过于局限和原始(纯粹的流式对话)。想分享一些新思路。作者本身是做算法逆向还原,所以文章就以我自己的方向为主进行抛砖引玉。如何高效的逆向。(本文章零AI注水请放心食用,除开绘图使用了AI)
AI逆向过程中难点
在AI逆向中结合经验我认为存在一下几个问题:
- 传统的以古法为主导,Ai辅助的一种工作模式。会导致工作效率不高大量的工作都是古法进行的。比如前期的定位,前期的勘探等等。希望的是AI为主导,人只做决策。这样可以同时进行多个任务。
- 上下文管理太弱。AI逆向经常会出现一个问题就是,人再通过N轮对话后,发现定位或者已经拿到了我们想要东西的(核心算法点位,风控协议的点位等等)。但是在链路上可能会存在一些相关联的信息,才能吧整个工作完成。或者一个对话不够的情况下会有N次压缩。然后发现信息丢失,需要再次回头去看这些辅助信息,这样也很低效。
- AI逆向是发散太强。当我们给了一个指令后,AI通过思维链,工具调用后可能已经偏离了正确的轨道。逆向很多时候会有N个的路径需要去看,去逆向分析。AI走了一条错误的路径后,我们总是要打断纠正,往往大量的时间还是浪费在叮他的思维链中了,并没有真正的解放出来。并且打断后很有可能由于上下文的问题导致这些分析路径都丢失了,中途如果出现意外情况,在启动一个窗口几乎是从头再来。
- Ai逆向不知道正确的逆向手法,虽然认识汇编,但是不懂汇编如何转高级代码。很有可能用了一个低效的方法再去逆向分析。并且逆向过程中极其依赖IDA F5,极易产生错误。尤其是在精度极高的算法还原下很有可能就是0.2%的精度问题,导致无法完成任务。这个问题国产模型最为严重。猜测还是训练集对这类太少了。虽然claude 4.7有改善。
- 爆上下文问题。AI在读取较大函数或者是汇编的时候会产生上下文爆掉的情况,虽然有No mcp这种工具但是由于AI的自身的问题,没法关注1w行汇编。claude的注意力机制一般都是前和最后。
如何解决
这几个问题也是苦恼我很久了。结合总结了一套方法:
- 构建一套上下文管理机制,任何Ai的产出必须要规范管理。
- 必须要有明确的方向,以及要避免模糊的提示词输入。在基模比较好的情况下可以让Ai拆解用户,进行多轮自主的信息了解,从制定方案进行分析定位。
- 多Agent隔离。好处是隔离上下文防止爆掉。避免汇编污染AI的决策。同时可以使用多个模型的进行操作,类似Git上很火的ohmycode这种项目。
- 半自动化的决策,必须要有HOOK来打断ai的,在特定情况下需要让人工进行决策,不然完全野蛮的发散。
框架设计
基于上面思考我设计了一套逆向RE的工程框架,只要参与大型的逆向工程我会使用我这套框架来进行。设计图如下:
设计了一套多Agent的框架,由主脑来进行决策还任务的派发。主Agent直接与用户进行交互,拆解用户的语义分析,并且指挥下面的小弟干活。并且吧上下文管理,逆向分析,审查。拆分成几个Agent完成,如果说后续任务复杂度增加,已经继续增加我们的子agent,继续拆分细化任务。 
上下文管理设计
我吧Ai产物分成两个部分:
- AI分析过程中的还原信息如:类信息,成员信息,函数信息,函数的高精度反汇编,结构体等等。(.artifacts)
- AI分析过程中的逆向路径,思维链都进行存储。防止二次返工。(.investigations)用图解释一下:
有考虑用sqllite或者rag的那种向量数据库管理,但是笔者认为这些方式都太重,而且存在大量的工具调用,sql生成。对于逆向来说不是很好,原因是浪费token,并且如果在国产模型上做这么多复杂会不会出现漂移问题这里需要打个问号了。
具体设计是这样的:
<project>/
├── CLAUDE.md ← init 从模板拷贝(项目自包含)
├── .mcp.json ← 项目级 MCP 注册(IDA / frida / dbg-*)
├── README.md
│
├── .artifacts/ ═══ AI 产物 ═══
│ ├── index.yaml 主索引(地址 → 路径)
│ ├── <binary>/ 每个目标 binary 一个目录
│ │ ├── classes/
│ │ │ └── <Class>/
│ │ │ ├── class.yaml 类元数据
│ │ │ └── methods/
│ │ │ ├── speak.yaml 主文件 = 当前活跃
│ │ │ ├── speak.v1.yaml 历史版本
│ │ │ └── speak.b1.yaml fan-out 分支
│ │ └── functions/
│ │ └── 0x140xxxxx.yaml 未归类函数
│ └── cross_refs/
│ ├── index.yaml 跨引用内部索引
│ └── xref_001.yaml 单条跨引用
│
└── .investigations/ ═══ AI 思考过程 ═══
├── 000-定位-<binary>/ Phase 1 产出
│ ├── 任务.md
│ ├── 发现*.md
│ └── 待挖清单.md
└── 001-<任务名>/ Phase 2 任务
├── 任务.md 任务简报(Phase 0 写)
├── 假设A-*.md 多假设并行
├── 假设B-*.md
└── 结论.md 结案后写
并且设计了如下几种文件格式用来存储我们的逆向中的信息:
schema_version: 1
binaries:
- name: foo.dll
sha256: 8f3a2b1c00000000000000000000000000000000000000000000000000000000
arch: x64
- name: bar.exe
sha256: a12b3c4d00000000000000000000000000000000000000000000000000000000
arch: x64
addresses:
'foo.dll:0x1400077c0':
path: 'foo.dll/classes/Animal/methods/speak.yaml'
kind: method
'foo.dll:0x140007740':
path: 'foo.dll/classes/Animal/methods/get_age.yaml'
kind: method
'foo.dll:0x140007550':
path: 'foo.dll/functions/0x140007550.yaml'
kind: function
'bar.exe:0x140003200':
path: 'bar.exe/functions/0x140003200.yaml'
kind: function
classes:
- id: 'foo.dll:Animal'
path: 'foo.dll/classes/Animal/'
- id: 'foo.dll:Dog'
path: 'foo.dll/classes/Dog/'
由一个树形结构,一个主索引,我们的具体的逆向信息。一共有四类产物:class类文件,方法文件, 未归类函数或者说是面向过程中用到的。
类文件格式:
| 字段 |
含义 |
类名 |
RTTI 真名优先,无 RTTI 用 Class_140xxxx 临时名 |
状态 |
status 值(见第五节) |
版本 |
时间线版本号 |
大小 |
sizeof(类) 字节数 |
vtable |
虚表地址 + 槽位数;无虚函数则 null |
父类 |
直接继承的基类列表,无继承则 [] |
字段 |
成员变量列表(偏移、类型、名字、初值、证据) |
方法清单 |
方法名引用列表(详情在 methods/<name>.yaml) |
方法文件格式:
| 字段 |
含义 |
类 |
所属类名 |
方法名 |
speak / Animal / ~Animal |
地址 |
函数地址(十六进制字符串) |
状态 |
status 值 |
版本 |
时间线版本号 |
签名 |
推断出的 C++ 签名 |
证据 |
判别依据列表 |
C 源码 |
反编译出的 C 代码(多行字符串) |
未归类函数:
| 字段 |
含义 |
地址 |
函数地址 |
状态 |
status 值(一般是 draft 或 candidate) |
版本 |
时间线版本号 |
签名 |
推断出的签名(不确定时填 ???) |
证据 |
判别依据列表 |
C 源码 |
反编译出的 C 代码 |
status 状态机
这里介绍一下status 值,这个实际上就是我们的逆向的置信度,在AI逆向中,任何ai逆向的产出都是不可靠的,都是存在置信度的如果一旦100%信任你肯定会出大问题。所以我们需要给每个结果打上一个置信度,当有足够证据多维度验证后才能是可信的。否则agent在阅读这些信息的都是有一定的怀疑态度去对待。这个状态的更改是由审查agent负责 + 半人工的方式完成的。(这一步主要是解决幻觉问题)
所以设计了如下几个状态:
| 状态 |
含义 |
谁能写 |
draft |
AI 单线程初稿,未经验证 |
worker |
wip |
正在分析中,尚未完成 |
worker |
candidate |
fan-out 候选 / judge 选中待审 |
worker / judge 选 |
confirmed |
已经过用户拍板的最终结论 |
仅用户 |
rejected |
经验证或拍板被驳回 |
judge / 审查 / 用户 |
superseded |
被新版本取代(历史归档常用) |
librarian / agent 升级新版本时 |
常见的流程是这样的:
[worker / fan-out 产出]
状态: candidate
↓
[审查 agent 独立审查]
读:candidate yaml + 关联 .investigations/ 推理
出:同意 / 不同意 / 需补证据
↓
[主 agent 转给用户]
附带审查意见
↓
[用户拍板]
confirmed / rejected / 保持 candidate(要求补证据再审)
最后给我们的不同的agent上不同的权限,但是confirmed一定要有人来审查,也就是意味着这个点完全没有问题。
- worker 自己产出的最高只能是 `draft` 或 `candidate`
- judge 评判 fan-out 后只出意见,不直接改 status
- 审查 agent** 只输出审查意见,不直接改 status
- `confirmed` 必须由用户亲自拍板 —— AI 永远写不到这一档
跨文件关联分析
在上面设计的情况下,我们是一个单一的一对一管理。但是我们知道逆向一般都是多个so或者dll进行的互相引入的,所以这种引入如何解决?用一个cross_refs文件来管理跨dll之间的关系表达:格式如下:
| 字段 |
含义 |
编号 |
形如 xref_001 |
状态 |
status 值 |
版本 |
时间线版本号 |
从 |
引用发起方(binary + 地址) |
到 |
引用目标(binary + 类 + 方法 + 地址) |
关系类型 |
调用 / 引用虚表 / 引用数据 / 字符串引用 / 继承 |
证据 |
判别依据 |
举例一下:
编号: xref_001
状态: confirmed
版本: 1
从:
binary: bar.exe
地址: '0x140003200'
到:
binary: foo.dll
目标类型: 方法
类: Animal
方法名: speak
地址: '0x1400077c0'
关系类型: 调用
证据: 间接调用 [rcx+8],rcx 在 0x14000312A 处加载 Animal::vftable(??_7Animal@@6B@)
版本管理
由于我们不可能一轮对话就可以吧一个任务完成,所以这里存在一个版本迭代问题。当我们逆向的第一版是这样的, 但是由于各种问题导致我们推翻了我们之前的分析过程和逆向过程。但是不确定第二个方向是对的情况下,就需要对版本进行管理。格式如下:
| 命名 |
含义 |
何时产生 |
<name>.yaml |
当前活跃版本(主文件,索引指向这个) |
总是存在 |
<name>.v1.yaml<br><name>.v2.yaml<br><name>.vN.yaml |
时间线历史版本(已被新版本替代) |
升级新版本时,旧主文件改名为 .vN |
<name>.b1.yaml<br><name>.b2.yaml<br><name>.bN.yaml |
fan-out 分支候选(同时刻互斥假设) |
scout 出 N 候选 → 主 agent 派 N 个 worker → 各产出一个 .bN 文件 |
真实的情况是这样的:
classes/Animal/methods/
├── speak.yaml ← 当前主文件(v3 内容,confirmed,从 b3 复制来)
├── speak.v1.yaml ← 第一次分析(status: rejected,把它当成普通函数)
├── speak.v2.yaml ← 第二次分析(status: superseded,识别为 Animal::speak 但还原不准)
├── speak.b1.yaml ← fan-out 候选 1(djb2 假设,status: rejected)
├── speak.b2.yaml ← fan-out 候选 2(strcmp 假设,status: rejected)
└── speak.b3.yaml ← fan-out 候选 3(CRC32 假设,status: superseded,已被 speak.yaml 取代为活跃)
文件迁移机制
通常笔者遇到的都是大型商业软件的逆向,所以遇到面向过程的的极少都是面向对象的设计模型。传统那几种吧。所以逆向到最后一定是一个类方法,虚方法等等。这里面就存在一个 最早期的函数到面向过程的转行问题,比如一个核心算法,他最后肯定是一个类,不是一个函数。在我们的上下文管理中就存在从函数状态的上下文到类对象的迁移过程。设计是这样的:
1. 写新位置 `classes/<X>/methods/<name>.yaml`,加上 `类` 和 `方法名` 两个字段
2. 更新主索引 `addresses` 里对应那条:
- `path` 改为新位置
- `kind` 从 `function` 改为 `method`
3. 更新类的 `class.yaml`**,在 `方法清单` 数组里加上 `<name>`
4. 删除旧位置** `functions/<addr>.yaml`
主要是做文件的迁移吧之前的旧文件删除,迁移到对应的类下面.
AI思维分析上下文管理
这个部分是非常杂的所以不能做那么精细化的区分。这里拿一个真实项目举例:
从任务出发到最后的定位总结分析 整套链路。设计如下:
---
编号: 001
任务: 分析 foo.dll 的 USB HID 协议
任务类型: 协议分析
模式档位: 轻量
状态: 进行中
创建时间: 2026-04-30
更新时间: 2026-04-30
---
knowledge 设计
在说完我们的上下文管理后,讲一下在我自己的任务中存在的高精度还原问题。首先我蒸馏我自己,将所有的最基础的RE和编译原理进行蒸馏成一个知识库。
INDEX是整个知识库的索引目录,用来读取Agent遇到问题的时候所需要的知识。
Skills
上面我说过AI是不懂逆向的,他懂的是F5以及汇编,不明白如何还原一个类或者一个算法。所以这里我整理了两个skills,一个是类分析和lift也就是我说的人肉f5.
这个位置就是我们需要技能,ai不会的,但是人为指点后就可以完成的事情。比如脱壳,去花等等。。。。
lift skills
在高精度的还原,我们人是通过汇编的骨架还原。流程如下:
步骤 1 骨架还原 控制流壳(if/while/for/switch),条件和函数体用占位符
↓
步骤 2 逐行机械翻译 asm → C 伪代码,变量名直接用寄存器名(不识别不优化)
↓
步骤 3 签名草稿 从调用方视角推断调用约定 + 参数个数(待修正)
↓
步骤 4 优化 约定寄存器识别 / 折叠中间寄存器 / 内存操作分类 / lea 处理
↓
步骤 5 语义折叠 识别算法模式 + 改有意义变量名 + 修正签名
这里产物一定是一个cpp文件,也就是对应上面的上下文的产物管理的部分。
Class-identify Skill
由于我工作中大量需要设计类和对象的整个项目的工程化的评估,我需要有一个系统的评估技能。根据我自己的业务制作了一个Skills
工作流(简版):
1. **RTTI 优先** → 有 RTTI 的话整套继承图、类名、虚函数表都能直接拿到
2. **没 RTTI 时用户给起点** → 用户给某个 ctor / thiscall 方法的地址作为分析起点
3. **通过 vtable 列虚方法**
4. **thiscall 偏移聚类列非虚方法**
5. **配合方法体粗还原汇总 `[this+N]` 偏移得成员清单**
输出强制 yaml schema
测试成功
这里我拿我之前网页AI时期的半古法分析的一个的帖子作为测试(Gtuner软件协议分析可以去看我自己的其他文章),看我这套东西是否能跑通。
最后产物:
几乎是没用我多少时间,全程agent就自主完成了,中间打断了我四次(打了一把游戏agent就完成了)
结尾
针对我自己的工作目前就这样设计的,目前还有动态调试的部分没有讲如何融入进来。这里主要是讲解设计,虽然我会把我的工程发到附件里面去,但是不推荐直接使用,可以利用我这种设计打造属于自己的"逆向工程sln"。这里推荐使用opencode,可以把主脑配置到deep seek v4,干活可以使用glm5.1,这样可以使用国产甚至是内网模型来完成工作,不再受制于claude 虽然它目前还是最强的。(感谢您看到这里了,如果觉得有帮助可以点个赞吗?对于有啥意见和感受都可以在评论区讨论)
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 1天前
被BitWarden编辑
,原因: