Skip to content
←返回开源项目

OPEN SOURCE DEEP DIVE

Reverse EngineeringMCPAI Coding

REA:把逆向工程接进编程智能体的一个 MCP 与 CLI

MIT 开源的逆向工程 MCP 服务与 CLI。一条 npx rea-agents setup 就把 Hopper/Ghidra/IDA、pwntools、JADX、Binwalk、CDP 等引擎收进统一的工具契约,注册进 Claude Code、Codex、Cursor 等编程智能体,覆盖原生二进制、JS/Electron、.NET、Android、固件、EVM 字节码与网站共 12 类目标。它的立场是证据优先:观测与推断分开标注,缺失的证据一律报 unknown 而不是 empty 或 false,提供方绑定不可变、失败不静默换引擎,绝不杀自己无法证明拥有的进程。

morluto/rea27kTypeScriptMIT6 min read

一句话定位

REA(Reverse Engineer Anything) 是一个把逆向工程能力接进 coding agent 的 MCP 服务,外加一个同源的命令行工具。一条命令 npx rea-agents setup,它就把自己的 MCP server 和配套的工作流指引注册进 Claude Code、Codex、Cursor、Gemini CLI、Grok Build 等宿主;之后你在 agent 里说一句「弄清 Notes 应用的搜索功能怎么工作,展示证据,然后给我的项目实现一个类似的」,agent 就真的会去反编译那个二进制、把渲染进程的剪贴板调用沿 preload 和 IPC 追到主进程,再把结论连同支撑它的证据一起端回来。仓库目前 26,401 星 / 2,978 fork / 85 watch,MIT 许可,主语言 TypeScript,npm 包名 rea-agents(v6.1.0),官网 rea.tools。

它的副标题写得很准:「看到喜欢的功能,弄清它的工作原理,深入到二进制层面。」 这不是又一个「AI 帮你写代码」的项目,而是「AI 帮你读懂别人已经编译好的东西」。放在 AI Coding 这一类里,它补的是输入侧:模型再强,看不到目标程序的真相,就只能靠猜和编。

REA 在 Hopper 中启动分析桥,检查一个原生二进制文件

数据面

项值
仓库morluto/rea(MIT,TypeScript,MCP 名 io.github.morluto/rea)
Stars / Forks / Watchers26,401 / 2,978 / 85(截至 2026-10-09)
建仓 / 最近推送2026-04-14 / 2026-10-09(release rea-agents 6.1.0 (#1106),五个月做到 2.6 万星)
npm 包rea-agents v6.1.0,双 bin:rea 与 rea-agents 都指向 scripts/rea.mjs
运行时要求Node.js ^22.19.0 || ^24.11.0 || >=26.0.0 + npm
源码规模src/ 下 1,282 个 TypeScript 文件,24 个顶层模块;最大的是 domain/(409)、application/(240)、browser/(107)、server/(69)、contracts/(60)
测试规模650 个 *.test.ts —— 约每两个源文件就有一个测试文件
跨语言桥bridge/ 下 Python(Hopper、mitmproxy、pwntools、pwndbg、LLDB tracer)、Java(Ghidra、JADX)、Swift(原生 UI 子树、进程运行令牌)
文档docs/ 下 40+ 份指南 + 3 份 ADR,README 官方翻译成 15 种语言
支持的 agent 宿主Claude Code、Codex、Cursor、Gemini CLI、Grok Build,以及任何能连本地 MCP server 的客户端

它到底解决什么问题

逆向工程一直是「专家工具 + 专家手感」的领域:Hopper、Ghidra、IDA 各自有一套 GUI 和脚本接口,pwntools、JADX、Binwalk、mitmproxy 各管一段,结果散落在人的脑子里和一次性的脚本里。这套东西对 LLM agent 几乎不可用 —— agent 没有眼睛去点 GUI,也不会自己判断某个反编译输出到底可不可信。

REA 的做法是把这些引擎全部收进一层统一的工具契约,再用 MCP 暴露给 agent。它不是自己写一个反编译器,而是一个提供方路由器 + 证据账本:Hopper 走 Unix socket 桥、Ghidra 走无头 Java 桥、IDA 走上游 MCP 适配、离线 ELF 和 core dump 走调用方自备的 pwntools、EVM 字节码走 EVMole(WASM,跑在受限 worker 里)、浏览器走 CDP/Playwright、Android 走钉死版本的无头 JADX、固件走 Binwalk/Unblob、.NET 走静态元数据读取。

关键在于它对这个位置很自觉。README 里那句话是设计纲领:「分析在本机运行,结果包含支撑各项结论的证据和相关限制。」 agent 拿到的不是「我觉得这个函数在算声像」,而是「这 12 条指令在这个地址、这段伪代码由这个引擎在这个版本产出、这条推断依赖那条观测、这几处我查不到所以是 unknown」。

架构:两个入口,一条会话,多个提供方

整个仓库的分层是清楚的,读 src/ 的目录就能看出边界:

REA 的调查流程:1 提问(你的智能体 + 本地目标:应用/二进制/浏览器)→ 2 检查与追踪(REA 通过 CLI 或 MCP 调用本地分析适配器)→ 3 读证据(代码、引用、unknown)→ 4 使用所得(智能体去解释、实现、测试),中间可以带着后续问题回到第 1 步

  • 入口层:src/cli.ts 是一次性 CLI 进程,src/main.ts 是 stdio MCP server,scripts/rea.mjs 是包的分发器。两条入口共用同一套应用工作流和证据契约 —— 这不是「CLI 是 MCP 的简化版」,而是同一份逻辑的两个前端。
  • 组合层:src/composition/(18 个文件)是带类型的工厂,负责把提供方、会话、记录器装配起来;src/server/(69 个文件)只做 MCP 协议翻译,不含分析逻辑。
  • 应用层:SessionProviderRouter + BinarySession 是核心。一个目标一次不可变的深度绑定;AnalysisProviderRegistry 给出排序确定的候选列表,选不出来就报 ambiguous,不做静默回落。此外还有调查记录(InvestigationRecords)、证据账本(EvidenceLedger)、Unknown 的归属、快照缓存,以及 JavaScript 制品重建。
  • 提供方层:src/hopper、src/ghidra、src/ida、src/evm、src/browser、src/dotnet、src/android、src/firmware、src/inspector、src/reference 各自封装一个引擎,对上只暴露提供方中立的契约。
  • 进程地基:src/process/(44 个文件)管自有进程组、私有运行时根目录、超时与所有权;native/windows/ 是一个 Node-API 插件(filesystem.cc、process.cc),配合 src/windows/ 的 WindowsOwnedProcess、WindowsPrivateRuntime、WindowsAuthority,在 Windows 上做 NTFS 准入、DACL 与 Job Object。

会话契约:绑定一次,不偷偷换引擎

这一块的设计密度明显高于普通工具项目,值得单说几条:

提供方绑定是不可变的。 open_binary 接受一个具体 provider id 或 auto;成功打开后,会话通过 analysis_provider_binding 暴露唯一的提供方、确切版本、选择来源与完整分析档案。文档写得很硬:「运行时失败之后,已选提供方绝不会被自动替换。」 这条对 agent 特别重要 —— 静默换引擎意味着前后两次结论出自不同反编译器,而模型完全看不出来。

可用性是逐工具上报的,不是全局开关。 binary_session 用 {} 调一次就能读到 tool_availability:当前目标、提供方、宿主、协商出的客户端能力下,每个工具是可用还是不可用、原因是什么、怎么修。它还能拿 expected_package_version、expected_catalog_digest、expected_server_path 跟调用方的预期对账。tools/list 则始终返回完整的规范清单,包含当前不可用的工具,开关目标不触发 list_changed 通知 —— 目录稳定,可用性浮动,两者分开。

输出 schema 不许有可达的递归引用,嵌套层级钉在 10 层以内(对象/数组/anyOf/oneOf/allOf),并且测试会在 SDK 转换之后连同生成物一起校验。这是 REA 自己定的兼容档案,因为各家模型 API 还会另加限制。

run_id 先于任何提供方进程分配。 每次成功的目标切换都会先拿到 analysis_run.run_id,process_lineage 在动态提供方启动前是 not_observed,之后变成 snapshots,记录每个已启动提供方的身份与所有权观测。每条观测要么是 verified(带启动 PID、父 PID、进程组、当次有界检查看到的子孙进程),要么是 unavailable 加原因。文档还特意声明:这些是历史快照,不是实时进程清单,也不声称「不存在过短命的子孙进程」。

进度不许编。 进度更新单调、限速到每 100ms 最多一条中间更新、终态更新永远放行;总量未知就直接省略,REA 不伪造百分比。 取消和超时是两个不同概念;清理失败走 cleanup_incomplete,且只列出仍然残留的自有资源类型。失败时可用观测保留在 details.partial_observation 里,并自报覆盖到哪一步。CLI 侧不需要 progress token,SIGINT 会翻译成同一个 AbortSignal;然后是最狠的一句约束:「REA 绝不杀掉一个它无法证明自己拥有的进程。」

工具形状学:先原语,后工作流

docs/tool-design.md 是这个项目最值得同行抄的一份文档。它规定了六种「工具形状」,新增工具必须先归类:

形状用在什么问题上契约该返回什么
inspect一个明确目标/地址/对象/资源的事实相关字段、源位置、分面级可用性
search / list找候选目标或实体稳定排序 + 有用的上下文;需要时才暴露分页
trace跨代码、元数据、UI 资源或观测的关系带类型的边、支撑证据、未解路径,并指出真实的遍历边界
compare两个被明确标识的制品、版本或证据集成对身份、可比的覆盖范围、带证据的差异
workflow值得在 REA 内部组合的独立分析成果可用的内联结果、贡献的证据、部分/不可用的分面
observe / capture必须靠运行时行为才能回答的问题所需权限、启动/附加行为、真实操作约束、生命周期与清理状态

配套几条判断标准同样锋利:能一次调用报告一个可复用事实的,先做成原语(一条指令解码、一个类型布局、一次引用、一个分派目标、一张资源图);只有当反复分析证明调用方总是要同一份多源结果、且 REA 能在不掩盖重要选择与不确定性的前提下把证据拼起来时,才升级成 workflow。判别方法是问一句:换一个共享同类证据的应用,这个 workflow 还有意义吗? 如果它的意义依赖某一个应用的业务规则,那就把解释留在契约外面,只暴露底层原语。

还有两条明确的「不要」:不要不透明的 mode 旗标,也不要把发现、执行、变更揉进一个巨型工具;公开的工具名和结果语义必须提供方中立,引擎特有的解析和协议处理放进适配器,「不要只因为引擎不同就造平行工具」。prompt 是可选的、简洁的,可以指出有用的工具,但不得在任务能直接回答时规定调用顺序。

能分析什么

覆盖面是它最直观的价值。除 Node.js 与 npm 之外,额外依赖都按目标类型可选:

目标REA 返回什么前置条件
原生二进制伪代码、汇编、字符串、符号、调用与引用Hopper、Ghidra 或 IDA
离线 ELF 布局节、段、原始符号/重定位、静态防护机制候选项Linux x64,调用方自备 pwntools
EVM 字节码分派选择器、字节偏移、推断的参数与状态可变性本地原始字节/十六进制输入
已记录的 Linux 崩溃原始 note 记录、每线程寄存器/信号、可选映射候选项pwntools;GDB/pwndbg 可选
JavaScript / Electron模块、导入、source map、路由、IPC 与原生扩展关系只要 Node.js 和 npm
网站页面结构、脚本、网络观测、按请求获取的截图Chrome 系浏览器
已保存的网络捕获请求、响应、可访问载荷与来源位置HAR;原生 mitmproxy 捕获需 Linux 上的 mitmdump
.NET 程序集元数据、CIL 指令、声明的原生依赖、构建对比纯静态检查,无外部引擎
Android APK清单声明、类、反编译方法与引用Linux/macOS 上的无头 JADX + 完整 JDK
固件区域、提取结果,以及转交原生分析的内容Linux 上的 Binwalk / Unblob
软件包与资源文件清单、摘要、plist、Apple bundle 结构、提取的资源无
进程行为终端输出、交互、退出与文件系统观测,以及运行对比支持原生 PTY 的 Linux/macOS

边界也写得清楚:静态 JavaScript 与 .NET 检查只读你提供的文件,不运行应用;运行时捕获则以你的用户权限运行目标或与之交互,各运行时指南逐条说明具体影响。Ghidra 另外支持 16 位 DOS 分析与实验性的 Windows 支持。

unknown 不是 empty:证据契约

如果说整套设计里有一条最值得抄进任何 agent 工具的规矩,就是这句:「缺失的证据是 unknown,不是 empty,也不是 false。」

这三个值在语义上完全不同,但在多数工具的输出里会被压成同一个空数组或 null。empty 意味着「我查了,确实没有」;false 意味着「我查了,结论是否定的」;unknown 意味着「我没能查到」。对 agent 而言,把第三种当成前两种,就会自信地写出「该应用不存在任何 IPC 调用」这种错误结论 —— 而它其实只是没打开对应的那条通道。

REA 因此在契约层面把三者分开,并且把「观测到的」与「推导/推断出的」边分开标注,效果(会不会启动进程、会不会联网、会不会写文件)必须如实声明。证据记录保留制品身份、源位置、观测、推断与未解发现,可以用 rea evidence-import / evidence-export / compare 校验、导出规范形式、比对两份 bundle;导出默认不覆盖已有目标文件,除非显式给 --overwrite。

快照机制同一套口径:只有目标字节、操作、参数、提供方与设置全部匹配时才复用结果,变更类调用与依赖游标的调用一律排除在缓存之外,快照文件本地存储且权限仅限所有者。

那个被自己删掉的重放引擎

仓库里最能说明团队判断力的一件事,是 docs/adr/0002 的状态:Superseded —— 被控制的 JavaScript 重放工具已经移除,设计文档作为历史保留。

这个工具原本的意图很合理:把从应用里恢复出来的 JavaScript 模块用受控输入和确定性 stub 执行一遍,好跨版本比对解析器、清洗器、序列化器这类纯逻辑。但 ADR 里把风险写透了:从应用中恢复出来的源码是不可信代码,它可能有意或无意地读文件、连服务、起进程、耗尽资源、污染分析进程、伪造协议输出,或者跟同用户的另一个进程交互。而结论是那句值得贴在墙上的话:「JavaScript realm 或 Node.js vm context 适合用来构造 API 表面,但它不是安全边界。」 Node 官方也把权限模型描述为「防止可信代码误访问」,而不是「containment of malicious code」。

它也没有把已有权限偷偷扩大:browser_observe 授权的是对已在运行的、操作者拥有的目标做被动附加,不含求值、导航、输入或目标生命周期变更;process_capture 授权一个显式声明的宿主进程场景,并明确写着「它不是沙箱」。于是这个能力被删掉了。docs/roadmap.md 记了两个 PR:#555 移除了 REA 的权限授予、作用域上限、elicitation 与重复审批字段;#572 移除了重放引擎、Node 特征化的 prepare/execute 流程,以及只做计划的托管运行时关联工具。 现在的口径是「本地操作使用当前用户的 OS 权限」,把安全模型从「我们自己实现一套权限系统」退回到「不假装能提供它给不了的隔离」。

在一个遍地「给 agent 加沙箱」卖点的项目群里,主动删掉自己的沙箱叙事、并留下 ADR 解释为什么,这是相当少见的工程诚实。

重建义务台账:把「我重建完了」变成可判定命题

另一个有意思的抽象是 build_reconstruction_obligation_ledger(CLI 同名 rea build-reconstruction-obligation-ledger):它把已认证的 Evidence 记录转成一份确定性的重建声明清单。

它保守到有点苛刻:静态应用图的事实只能产生候选义务,不能证明运行时或进程行为。一条必需义务的关闭条件是,某个 manifest 绑定同时提供唯一的所有者、所需的解析器/schema/领域类型、每一个必需的用例 fixture,以及一个通过且权限等级与原始观测相当的验证器;验证器必须枚举出义务 ID,其结果必须出现在输入的 Evidence bundle 里。矛盾、重复定义或重复所有者、残留 unknown、缺失依赖、权限不可得 —— 任何一项都会让关闭状态保持 open 或 failed。

文档还专门给了一个空请求示例,用来检查客户端集成:它合法,返回一个 unknown 的台账、零条义务,因为「源 Evidence 的缺失永远不声称已关闭」。这就是整套证据哲学在最后一个环节的落地 —— 连「什么都没查」都不许伪装成「查完了没问题」。

三个案例:它到底能挖到多深

README 给的三个 showcase 是判断这类工具真实水平的最好材料,因为它们都带可复现的落点:

DX-Ball 声像调查图:REA 给出 DXBALL.EXE 0x00406400 处一个函数的指令、调用方与字节读取证据;栈上的输入 x 先乘 1.5625、再减 500.0、再乘 pan_scale、最后转成整数返回;重建出的函数被维护成 C,一边与砖块命中调用方的 20 + 30 × tile_x 对照,一边与 VC4.0 构建做比较,结果是 3,205 个用例通过、63 个字节完全一致

  • DX-Ball:重建声像计算。 沿声音调用追到 DXBALL.EXE 偏移 0x00406400 处的一个辅助函数,它把栈上的位置输入 x 乘 1.5625、减 500.0、再乘一个 pan_scale 后转成整数返回;调用它的砖块命中逻辑算的是 20 + 30 × tile_x。检查指令、把不完整的伪代码转成 C 之后,重建结果通过了 3,205 个原始 x86 测试用例,并复现了 VC4.0 编译后函数的全部 63 个字节。字节级一致是逆向里最硬的验收标准 —— 不是「行为差不多」,是「编译器输出对得上」。
  • Notion:追踪 Electron 剪贴板桥。 找到渲染进程的剪贴板 API,沿 preload 和 IPC 追到主进程,并检查富格式剪贴板数据。这是典型的「我想在自己的产品里做一个一样的功能」场景,跨的正是 Electron 最容易糊住的那道进程边界。
  • TH04:恢复 DOS 环形弹幕的计算。 检查原始 PC-98 游戏的 16 位指令,恢复环形弹幕的角度算术:那一代代码把一个完整顺时针圆周定义成 256 个角度单位,16 发子弹的间距就是 256 ÷ 16 = 16 个单位(22.5°);固定环从角度 0 起(0、16、32 … 240),瞄准环则从玩家方向起(例如玩家方向 40 时是 40、56、72 … 24)。重建的 C++ 再与当年编译器的输出对比。三十多年前的 16 位 x86,靠 Ghidra 的 DOS 支持啃下来。

TH04 环形弹幕的间距与瞄准:一个完整顺时针圆周是 256 个角度单位;固定环的第一发在角度 0,瞄准环的第一发指向玩家方向;两者间距相同,都是 256 ÷ 16 = 16 个角度单位,即 22.5 度

安装与日常使用

给 agent 装:

npx rea-agents setup

选择宿主、审阅计划中的变更、批准。setup 会添加 REA 的 MCP 服务与匹配的工作流指引,并备份已有配置,然后重启 agent。原生分析可以复用已装的 Hopper/Ghidra/IDA,setup 也可以在你批准后安装 Hopper;静态 JavaScript 分析两个引擎都不需要。想先看一眼不落盘,用 setup --dry-run,它返回 planned 并以 0 退出(被取消的 setup 同样退 0);返回 needs_confirmation 或 needs_human 时退 1。

直接在终端用(无需全局安装):

npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json

# 原生分析(需先配置提供方)
rea analyze /absolute/path/to/program --provider ghidra --json
rea search  /absolute/path/to/program "search" --provider ghidra --json
rea decompile /absolute/path/to/program 0x1000 --provider ghidra --json
rea xrefs   /absolute/path/to/program 0x1000 --provider ghidra --json
rea trace   /absolute/path/to/program "search" --provider ghidra --json

# 提供方与能力自检
rea providers --json
rea capabilities --json
rea doctor --provider ghidra --json

几个实用细节:终端默认输出格式是 TOON,给 JSON 消费者用就加 --json,输出格式不影响操作状态;退出码是 0 完成(结果里可能仍带部分证据、警告或未解问题)、1 未完成、128+N 被信号 N 结束。REA_ANALYSIS_PROVIDER 设长期偏好,显式 --provider 覆盖它,MCP 侧对应 open_binary 的 provider_id。--snapshot 把成功结果存下来给后续查询复用。管道里记得 set -o pipefail,否则下游的 jq 会把 REA 的失败状态吃掉。

版本更新走 rea update(CLI)或 npx rea-agents@latest setup(agent 注册与 skill),README 特别提醒这个项目迭代很快、新版本经常带 bug 修复,建议保持最新。

工程面

1,282 个源文件配 650 个测试文件,这个比例在同类项目里属于很高的水位,而且测试文件名能看出它测的是什么:ProcessOwnership.identity.test.ts、ProcessOwnership.part2/3.test.ts、ProcessOwnership.validation.test.ts 把「进程所有权」这一个概念拆成四组测试;EvmWorkerLimits.test.ts 盯 EVM worker 的资源上限;contractSnapshot.test.ts 把工具契约本身做成快照。docs/mcp-contracts.md 里还提到,机器可读的目录 docs/public/product-catalog.json 由 npm run build:cached 生成、描述的是正在构建的那个确切源码修订,而不是签入的快照,PR CI 会把它连同打包的 skill 与可移植一致性投影一起留存在 generated-docs 产物里。

路线图上的方向也很明确:让生成的元数据与叙述文档跟工具/提供方/setup/发布保持对齐;在 Hopper 与 Ghidra 上扩展原生架构、类型与间接调用的验证;把更多静态提取器与运行时观测接到跨层特性追踪上;改进混淆 .NET 的比对,以及托管发现与已验证原生分析之间的连接;扩展进程、协议、文件系统、重连与构建对比的覆盖;评估通过 LLDB、Frida、系统日志与 API 追踪做原生运行时观测;接入 Binary Ninja、Rizin 等更多引擎与目标。

判断:它适合谁

适合:需要在没有源码的情况下理解一个桌面应用/游戏/插件的行为,并让 agent 参与这个过程的开发者;做安全研究与漏洞分析、需要可引用证据链而不是聊天记录的团队;想给自家 coding agent 补上「读二进制」这项能力的平台方 —— 它的 docs/tool-design.md 本身就是一份可以直接抄的 MCP 工具设计规范。

不适合:指望它替你判断结论对错的人。REA 的立场是把证据、限制和 unknown 如实交给你,解释和决策仍然在调用方;如果你想要的是「一键告诉我这个程序在干什么」,它给的是一份需要你(或你的 agent)去读的证据档案。另外它对宿主要求不低:Node 22.19+/24.11+/26+,原生分析要 Hopper/Ghidra/IDA 之一,Android 要 Linux/macOS + 完整 JDK,固件要 Linux,历史源码导入在原生 Windows 上直接返回 unsupported_host(官方建议在 WSL 里跑 Linux 版)。

五个月 2.6 万星不是靠营销 —— 它踩中了一个真实的空缺:agent 已经会写代码了,但还不会读已经编译好的世界。 REA 把这块补上,而且是用一套「宁可说不知道,也不假装知道」的契约补上的。这在当下的 AI Coding 生态里,是比多一个补全模型更稀缺的东西。

作为亚马逊联盟会员,我们可能从符合条件的购买中获得佣金。