rea-reverse

REA 的 Evidence、快照与版本对比:从原理到实操

基于 REA 6.4.0更新于 约 9 分钟读完
本页目录

结论先行: REA 的每个结果都是一条 Evidence 记录,绑定了具体产物(按 SHA-256)、provider、操作、参数和局限说明。多条记录组成 Evidence 包(bundle),可以用 rea evidence-import 校验、用 rea evidence-export 规范化导出、用 rea compare 做对比。快照(--snapshot)缓存原生分析结果,里面也带着一个证据包。要回答“版本 A 和版本 B 之间改了什么”,三样东西各管一摊:真正的差异由各领域的对比命令给出;证据包对比用来审计证据本身;rea import-reference-source 负责把历史源码树作为参照导进来。

这篇文章写给已经在用 REA、需要让结果“留得住”的人:一周后还要为结论辩护的审查者,需要共享发现的团队,以及要逐个版本审查依赖的人。场景是 同一个开源 Electron 应用的两个发布版本(任何能从 GitHub Releases 下载的 MIT 或 GPL 项目都行),外加在其中一个 tag 上检出的源码树。

读完你会知道:

  • Evidence 记录和 Evidence 包是什么、不声称什么
  • --snapshot 怎么保存和复用原生分析
  • evidence-import、evidence-export、compare 实际做了什么
  • 怎么对比两个版本的应用图
  • 怎么把历史源码文件映射到发布出来的 bundle 上

前置条件

  1. 已安装 REA(npm install --global rea-agents,或用 npx -y rea-agents@latest <command>)。见 安装 REA 并接入 Claude Code / Cursor / Codex。
  2. 快照那一步需要原生 provider(Ghidra、Hopper 或 IDA)。Ghidra 的配置见 REA + Ghidra 免费原生二进制分析。
  3. jq 和 Node.js,用来拼 JSON 输入。
  4. 开源应用的两个版本,已经解开。对 Electron 应用来说,就是每个版本的 app.asar(或解包后的目录),以及 app.asar.unpacked 里的原生 .node 扩展。这些文件在哪里,可以参考 Electron 应用分析。
  5. 项目的本地 git 仓库,检出到旧版本的 tag。

本文全部是静态操作:REA 只读文件,不运行应用。如果要扩展到运行时采集,只能针对你有权分析的软件和系统,并先读 DISCLAIMER.md。

示例目录如下(按你的实际情况调整):

/work/app-v1/app.asar               # older release
/work/app-v2/app.asar               # newer release
/work/app-v1/native/addon.node      # native addon from v1
/work/app-v2/native/addon.node      # native addon from v2
/work/src-v1/                       # git checkout at the v1 tag
/work/evidence/                     # outputs

模型:记录、证据包、快照

动手前,先弄清每种文件里装的是什么。

Evidence 记录。 一次分析的结果。按 REA 源码中的 schema,它包含 evidence_id(带 ev_ 前缀的摘要)、subject(产物名、SHA-256、格式、架构、本地路径)、provider、operation、parameters、normalized_result、confidence(observed、derived 或 inferred)、limitations、locations 和 evidence_links。ID 由记录内容推导而来,同样的输入用同样的方式分析,得到的 ID 也相同。对比正是建立在这一点上。

Evidence 包。 一个 JSON 文档,包含 artifacts、providers、environments、scenarios、captures、unknowns 和 records。记录按确定性规则排序,顺序本身没有含义。证据包里还有残留未知项(residual unknowns),也就是调查中记下的未解决问题及其修订历史。

分析快照。 针对单个目标的成功分析结果缓存,包含 target、binding、entries、workflow_entries 和 evidence_bundle。只有目标字节、操作、参数、provider 和设置全部一致时,才会复用缓存结果。快照文件只有属主可读写。

整个体系都遵循同一条原则:REA 把观测、推断和未知分开存放,缺失的数据永远不会变成“没有变化”。读后面的对比结果时请记住这一点。

第 1 步:用 –snapshot 保存原生分析

先处理原生扩展,因为它分析最慢,缓存的收益也最大。--snapshot 选项(“Load and update a local analysis snapshot”)可以用在 analyze、inspect、decompile、xrefs、trace、function、instructions、search,以及 inspect-native-api 和 inspect-native-dispatch-metadata 上。

mkdir -p /work/evidence
rea analyze /work/app-v1/native/addon.node --provider ghidra \
  --snapshot /work/evidence/addon-v1.snapshot.json --json > /work/evidence/addon-v1.overview.json
rea analyze /work/app-v2/native/addon.node --provider ghidra \
  --snapshot /work/evidence/addon-v2.snapshot.json --json > /work/evidence/addon-v2.overview.json

同样的命令再跑一次,就会直接返回缓存结果。CLI 文档说明,精确命中时会在启动 provider 进程之前读取缓存,所以重复查询很快。每个版本单独用一个快照文件:一个快照只对应一个目标身份,混用就失去了意义。

需要留意:

  • 元数据写入会让缓存失效。 如果在会话里重命名了函数或加了注释,保存或导入下一个快照之前,先关闭再重新打开目标。
  • 分析 profile 变化也有影响。 provider 的分析 profile 变了之后,旧快照会报 profile 不匹配,不会回放过期结果。旧快照里的 evidence_bundle 仍然可读;不带旧快照重新跑一遍,就能生成新快照。
  • 走 MCP 时,导入用 open_binary 加 snapshot_path,保存用 close_binary 加 snapshot_path,可选 overwrite: true。保存失败时会话保持打开,已缓存的工作不会丢。

第 2 步:取出证据包并用 evidence-import 校验

快照的证据包放在顶层的 evidence_bundle 键下。REA 原生分析文档里描述的正是这个做法:把该对象复制成单独的 JSON 文件,再用 rea evidence-import 校验。

jq '.evidence_bundle' /work/evidence/addon-v1.snapshot.json > /work/evidence/addon-v1.bundle.json
jq '.evidence_bundle' /work/evidence/addon-v2.snapshot.json > /work/evidence/addon-v2.bundle.json

rea evidence-import /work/evidence/addon-v1.bundle.json --json

在 CLI 里,evidence-import 的作用是校验:每个 Evidence ID 都必须和记录内容一致,各 manifest 必须自洽。之后返回计数(imported、unknowns_added、total)。单独的 CLI 进程没有会话可以合并,所以这里真正有用的是校验这一步。在 MCP 里,import_evidence_bundle 才会把证据包合并进当前会话;按契约,导入的内容只当作数据,绝不会被执行。

另一种常见来源是 agent 会话。agent 通过 MCP 做完分析后,让它用一个绝对路径 path 调用 export_evidence_bundle,就能原子地写出会话的完整账本,包括已记录的未知项。一定要在 close_binary 之前导出,因为关闭会清掉保留的记录。

第 3 步:用 evidence-export 规范化

rea evidence-export /work/evidence/addon-v1.bundle.json /work/evidence/addon-v1.canonical.json
rea evidence-export /work/evidence/addon-v2.bundle.json /work/evidence/addon-v2.canonical.json

evidence-export 会重新校验源文件,再原子地写出规范化、字节稳定的 JSON。目标文件已存在时会拒绝覆盖,除非显式加 --overwrite。证据包已经合法了,为什么还要导出一次?

  • 差异干净。 字节稳定之后,对两份导出做 git diff 或比对校验和,看到的只有真实的内容变化,没有格式噪音。
  • 便于归档。 和审查报告一起提交、或附在 issue 上的,就应该是这份规范化文件。
  • 输入规范。 文档中 rea compare 的定义就是对比两个规范化证据包。

如果导出时文件已经写出、后续清理却失败了,看 details.published、details.published_path 和 details.bytes。MCP 契约靠这几个字段区分“已完成写出但清理有问题”和“根本没写成”这两种情况。

第 4 步:用 rea compare 对比证据包

rea compare /work/evidence/addon-v1.canonical.json /work/evidence/addon-v2.canonical.json --json \
  > /work/evidence/addon.bundle-diff.json

jq '{status, summary, limitations}' /work/evidence/addon.bundle-diff.json

(别名是 compare-bundles。)这一步一定要理解准确:证据包对比比较的是证据成员,不是程序行为。 按 REA 的实现:

  • 两边都有的记录(evidence_id 相同)计为 unchanged。
  • 只出现在一边的记录归类为 unknown,结论类型是 unresolved_branch,不是 added 或 removed。理由是证据包没有“清单完整”标记,缺某条记录可能只是没采集到。
  • 残留未知项按修订历史对比:一边的历史是另一边的延续时记为 history_advanced,两边分叉时记为 history_diverged(矛盾)。
  • 顶层 status 为 unchanged、changed 或 unknown,同时附上两个证据包的 SHA-256。

两个版本的二进制 SHA-256 不同,记录 ID 自然也不同,所以几乎所有记录都会落进 unknown。在这个层面上,这就是如实的回答。证据包对比真正的用处在别处:对比同一版本的两次运行(同事有没有复现我的结论?REA 升级后结果变了吗?),以及追踪未解决问题在几轮审查之间的进展。MCP 里的 compare_bundles 还支持 record_pairs,可以显式一对一配对具体记录;CLI 命令只接受两个路径。

要回答“代码改了什么”,得用对应层面的领域对比命令。

第 5 步:用 compare-application-versions 对比 JavaScript 层

先把每个版本的 JavaScript 分析成应用图:

rea analyze-javascript-application /work/app-v1/app.asar --json > /work/evidence/app-v1.json
rea analyze-javascript-application /work/app-v2/app.asar --json > /work/evidence/app-v2.json

compare-application-versions 的输入是一个 JSON 对象,left 和 right 放两条完整的 Evidence 记录。CLI 工作流命令接受内联 JSON 或文件路径,但不接受只有 ID 的输入,因为单独的 CLI 进程看不到 MCP 会话里保留的记录:

jq -n --slurpfile l /work/evidence/app-v1.json --slurpfile r /work/evidence/app-v2.json \
  '{left: $l[0], right: $r[0]}' > /work/evidence/versions-input.json
rea compare-application-versions /work/evidence/versions-input.json --json > /work/evidence/versions.json

jq '.normalized_result | {summary, coverage}' /work/evidence/versions.json

真实应用的 Evidence 可能有几百 MB,jq --slurpfile 会比较吃内存。JavaScript 工作流文档里有用一小段 Node 脚本完成同一步的写法,可以作为替代。

配对规则见 JavaScript application workflows:只有当某一层在两边各产生唯一候选时才配对。各层依次是:内容摘要精确匹配、模块源码摘要精确匹配、节点身份精确匹配、source map 身份、结构指纹,最后是非模块实体的语义键。压缩后的变量名和模块序号永远不会被当作身份。每一项的结果是 unchanged、added、removed、changed 或 unknown;要判为 added 或 removed,另一侧的图必须是完整覆盖,否则一律是 unknown。

想做端到端核验的话,仓库里有一个验证脚本,接受两个由你提供的目录或 ASAR(npm run verify:application-workflows -- --left ... --right ...)。它是 REA 源码仓库里的开发脚本,只有基于源码工作时才需要运行。

第 6 步:用 import-reference-source 导入历史源码

发布产物都经过打包和压缩。项目开源的话,tag 上的源码就是最好的参照。REA 把它当作参照数据,绝不当作对发布应用的观测:

cd /work/src-v1 && git checkout v1.0.0   # the tag matching app-v1
rea import-reference-source /work/src-v1 --json > /work/evidence/src-v1.graph.json

按 CLI 文档,导入会记录:

  • 每个纳入文件的哈希和元数据
  • 最近一层 Git 仓库的 HEAD 提交。工作区是否真的和这个提交一致,会被记为未知,所以导入前先提交或 stash 本地改动
  • 排除规则:项目的 .gitignore,加上 node_modules/、dist/、*.log 等默认规则,每条都记下匹配的模式和来源
  • 可选的敏感路径模式:把 JSON 数组写进 REA_REFERENCE_SECRET_PATTERNS_JSON,这些会单独报告

平台说明:历史源码导入依赖 Linux 或 macOS 上的安全 no-follow 文件打开。原生 Windows 会返回 unsupported_host,请改在 WSL 里运行。

接着把源码映射到发布的 bundle 上:

jq -n --slurpfile ref /work/evidence/src-v1.graph.json --slurpfile app /work/evidence/app-v1.json \
  '{reference: $ref[0], application: $app[0]}' > /work/evidence/src-to-bundle-input.json
rea compare-source-to-bundle /work/evidence/src-to-bundle-input.json --json > /work/evidence/src-to-bundle.json

jq '.normalized_result.summary' /work/evidence/src-to-bundle.json

每个历史文件会归入 unchanged、modified、removed、split、merged、duplicated 或 unknown。所有打分信号都连同权重一起报告:源码摘要精确匹配、source map 原始路径、当前路径精确匹配、路径后缀、文件名、语言扩展名。精确摘要优先;仅靠文件名或扩展名这类弱信号,永远不会强行建立映射。

两种对比各有用处:

  • v1 源码 vs v1 bundle:自检。看发布产物和 tag 源码的对应程度,以及构建流程在哪些地方改动了代码。
  • v1 源码 vs v2 bundle:看哪些旧源码文件在新版本里仍有精确或高置信度的匹配,哪些变成了 modified 或 unknown。和第 5 步结合起来,就知道真实的 Git diff 应该先读哪几处。

如果某个图是用旧版 REA 保存的,且其中基于模式的排除项缺少 pattern 字段,按文档要求,先重新导入再做对比。

常见问题和排错

evidence-import 拒绝了我的证据包。 多半是 ID 不匹配:导出后记录被人工或其他工具改过。Evidence ID 由内容推导,任何修改都会让它失效。请从原会话或快照重新导出。

evidence-export 不肯写文件。 目标文件已存在。确认要覆盖就加 --overwrite,否则换个文件名。

rea compare 几乎全是 unknown。 跨版本对比时这是预期结果,原因见第 4 步。代码层面的差异请用 compare-application-versions,.NET 用 compare-managed-members,原生和产物层面在 MCP 里用 compare_functions / compare_artifacts。

快照好像没有被复用。 命中缓存要求字节、操作、参数、provider 和设置全部一致。换了 --provider、分析 profile 变了、或者发生过元数据写入,都会导致未命中。

工作流命令传 Evidence ID 时报 invalid_request。 CLI 需要完整的记录;保留 ID 只在同一个 MCP 连接内有效。

import-reference-source 报 unsupported_host。 你在原生 Windows 上,请在 WSL 里运行 REA。

下一步