ARTICLE · 1197732
REA 拆解:AI 是怎么把一个 App 拆开读懂的
摘要:REA 不是反编译器,是给 AI 装的一层接口。它把 Ghidra、IDA、Hopper 这些引擎接到你的编程 agent 上,让「这个功能到底怎么实现的」变成一句能问出口的话。这篇从反汇编原理讲到 Evidence 机制,把它的技术底细一层层拆开。
上个月我想弄明白一个 Electron 应用把配置存在哪。
按老办法,解开 asar,翻几万行 webpack 产物,再装个 Ghidra 去啃它自带的原生模块。一个下午过去,我只弄清楚了「配置文件叫 config.json」,至于它什么时候写、写之前做了什么校验,没头绪。
后来换了个路子:把目录丢给 agent,让它自己去读、去问、去查。它跑了十几分钟,回来给我一条链:renderer 点了保存 → 一个叫 notes:save 的 IPC 通道 → 主进程写文件。
中间没装任何新软件,因为真正干活的东西早就在我机器上了。缺的只是一层把它和模型接起来的胶水。
那层胶水叫 REA。
先把「逆向」这件事讲明白
不弄清楚这一步,后面全是黑话。
你写的 TypeScript 经过编译,最后变成本地机器能直接执行的指令序列。这个过程中丢掉了三类信息:变量名和函数名、类型、以及代码的结构(哪里是 if,哪里是循环)。
发布版本还会再剥一层,把符号表也去掉。所以拿到一个二进制,你手里只有地址和字节。
逆向干的事,就是从这个状态往回推。标准流程是三步:
第一步切函数。哪些字节属于同一个函数,入口在哪,谁调用了谁。
第二步认指令。MOV EAX, [R13+0x18] 是把内存里某个值搬进寄存器,CMP EAX, 0x5c 是拿它跟 92 比。这一步是确定性的,反汇编器不会猜错指令编码。
第三步猜数据流。为什么要跟 92 比?比完之后跳去干嘛?这一步开始有不确定性,因为中间值叫什么、代表什么,编译器没留下任何提示。
反汇编器的核心算法有两种。线性扫描是从头到尾一段段解码,简单但会把数据段误认成指令。递归下降是从入口出发,遇到 call 就进去,遇到 jmp 就跟过去,只走「真实执行可能到达」的路径。后者准确,代价是碰到间接跳转就断链,因为目标地址是运行时才算出来的。这也是混淆器和虚拟化保护能生效的原因:它们专门制造这种断链。
反编译再往上走一层。以 Ghidra 为例,它先把每条机器指令提升成一种叫 P-code 的中间表示,在中间表示上做控制流图、数据流分析和 SSA 化,识别出 if/while/switch 的结构,最后吐出一段 C 伪代码。
伪代码看着像源码,实际上不是。变量是 v1、v2,类型是推出来的,函数边界可能是错的,宏和模板全部展开成了最原始的样子。
所以行业里一直有句话:反编译给你 80% 的骨架,剩下 20% 靠人读。那 20% 恰恰是最费时间的部分。
REA 想接的,就是这 20%。
它不是引擎,是接线员
刚看到 REA 的时候我以为是又一个反编译器,用下来发现它自己不负责把机器码翻译成汇编。
它的定位是中间层,三层结构很清楚:
底层是引擎,Ghidra、IDA Pro、Hopper,负责真正把字节变成汇编和伪代码。
中间是 REA,负责调用引擎、编排流程、把结果整理成一份带证据的报告。
上层是你的 agent,负责推理:这段代码在算什么,跟业务对不对得上。
连接方式是 MCP,Model Context Protocol。说人话就是一套让模型调用外部工具的协议,跑在标准输入输出上,一次请求一次响应。装了 REA 之后,agent 的工具列表里会多出 open_binary、analyze_function、get_call_graph、trace_call_path 这类东西。
对模型来说,它不需要知道 Hopper 的 Python API 长什么样,也不需要拼 Ghidra 的命令行,它只需要决定「现在该看函数清单,还是该看这个函数的调用者」。
这个分工是整件事成立的原因。Ghidra 的伪代码质量早就够了,卡住人的从来不是能力,是操作成本。
一条命令,装了什么
官方给的起步命令是 npx rea-agents@latest setup。
它做的事比看起来多。先探测你机器上装了哪些 agent,Claude Code、Cursor、Codex、Gemini CLI、Windsurf、VS Code 都在支持列表里,然后生成一份变更计划让你确认:要改哪几个配置文件、要写什么内容。确认之后才动手,改之前会备份原文件。
写进去的是两样东西。
一样是 MCP 配置,让 agent 知道有这么个服务可以调,并且把包版本钉住,避免下次跑出不一样的结果。
另一样是一个 investigation skill,一份给 agent 看的工作流说明书:先看清单再定位函数,先读伪代码再核对指令,跨引擎的结论要交叉验证。这东西容易被忽略,但没有它,模型拿到一堆工具也只会乱点,像给新人一整套 IDE 却不给规范。
原生二进制分析还得自己准备引擎:Ghidra 免费,但要 JDK 21;Hopper 是商业软件,macOS 上体验最好;IDA 最贵也最全。JS 和 .NET 的静态分析不需要引擎,Node 就够。
Node 版本要求是 22.19 以上、24.11 以上或者 26 以上,这个门槛不算低,但也说明它用了不少新特性。
一次调用,经过哪几层
这一段我想讲细一点,因为它能解释很多「为什么它有时快有时慢」的现象。
入口是一个分发脚本,判断这次是走 MCP 常驻服务,还是走一次性命令行。两条路后面接的是同一套业务逻辑,所以你手动跑 CLI 得到的结果和 agent 拿到的一致,不会出现两套行为。
MCP 服务启动后注册工具、等请求。模型发起一次工具调用,请求进到服务层被翻译成内部指令,交给应用层的工作流。工作流里有一个路由组件,负责给这个目标绑定一个分析引擎。
这里有个设计我觉得挺关键:绑定是不可变的,选定之后整个会话都用它,而且选择过程是确定性的排序匹配,不做 fallback。
意思是不会出现「Ghidra 挂了就悄悄换 Hopper 顶上」这种事。因为两个引擎的反编译结果本来就不一样,混着用会得到看似连贯、实际无法比较的结论。宁可报错,不可污染。
再往下就是各引擎的适配器,每个的处理方式都不同:
Hopper 是个 GUI 程序,REA 会把它启动起来,通过本地 socket 跟一个跑在 Hopper 进程内部的 Python 桥通信。桥脚本用 Hopper 自己的 API 取反汇编、伪代码、交叉引用,等于借 Hopper 的手干活。
Ghidra 走的是无头模式,REA 带了一个 Java 桥脚本,注册成 Ghidra 的 HeadlessScript,做函数清单、反编译、引用和调用图。导入之前它会先把目标复制成一份私有副本,你原来的文件不会被改。
IDA 的情况特殊,它复用了社区已有的 ida-pro-mcp 契约,可以连你手动打开的 GUI,也可以自己起一个无头数据库。
底层还有一层统一管理:所有子进程归一个模块管,独立进程组、私有工作目录、超时、读取上限、退出清理。这层不出现在任何演示里,但它决定了这东西能不能在你自己的电脑上跑一个几 GB 的程序而不把系统拖死。
Evidence:最值钱的设计
结果回来的时候,不是一段自然语言,是一条结构化的 Evidence 记录。
里面有几样东西:normalized_result 是整理后的结果,raw_result 是引擎的原始输出,analysis_profile 是这次分析所用配置的指纹,coverage 声明这次观测覆盖到什么程度,取值为 complete 或 unknown,还有 limitations 和 unknowns 两个字段专门记录没看到的东西。
为什么这个设计重要?
因为大模型最擅长的就是在证据不足时把话说圆。你把 Ghidra 输出粘进对话框,问它「这个函数是不是在做加密」,它会给你一个很像样的答案,而那个答案可能是编的。
Evidence 机制把「不知道」变成一等公民。分析器没看到的东西,必须显式声明 unknown;比对两次分析结果时,只要有一边的 coverage 是 unknown,工具就只允许你得出「无法判定」,不许你说「一致」。
官方文档里甚至规定了:函数证据要按维度声明可用性,伪代码、汇编、调用者、被调用者、引用、字符串、控制流图,每一项都要标是「看到了」还是「没拿到」。某个维度 unavailable,比对结果里那个维度就只能是 unknown。
这套东西啰嗦得有点过分,但它把「可信」从一句口号变成了数据结构。
顺带解决了一个工程问题:大结果不塞进单次响应。超出传输预算的记录留在会话里,agent 拿到一个引用,需要细节时分页去取。所以你不会看到 agent 因为一个函数的伪代码太长而崩掉。
它对不同软件,分别用了什么招
这是最多人关心的部分。REA 覆盖的目标比想象中宽,但每一类背后的技术完全是两套东西,混在一起讲容易晕。
挑几个展开。
APK 本质是个 zip。里面最关键的是 classes.dex,Dalvik 字节码。JADX 的做法是先把 dex 转成 smali 这种可读的汇编,再把 smali 的控制流还原成 Java 代码。资源走 apktool,清单、布局、字符串都在那。如果 App 装在你手机上,adb 直接 pull 下来就行,不用去网上找安装包。
Electron 是另一条路,压根不碰机器码。打包产物 .asar 是个带索引的归档,解开就是 webpack 或 vite 的输出。REA 在这一层做三件事:建模块图,弄清谁 import 谁;读 source map,把压缩后的行列号映射回原始文件,映射表用的是 VLQ 编码;找 IPC 通道,也就是 ipcRenderer.invoke 和 ipcMain.handle 里那一对字符串。新版还能读 Electron fuses,那些编译进二进制的开关位,决定能不能开调试端口。
.NET 是最舒服的目标。PE 文件里有完整的 CLI 元数据,方法体是 IL 而不是机器码,类型名、方法名、参数名全在。读它更像读文档,不像逆向。
网站和浏览器页面属于运行时观测。CDP 是 Chromium 自己开的调试协议,Playwright 在上层封装。挂上去之后能看到已加载的脚本原文、网络请求、以及运行时的一些状态。这部分得到的信息和静态读文件完全不同,因为它反映的是「此刻真的发生了什么」。
固件是另一套思路。binwalk 和 unblob 会扫描文件头签名,找出里面嵌着的压缩包、文件系统镜像,层层切出来再解压。很多路由器和摄像头的固件是这么拆开的。
静态和运行时,得分清楚
这一点必须说透,否则很容易对工具有错误期待。
REA 的主体是静态分析。它读文件,不运行程序。你给它一个 exe,它分析这个 exe 的字节,不会替你点开界面。
所以「这个按钮点下去会发生什么」这类问题,静态分析只能回答一半:它能告诉你代码里有哪些分支、调用了哪些函数,但哪条分支真的会走,取决于运行时状态。
要观测运行时,得你自己先启动一个场景,让它挂上去看。浏览器有 CDP,进程有 PTY 捕获,macOS 上还能用 LLDB 跟踪函数和方法调用。这些是另外一条线,需要显式发起。
第三方做过一次实测,对象是一个真实的 Electron 应用。459 个文件、24.2 MB,52 秒解析完,文件和行号定位 30 项全对。它找到 18 个 IPC 通道,而应用实际有 17 个,多出来那个是误报。但 8 处窗口实例化和 2 个 preload 入口全部漏掉,因为那些行为只在运行时发生。
这个结果挺诚实:静态分析在「读代码」这件事上已经很强,在「还原行为」上仍有明确边界。
有意思的是,REA 自己的输出会告诉你它漏了。coverage 标成 unknown,limitations 里写着哪里没覆盖到。这比给你一个看起来很完整的错误答案要好得多。
法律这条线
工具越强,边界越要说清楚。
REA 不做的事:不还原原始源码,不做加壳脱壳,不绕过 DRM。它读的是没被加密的字节,遇到加密的二进制就没办法。
法律上有两条线要分开。一条是合同,很多商业软件的 EULA 明确禁止逆向,违反它是违约,不是侵权,但依然是麻烦。另一条是版权法里的访问控制条款,比如美国 DMCA 1201,禁止绕过保护措施,哪怕逆向本身是合法的。
而为了互操作性去逆向,在司法实践里长期被当作合理使用。EFF 整理过一份 Coders' Rights FAQ,讲得比大多数法律科普清楚。
落到实操上:拿它学习别人的实现思路、排查第三方 SDK 的行为、维护没有源码的老项目,都是正常用法。拿它把别人的闭源功能抄成自己的产品,那是另一回事。
对做移动和前端的人,它解决什么
我在移动开发里遇到的三类问题,它都能省时间。
接手一个没有源码的老项目。Electron 或 Flutter 打包出来的东西,前一个人走了,仓库里只剩构建产物。以前只能靠行为猜,现在可以问结构。
核查第三方 SDK。有些 SDK 会偷偷读设备信息、偷偷连某个域名。静态看一遍它的代码路径,比看它的隐私政策靠谱。
学一个功能的实现。比如某个编辑器的富文本复制为什么能保留格式,某个游戏的加速度曲线是怎么算的。看清规则,然后用自己的方式重写。
最后这个用法,官方给的例子挺典型。Windows 计算器里 200 + 10% 得 220,而 200 × 10% 得 20。为什么?
REA 把 % 处理函数的汇编取出来,看到两处比较:CMP EAX, 0x5c 和 CMP EAX, 0x5b,都跳向同一个地址。对照微软公开的源码,0x5c 是乘、0x5b 是除。
规则就出来了:乘除时把 10% 当成 0.1;加减时取第一个数的 10%,也就是 200 的 10% 等于 20,再加回去得 220。
注意这里谁做了什么。REA 提供指令、伪代码和引用关系,判断是 agent 做的。工具负责让证据到位,模型负责解释证据。这个分工,是这类工具和「一键脱壳破解」最本质的区别。
想自己试,从这三步开始
第一步,装。Node 版本够的话一条命令:
npx rea-agents@latest setup按提示选你的 agent,看完变更计划再确认,然后重启客户端。重启这步别省,MCP 配置是启动时读的。
第二步,问对问题。有效的提问有两个要素:绝对路径,和一个具体行为。
用 REA 分析 /Users/me/apps/notes,回答一个问题:点「导出」之后,数据是怎么写到磁盘的?给出对应的函数、反编译代码和调用链,标注哪些环节没观测到。路径必须是绝对的,这是 6.0 之后的破坏性变更,相对路径会直接报错。问题必须具体,「帮我分析这个 App」这种问法只会得到一堆泛泛而谈。
第三步,看 coverage。结果里标 unknown 的地方,就是这次没看到的地方。别把没看到当成没有。
最后说句实在的
这类工具刚出来的时候,讨论区里最常见的两个极端:一个说软件行业要完了,一个说这就是个套壳。
都不对。
它没有让逆向变得不需要理解。你仍然要知道什么是函数边界、什么是调用约定、什么是控制流,否则 agent 给你一段伪代码,你也判断不了它说得对不对。
但它确实搬走了一块很重的石头:过去你要在四个工具之间来回切换、导出、粘贴,现在这些动作被压缩成一次提问。
门槛降低不等于知识作废。相反,懂原理的人用它会更快,因为知道该问什么,也知道什么时候该怀疑它。
你觉得最想拆开看看的是哪个 App 的哪个功能?留言说说,我挑一个下次拿 REA 跑一遍。
💡 移动APP开发 | 前端 · 跨端 · AI 应用📱 关注我们,获取更多移动开发与 AI 工程实战内容💬 回复「逆向」加入技术交流群,一起做工具选型与排障❤️ 觉得有用?点个「在看」分享给更多人















































































