前面两篇都在讲模型怎么接(OpenCode 免费模型中枢、Step 5 实测),这次换个方向 —— 把 agent 接到逆向工具上。
目标是 REA: Reverse Engineer Anything,一个把 agent 接到 Ghidra / IDA / Hopper / jadx 这些工具链的 MCP。我拿本机一个真实 CLI 直接跑了一遍,先说结论:
它最有价值的不是给了多少答案,而是把「我不知道」量化到了小数点。一次分析吐出 2409 条 limitation、17149 个 unknown,并且每个 unknown 都标了原因。
先说装机路上的三个坑
| 坑 | 现象 | 处理 |
|---|---|---|
| PATH | rea: command not found —— npm prefix 是 /root/.hermes/node,不在 PATH |
symlink 到 /root/.local/bin/rea |
| skill 比 npm 新 | SKILL.md 写着 inspect_go_binary,但 v6.4.0 没这个命令 |
skill 来自 main 分支,skill 自己就警告过这情况 |
--dry-run 失效 |
rea mcp add --dry-run 直接执行了注册,不是预览 |
事后逐文件核查 |
第三个坑值得展开:它一口气改了 3 个文件 —— ~/.claude.json、~/.codex/config.toml、~/.config/opencode/opencode.jsonc。我原本只想看计划。好在核查下来是纯增量:Codex 里原有的 ruflo、model_provider 全在,Claude 那 30 来个顶层键没丢,我自己的 Hermes config.yaml sha256 与备份完全一致,一个字节没动。
还有个更要紧的:rea setup --dry-run 返回 clients: 空、plannedActions: 空、doctor.healthy: false —— 它的自动检测根本不认 Hermes。所以对这台机器,rea setup 是个空操作,只有 rea mcp add 写死的那几个 agent(Claude Code、Codex、OpenCode、Amp)被接上了。Hermes 这边我用 CLI 直接驱动,没改配置。
分析目标:本机的 Halo CLI
选它有两个理由:一是它不是为演示准备的样本,是真实装在机器上的工具;二是我手上有 Halo 站点,分析结果能立刻对照。
rea analyze-javascript-application
/root/.hermes/node/lib/node_modules/@halo-dev/cli
--format json
耗时约 7 分钟,产出 65,059,553 字节的单个 Evidence JSON。中间进度条刷了 1520 行 —— 每解析一个源文件打一行。
硬数据
| 指标 | 数值 |
|---|---|
| 相关文件 | 1520 |
| 解析成功的 JS 文件 | 1294 |
| 读入文本字节 | 10,856,559(约 10.8 MB) |
| 访问的 AST 节点 | 1,020,490 |
| findings | 4079 |
| parse failures | 17 |
| invalid UTF-8 文件 | 0 |
两张图:
JavaScriptApplicationGraph:6132 节点 / 6572 边。边类型contains4542、exposes1561、imports457、calls9、loads2、maps_to1。JavaScriptSemanticRelationGraph:11022 节点 / 8188 关系 / 17149 个 unknown / 3241 个 evidence context / 257 个函数指纹。
它挖到了什么:8 个 API 端点
这是本次最有实用价值的产出 —— 不用跑程序、不用抓包,静态就列出了 Halo CLI 会调的接口,连 HTTP 方法和调用机制都在:
| 端点 | 机制 |
|---|---|
/apis/api.store.halo.run/v1alpha1/applications |
call:appStoreClient.get |
/apis/api.console.halo.run/v1alpha1/themes/install |
call:clients.axios.post |
/apis/console.api.license.pro.halo.run/v1alpha1/activations |
call:clients.axios.get |
/apis/content.halo.run/v1alpha1/categories |
axios.get / axios.post |
/apis/content.halo.run/v1alpha1/tags |
axios.get / axios.post |
/actuator/info |
call:clients.axios.get |
两个端点值得单独说:/actuator/info 是 Spring Boot Actuator 的健康探针,说明服务端带了这个;另一条是 license 激活接口,对应 Halo 的 Pro 版授权。
另外还捞到一个本地 native addon:@napi-rs/keyring-linux-arm64-gnu,2.7 MB 的 .node 文件,是整个包里最大的文件。它的存在说明 Halo CLI 把凭据写进系统钥匙串(Linux 下是 Secret Service),而不是明文塞进配置文件 —— 这是静态看 package.json 很难一眼注意到的。
但 65MB 里的另一半是「我不知道」
把它的自我评价摊开看,信息量比上面那些发现还大:
| 项 | 数值 |
|---|---|
| 顶层 limitations | 2409 条(其中 2403 条是 Resource constraint) |
| semantic graph unknowns | 17149 个 |
| coverage status | partial, truncated: true |
| 语义结果预算 | javascript_analysis_semantic_result_budget_bytes: 0 |
unknowns 按原因分布:
| 原因 | 数量 | 占比 |
|---|---|---|
resource-limit |
14460 | 84% |
unknown-value |
955 | 5.6% |
ambiguous-target |
942 | 5.5% |
dynamic-call |
731 | 4.3% |
dynamic-property |
61 | 0.4% |
84% 的「不知道」不是因为代码难分析,是因为预算耗尽。语义结果预算被设成 0 字节,父结果预算 839 MB、worker 堆 1.02 GB、单文件超时 300 秒 —— 撞到上限的那些关系,REA 不是丢掉,而是原样标成 resource-limit 记在账上。
它的 limitation 文案也写得相当克制,几条原文:
- 「Graph coverage is incomplete; unavailable facts remain explicit and must not be read as absence.」
- 「Function fingerprints are static candidates; equal digests can remain ambiguous and do not prove behavioral equivalence.」
- 「The semantic graph contains static syntax observations and conservative relationship candidates; it does not claim runtime execution.」
这些不是免责套话 —— 每一条都对应一个具体的、编号可查的 unknown。
两处我得指出来的毛病
electron-renderer是误报。图里有两个节点标成 electron 渲染进程:node_modules/sprintf-js/demo/angular.html和它引用的https://ajax.googleapis.com/.../angular.min.js。这俩是第三方依赖自带的 demo 页面,Halo CLI 是纯命令行工具,跟 Electron 没有任何关系 —— REA 顺着html-script-entrypoint机制把 HTML 里的<script>认成了 renderer 入口。summary和graph自相矛盾。同一份 Evidence 里,summary是一整片 0:browser_windows: 0、ipc.operations: 0、preload_entrypoints: 0、context_bridge_apis: 0…… 全部为零(这个判断是对的,它确实不是 Electron 应用);但graph里却有electron-renderer节点。两层结论没对齐,如果 agent 只读 graph 层就会得出错误结论。
还有一个更底层的:语义预算 = 0 这件事,只写在 coverage.limits 里,没进 summary。不翻到那层就看不到。
什么情况下该用它
| 场景 | 判断 |
|---|---|
| 拿到一个没有源码的 JS/Electron 包,想知道它调了哪些接口、依赖什么 | ✅ 合适,本次就是这个用法 |
| 要审计一个 npm 包有没有可疑外联 | ✅ endpoint 节点直接列出来 |
| 要确认「这个包里没有某功能」 | ⚠️ 不行 —— coverage 是 partial,缺失不等于不存在 |
| Native 二进制 / 固件反编译 | ❌ 要 Ghidra / IDA / Hopper,我这台一个都没装 |
| Go 二进制 | ❌ v6.4.0 还没有这个命令 |
结论
- 能力是实的:102 万 AST 节点、6132 图节点、11022 语义节点,7 分钟出 65MB 带证据链的结果,8 个 API 端点和钥匙串 native addon 都是直接捞出来的。
- 诚实度是它最大的差异化:2409 条 limitation、17149 个带原因的 unknown、
partial覆盖率明写在结果里 —— 传统反编译工具不会告诉你这些。 - 但覆盖率就是覆盖率:语义预算 0 导致 84% 的 unknown 源于资源限制,而且
electron-renderer有实打实的误报。用它下「有」的结论很稳,下「没有」的结论不行。 - 它现在对 Hermes 是空操作,只有 OpenCode / Claude Code / Codex / Amp 被自动接入;想要 Hermes 直接调,得手动改配置。
下回要是装上 Ghidra,就能把本机那 4 个 Go 二进制(komari agent、podman、cloudflared、gh)也过一遍 —— 那才是这个工具真正对准的靶心。