☰
用AI Agent重塑逆向工程:从动态分析到二进制还原的自动化实践
2026/10/13 11:25:18 网站建设 项目流程

1. 逆向工程还没被 Agent 替代:它真正擅长的是整合与调度

1.1 传统手工逆向的时间花在哪

我不知道别人怎么开始的,反正我第一次听说把 AI Agent 用到逆向工程上时,脑子里第一反应是:这不就是拿 LLM 把老逆向师傅的活儿再干一遍吗?可等我真正拆开看,才发现传统逆向分析里最耗时间的部分,根本不在“看懂代码”这一步,而在“反复切换工具和上下文”这一步。

一个稍微有点规模的 App 做下来,你至少要经历这么一圈:先把应用跑起来,用动态插桩工具看它调了什么函数;再开抓包工具看网络请求和返回;然后解包静态资源,读配置清单;接着反编译代码,从几千个方法里找到关键路径;最后再回到动态环境,验证你猜的逻辑对不对。每一步之间都要手动搬运数据:抓到的类名要去反编译结果里搜,反编译看不懂的函数要到调用栈里对照,调用栈里的符号名又要在资源文件里找线索。这中间只要换一次工具,脑内缓存基本就断了。

我试过用脚本把这些步骤写成固定的批处理,问题是逆向分析不是流水线,不同目标的入口、逻辑、保护方式都不一样,固定脚本一遇到例外就废了。这时候你需要的不是一个“自动执行预设命令”的脚本,而是一个能根据上一步结果决定下一步动作的智能调度系统。REA 就是奔着这个缺口去的。

1.2 Agent 切入的准确位置:指挥工具,而不是发明工具

REA 的全称是 Reverse Engineering Agent,核心思路只有一个:把逆向工程里重复、机械、吃上下文的那部分工作交给 LLM Agent 去干,人只负责判断方向和审核结果。

它不负责把一个二进制文件变成奇迹,它负责拿 LLM 的推理能力去指挥一堆已经存在的工具,比如运行时插桩、反汇编器、字符串搜索、调用链提取,让这些工具按顺序执行,并根据上一步的结果决定下一步做什么。你可以把它看成一位现场协调员:动态插桩工具是它的听诊器,反编译器是它的显微镜,解包工具是它的开箱刀,而它自己负责看检查结果,决定下一步拆哪里。

我跑过的 Agent 循环基本长这样:

def run_rea_analysis(target): plan = llm.plan("对目标 App 做行为与二进制分析,目标:找到核心输入处理函数") context = [] for step in plan: result = tool_executor(step, target) context.append(result) if result.status == "error": fix = llm.suggest_fix(step, result) context.append(tool_executor(fix, target)) return llm.write_report(context)

这段伪代码说明了一个关键区别:REA 不是一个“自动破解器”,而是一个持续调度的 Agent。它和人做分析时的思路一样,也是先猜、再验证、猜错了就换方向,只是它不需要休息,可以在几分钟内完成几十轮工具调用。

我实际用下来,效率提升最大的一段并不是“读伪代码”这个环节,而是“发现下一步该查什么”这个环节。过去这一步完全依赖个人经验,现在可以交给 Agent 的推理能力。虽然它偶尔会跑偏,但只要你设定好检查点,它跑偏的代价远低于手工遗漏。

1.3 合规边界先讲清楚:这套流程只用在授权目标上

在我把具体方法摆出来之前,必须先说一句非常重要的话:REA 这套流程,只能用在你有权分析的对象上。什么叫有权?要么是你自己开发的 App,要么是公司或客户明确委托你做的安全测试,要么是开源项目、CTF 题目这类本身允许被研究的代码。

标题里的“破解”我理解为把黑盒还原成白盒的过程,不是绕过授权机制去白嫖功能。逆向工程本身是安全研究、漏洞分析、知识学习的正常手段,但拿它去分析任何你无权接触的 App,结论就是越界。

后面所有操作,我都会默认你已经站在合法授权的前提下。简单说,技术本身是中性的,用在哪、为谁用,这个判断比一百个 Hook 脚本都重要。把合规意识刻进流程里,REA 才是一款对自己负责的工具。

2. 动态行为分析:让 Agent 先用日志画出 App 的执行地图

2.1 动态分析为什么比“翻开代码”更适合第一步

做逆向最怕的事情,是闭着眼睛进到一个完全陌生的代码仓库里,然后试图用跳蚤跳高的方式去理解整个系统。行为分析的意义在于,它能在你打开反编译工具之前,先建立起一层“预期”。你不需要知道一个按钮背后的函数具体长什么样,你只需要知道用户点击按钮之后,应用会发起一个网络请求、把加密过的内容写到本地文件、然后刷新界面。有了这层动态预期,你再去静态代码里找“那个加密函数”时,搜索范围就小了很多。

REA 的第一层能力就是做自动化的行为采集。它会在目标 App 启动时注入观察代码,记录被调用的类和方法名、关键函数的入参和返回值、操作后产生的外部特征,比如新建了文件、发起了网络请求。这和很多测试工程师跑自动化测试的思路很像,但区别是 REA 不会按预设用例跑,它会根据你给的“分析目标”自己决定要触发哪些行为。

比如目标是“找到登录逻辑”,它就会优先去观察所有和认证相关的函数;目标是“找到某个字符串的生成算法”,它就会优先 hook 所有接收字符串参数的方法。行为层收集到的信息质量,直接决定后面静态分析的准确率,所以这一步值得花时间做扎实。

2.2 让 Agent 自动写 Hook 脚本,而不是手动猜方法名

动态行为采集最常用的手段是运行时插桩,核心能力是给正在运行的代码“打补丁”,让函数调用时额外打印参数和返回值。常见方案对比可以整理成一张表:

方式适用对象上手难度关键优势
进程运行时注入原生/混合应用中不需要重启进程,适合长期观察
启动时注入需要 hook 初始化逻辑时中不会错过早期函数调用
轻量日志 hook快速验证低改造成本小,只记录关键点

REA 在这个环节做的事,就是把“我想观察方法 X 的参数和返回值”翻译成一段可执行的 hook 脚本。以 JavaScript 插桩为例,Agent 生成出来的脚本风格是这样的:

Java.perform(function () { var target = Java.use("com.example.securenote.core.CipherBox"); target.encryptText.implementation = function (input) { console.log("[hook] encryptText called, input=" + input); var result = this.encryptText(input); console.log("[hook] encryptText result=" + result); return result; }; });

看起来简单,但这里有一个 Agent 和人一样必须面对的问题:你怎么知道有encryptText这个方法?所以 Agent 不会凭空写脚本,它会先用反射接口把目标类里的所有方法列出来,看名字、参数列表,再挑和任务相关的对象去下钩子。这其实是个很好的思路:让 Agent 先做“侦察”再决定“打哪”,而不是一上来就乱猜函数名。

2.3 日志和对象状态要横向对照,才能还原因果链

单看函数调用日志,往往只能得到“发生了什么”的表面叙事。比如你看到encryptText被调用,输入是明文,输出是一串看起来像密文的内容,但这还不能证明它就是加密函数,因为也许它只是做了 Base64 编码。要判断真实逻辑,REA 会把行为日志和对象状态变化放到一起分析:调用前后内存里某个字符串有没有变、调用后有没有新建文件、文件内容是不是和返回值一致。这些关联信息是纯日志抓不到的。

我常用的做法是让 Agent 在行为采集的同时,定期 dump 关键对象的属性值,或者监控应用沙盒目录里的文件变化。等行为跑完,Agent 会生成一张“操作-结果对照表”。比如一次典型会话可能产出这样的结论:CipherBox.encryptText()接收明文 A,返回密文 B,同时把 B 写入了securenote.db的字段cipher。

这个信息链非常关键,因为到了静态分析阶段,你只需要搜索“对同一个数据库字段赋值”的代码,就能反向定位到调用点,效率提高很多。我一直觉得,动态分析做得好不好,不取决于你收集了多少条日志,而取决于你能不能把日志还原成一个有因果关系的“故事”。REA 的价值就是它会把零散日志整理成故事,再把这个故事作为静态分析的地图。

3. 静态资源与结构盘点:动手还原前先摸清目标的骨架

3.1 拆包和资源提取:先把零件摆上桌

动态分析有了初步结论,接下来要去静态侧找证据。这一步做的第一件事是拆包。无论目标 App 是什么平台,整个安装包其实就是一个压缩文件,里面装着可执行代码、资源配置、清单和元数据。拆包的意义不是直接读二进制,而是先弄清楚“这个 App 有哪些组成部分、入口在哪里、权限边界是什么”。

对常见的移动应用包,我一般分成两个层级处理:第一层是直接用解压工具把所有文件倒出来,第二层是解析清单/配置元数据,用专门工具对代码进行反汇编和资源解码。REA 会把整个拆包过程当作一次“资料盘点任务”:它会自动解压资源,用工具还原可读配置,再把解析结果按模块分类。

例如清单文件里通常会声明这个应用包含哪些组件、启用了哪些系统权限、对外暴露哪些服务。即使没有细节代码,这些信息已经能帮你建立行为假设:一个需要定位权限的地图类 App,和一个需要读取通讯录的社交类 App,分析的侧重点完全不同。Agent 在这一阶段还会做一件事,就是提取所有硬编码字符串和资源引用。这些字符串往往藏着 API 地址、加密算法名、错误提示等线索。目标里面只要出现过 “AES” 这个词,后面分析基本上就有一个明确方向了。

3.2 入口清单决定从哪个类开始读

拿移动 App 举例,无论平台怎么抽象,每次都必然有一个“启动入口”和一个“界面加载逻辑”。 静态阶段要做的,不是在几万个类里大海捞针,而是先在配置清单里找到启动入口组件,然后把它当作分析的第一站。

启动入口通常对应一个界面控制器,它的生命周期方法里会做界面初始化、读取参数,甚至调用核心业务逻辑。把这些生命周期方法打开,等于打开了一扇门:入口接什么数据、跳转到哪个界面、初始化时创建了哪些服务,这些信息会形成一条主链路。

REA 的做法是让 Agent 在解析完清单之后,顺着入口类往下走:先看它的初始化方法,收集它 new 了哪些对象;再看这些对象各自的类定义,找出它们都暴露了哪些方法;最后比较这些方法名和行为日志里的函数调用记录,找出重合的部分。重合的这部分,就是你接下来真正要盯住的“核心逻辑区”。

很多新手会卡在“我要看所有代码”这个误区里,而 Agent 的优势恰恰是没有耐心,它只会按“跟任务相关的链路”走。你给它的目标越明确,它推荐的入口就越准;如果目标含糊,它就会退化成一台只会罗列类名的“名词解释器”。

3.3 调用关系树:动静交叉把地图画干净

静态分析最难熬的时候不是看单个函数,而是看函数之间怎么调用。你可以用反编译工具打开某个方法,但一个方法里可能调了二三十个其他方法,而这些方法分布在完全不同的类里。人工跟踪这种调用链非常容易迷路:你需要来回搜索方法名,记录谁是调用方谁是外部依赖,还要随时注意循环引用。

REA 在处理这件事时,会让 Agent 把反编译工具导出的调用图接口自动跑一遍,把目标方法的所有引用位置抓出来生成一张调用关系树,再结合动态日志里记录过的调用顺序,给这棵树标注“哪些路径是实际发生的,哪些只是理论可达”。这里的价值特别明显:理论调用图可以很大,但实际执行路径往往只有两三条。Agent 把两套数据叠在一起,等于把一张杂乱的地铁图简化成了一条你真正要坐的线路。

我遇到过不少情况,静态上看到一个方法有三十多处引用,看起来特别复杂,但动态日志一对照,只有两个调用点真的出现过。这时候就知道该把注意力集中在哪了。这种“动静交叉验证”的思路贯穿 REA 的整个流程,也是让它和单纯的反编译工具拉开差距的原因。

4. 二进制级还原:从行为特征反推核心函数,破解不是绕过

4.1 反汇编和反编译是两个层级,别混为一谈

静态分析走到后段,你会遇到一个分水岭:有些目标 App 的代码逻辑并没有被完整还原成高级语言,而是以汇编指令或者中间表示的形式躺在二进制里。这种情况常见于用非托管语言编写的核心模块、商业加固模块或者跨平台编译产物。这时候需要的工具链就变成反汇编器和反编译器。

反汇编器负责把机器码转成汇编指令,告诉你 CPU 执行了什么;反编译器负责再进一步把汇编还原成接近高级语言的伪代码,告诉你“人的意图大概是什么”。REA 在这里的角色有点像“代码讲解员”:它拿到反汇编结果后,会先让 Agent 做一轮符号扫描,把常见库函数、常量字符串、导入表都标记出来,再把这些标注喂给 LLM,让它输出一段带注释的伪代码。

我常用的交叉验证方式是:先在反汇编层面看数据引用,比如某个函数访问了一个特殊的内存字面量,那么大概率这个函数和某个关键常量相关;然后到高级伪代码层面,把这段逻辑和动态行为日志里的输入输出做对比。两道证据对上了,这个函数的功能基本上就可以定案了。

4.2 把长函数拆小块,让 Agent 当反编译代码讲解员

反编译工具生成伪代码的能力已经很强了,但伪代码依然是反编译器的规则输出:变量名可能是一堆形如local_78的占位符,类型信息可能是粗略推断,控制流里还可能有反编译器自己的伪宏。人肉看这些代码,很容易被拗口的表述带偏。

Agent 使用的策略是“分块讲解”:不把一个巨大的函数整体扔给 LLM,而是把函数拆成 5 到 10 行的小块,每块让 Agent 回答四个问题:这段在做什么、输入来自哪里、输出流向哪里、有没有和已知加密库/字符串相关的特征。等所有小块都分析完,再让 Agent 合并成整体逻辑。

这个技巧看着笨拙,但在上下文受限的现实环境里非常有效。大部分模型一次能处理的信息量有限,小步快跑反而比一口气吞下整个函数更稳定。另外一个值得记录的细节是,Agent 在讲解代码时往往会产生“幻觉性命名”:明明只是做了个字节交换,却因为里面出现了一个看起来像 key 的变量,就猜测这是加密算法。所以我在流程里专门设计了一步“反讲解校验”,要求 Agent 的输出必须能对应到实际指令中的具体参数和操作。宁可让输出啰嗦一点,也不要因为它猜得顺滑就盲信。

4.3 行为特征反查关键函数,比从头读代码更快

二进制逆向最实用的经验之一,就是“从结果倒推源头”。如果你在动态阶段已经观察到某个核心函数的输入输出特征,比如收到固定长度字符串、返回一串特定密文,那你到二进制里找它的方法不是从头开始读代码,而是直接搜索这个输入输出特征在代码中留下的痕迹。

这些痕迹包括:字符串常量、特定的位运算模式、内存写入顺序、导入表中被调用的标准函数。Agent 可以自动把这些特征组合成一个搜索式,在反编译结果里做全量匹配,然后把匹配到的所有位置汇总。这种方法对加密逻辑特别有效:很多开发者在应用里自己实现了加解密流程,哪怕他们最终调用了标准库函数,在数据变换前后也一定会出现典型的特征操作,比如异或循环、查表、位移。Agent 会把这些操作和动态日志里的函数调用点做关联,覆盖掉“从入口一层层追”的低效路径。

这里想再强调一次:二进制级“破解”的核心关键词是“还原”,整个过程的目标是理解目标软件的行为与逻辑,而不是破坏、绕过或滥用。分析过程中生成的任何伪代码、调用链和报告,都只应服务于授权范围内的安全研究、教学或开发调试。

5. REA 实操一条龙:五步从零还原一个模拟目标的完整过程

5.1 先造一个合规的模拟目标

为了演示不越界,我准备了一个我自己写的模拟目标:一个叫com.example.securenote的简单笔记应用,把用户输入的标题做一轮字符串变换后写入本地文件,同时在界面显示变换后的结果。这个 Demo 没有真实的云端接口,也没有第三方 SDK,做它的目的就是让整套流程在自己可控的范围内跑通。

你得知道,实操练习最忌讳一上来就拿真实生产环境试手。我建议每个人都先搭这样一个“靶场式”目标,把 Agent 的每个步骤跑出信心后,再到有授权的商业项目里使用。搭建目标的过程很简单:一个空工程、一个按钮、一个输入框、一个文本显示区域,以及一个名为CipherBox.encryptText()的核心方法。这个模拟项目的代码量很小,但它已经覆盖了 REA 的完整流程:有界面入口、有核心处理函数、有文件写入。实战中你会遇到的功能类型它基本都包含了,唯一的区别只是规模。

5.2 Agent 的五步执行链路,每一步都在验证上一步

我把整个 REA 的实跑过程拆成五个大步骤,Agent 会沿着这个循环执行。

第一步是动态采集:让目标 App 启动,Agent 自动注入监听代码,记录点击按钮到文本变化的完整路径。第二步是特征提取:从日志里找关键类名、方法名、输入输出和文件操作记录。第三步是静态检索:基于动态特征去反编译结果里搜索对应的方法和调用链。第四步是二进制验证:在反编译代码层面确认核心方法的运算细节,并把关键参数和分支标记出来。第五步是回归校验:修改输入,重新跑一遍动态采集,用新的日志验证静态还原的逻辑是否一致。

这五步不需要人为穿插,Agent 会自己决定从一个阶段进入下一个阶段的时机。比如它在第三步没有找到匹配方法,就会回到第一步重新补充行为数据,而不是硬着头皮继续。执行的关键在于“中途不手工接盘”:你只需要启动 Agent、给定目标、指定产物格式,然后观察每一步生成的分析中间结果。如果哪一步的结果和常识冲突,比如日志显示调用了一个静态分析里完全不存在的方法,Agent 会把它标记为异常并尝试重新取证。

我实际跑完这一套流程后,产出通常是两份东西:一份是行为分析报告,另一份是核心逻辑还原说明。前者用于回答“这个 App 做了什么”,后者用于回答“它是怎么做出来的”。

5.3 四段式报告怎么生成才不用返工

机器自动生成的报告,最常见的问题就是“看起来全面但站不住脚”。为了避免这种空壳报告,我在 REA 里给 Agent 指定了四段式结构:第一段写清楚分析目标、授权范围、分析环境;第二段放行为层面的证据,包括关键调用时间线、输入输出样例;第三段放静态和二进制层面的证据,包括方法签名、调用链和伪代码关键片段;第四段给出结论,结论里每一项都必须对应到前面某一项证据。

如果一段结论找不到证据,Agent 必须明确标注“推测”,而不是让它混进事实陈述里。这个结构看起来简单,但能让报告真正经得起同事和客户的追问。我还习惯让 Agent 在报告的末尾加一栏“待验证项”,把所有没走完验证流程的猜测单独列出来。这样即使 Agent 有幻觉,也不会污染已经验证过的结论。

6. 避坑实录与验证方法:让 Agent 的分析结论可回查、不跑偏

6.1 Hook 白写大多是时机问题,不是脚本问题

我最早用 REA 跑动态采集时,遇到最多的问题就是:脚本明明注入成功,日志却一条都没有。后来排查了一圈,发现绝大多数情况是 Hook 时机太晚:目标 App 在我注入之前,已经把要观察的方法执行完了。解决的办法是让进程启动后、在入口代码执行前完成注入,或者给目标方法增加一个延迟等待条件。

在实际操作中,我会让 Agent 在刚开始抓行为时优先看一眼目标方法的声明位置和调用时机,再决定用立即注入还是延迟注入。还有一类问题是线程:界面操作和后台任务运行在不同线程,Hook 打印语句虽然存在,但中途发生了异步回调,日志顺序混乱。对这种情况,我建议在 Agent 生成的脚本里强制添加线程标识和时间戳,后续分析才能把不同线程的事件理清楚。这两条经验都属于“工具不复杂,错在预期不对”的典型坑。

6.2 上下文窗口不够时,用三层压缩法解决

LLM Agent 不是无限内存,处理超大函数时很容易把前面的细节忘掉。我现在的做法是三层压缩:首先用静态分析工具做一次函数精简,去掉外部库调用部分,只保留任务相关路径;其次让 Agent 对每段伪代码生成更精炼的“逻辑摘要”,存成一个可检索的中间文件;最后在最核心的环节,再把摘要拉回上下文,做深度的逻辑推理。

这种分层记忆机制很像人的思维方式:先看大概,再看重点,最后看细节。如果你发现 Agent 在一轮分析里反复重复同一段结论,大概率不是模型变笨了,而是它的记忆里塞了太多不重要的东西。这时候的解法不是买更大的上下文,而是把输入变得更干净。

6.3 遇到混淆和加壳别急着脱壳

我想先说结论:不要一上来就脱壳。先做行为分析,再做资源盘点,再到反汇编里搜特征字符串。如果目标 App 的保护强度并不影响你回答“它做了什么”这个问题,那很多情况下你根本不需要碰更底层的东西。

但如果静态代码确实被混淆成无意义符号名,而你又必须还原核心逻辑,那才需要考虑在授权范围内做脱壳或内存 dump。这类操作在技术上并不难,难的是它要求你对进程的内存布局和 App 的加载机制足够熟悉,而且容易制造大体积内存镜像,导致分析环境卡死。我的建议是先把脱壳当成最后手段,而不是默认选项。另外,凡是涉及加壳目标的报告,一定要把脱壳前后的代码差异写清楚,避免让读者以为你“凭空变出”了源代码。

6.4 三验法验证结论,防止 Agent 编出“合理幻觉”

REA 最大的风险,不是它能力不够,而是它太自信。模型在解释一段绕口的反编译代码时,很容易生成听起来非常合理、但实际上不存在的内容。为了防幻觉,我总结出一个“三验法”:第一验,单步运算验证,拿一个已知输入手动跑一遍还原出来的伪代码,看输出是否和动态日志一致;第二验,输入变异验证,改变参数重新运行,看行为特征是否随还原的逻辑预期变化;第三验,交叉验证,用两种不同工具分别生成伪代码,比对关键段落的一致性。

这三步做完,我才会把一个结论写进报告的事实部分。很多同事觉得这套流程麻烦,但逆向工程本来就是个试错学科,任何不经过验证的“聪明结论”,最后都可能在线上环境里变成事故。宁可慢一点,也要保证每一个结论都能追到证据。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询