用 REA 做固件分析:Binwalk、Unblob 解包再交给 Ghidra
本页目录
先说结论: REA(morluto 维护的开源项目)本身不带固件解包器。它调用你已经装好的 Binwalk 和 Unblob,在固定的资源限制下运行它们,再把结果整理成 Evidence:区域偏移、提取出的文件路径、SHA-256、以及一份限制说明。之后你从里面挑一个 ELF,用 Ghidra provider 像分析普通原生程序一样打开它。固件这一步只在 Linux x64 上验证过,所以请直接准备一台 Linux x64 主机或虚拟机。
这篇适合想把一个开源固件镜像从“一整块二进制”一路走到“反编译出的函数”、又不想自己写胶水脚本的开发者和安全研究人员。读完你会知道:
- 怎么确认主机和工具满足 REA 的固件要求;
- 用 Binwalk 查看 OpenWrt 镜像的区域(
inspect-firmware-regions); - 用 Unblob 解包(
extract-firmware),包括只解指定范围; - 怎么正确理解 Evidence,尤其是 “complete” 到底意味着什么;
- 怎么通过
rea analyze或 MCP 工具open_binary把提取出的 ELF 交给 Ghidra。
下文的命令和限制都来自 REA 6.4.0 的固件分析文档。凡是展示输出的地方,都是示意。
只分析你有权分析的固件:OpenWrt 这类开源构建、你自己编译的镜像,或者 CTF 题目。请先阅读 REA 的 DISCLAIMER.md。
先看平台限制
装任何东西之前先看这一节。REA 的固件支持范围比它的大多数功能都窄。
| 组件 | 支持范围(REA 6.4.0 文档) |
|---|---|
| 固件区域查看与解包 | 仅在 Linux x64 上验证;README 里写的是 “Binwalk / Unblob on Linux” |
| Binwalk | 接受 3.1.x,解析器用 3.1.0 验证 |
| Unblob | 接受 26.6.x,解析器用 26.6.4 验证 |
| 资源限制 | 依赖 util-linux 的 prlimit(通常是 /usr/bin/prlimit) |
| Ghidra provider(下一步) | Linux x64/arm64、macOS x64/arm64;Windows x64 是实验性的 P0,只支持 PE |
| 已验证格式 | 生成的 gzip、USTAR、ext4,内含 Linux x64 ELF |
| 未验证 | SquashFS、UBIFS、私有固件格式、其他主机和架构 |
实际影响有两点:
- 别指望固件功能在 macOS 或 Windows 上跑。
prlimit属于 util-linux,验证流程也只在 Linux x64 上跑。用 Mac 的话,整套流程放到 Linux x64 虚拟机或远程机器上做。 - 大多数家用路由器固件是 MIPS 或 ARM。 REA 的 Ghidra MIPS 支持刻意做得很窄:只接受带显式 o32 和 MIPS32r2 标志的 ELF32
ET_EXEC/ET_REL,另外给 MIPS16 共享库单独留了一个有边界的 profile。很多真实的 MIPS 用户态程序不在这个范围里,REA 会明确拒绝并说明是哪条约束,而不是瞎猜一个处理器。详见 ghidra-mips.md。所以本文选 OpenWrt 的 x86/64 目标:它的用户态是 x86-64 ELF,在同一台 Linux x64 机器上 Ghidra provider 就能处理。
另外要清楚固件这一步不做什么:不连设备、不需要 root、不挂载、不模拟、不执行提取出的程序。它只是纯静态解包。
前置条件
- REA 6.4.0 或更新版本。还没装的话先看REA 安装与接入。REA 需要 Node.js 22.x(>=22.19)、24.x(>=24.11)或 26+。
- 一台带 util-linux
prlimit的 Linux x64 主机。 - 自己按上游说明装好的 Binwalk 3.1.x 和/或 Unblob 26.6.x(Binwalk、Unblob)。REA 不负责安装它们、它们的依赖或系统包。
PATH上有debugfs(来自 e2fsprogs),Unblob 处理 ext2/3/4 时要用它。- 最后一步需要 Ghidra 12.1.x 和 64 位 JDK(当前 12.1 版本要求 JDK 21 及以上)。参见REA + Ghidra 原生二进制分析。
区域查看(Binwalk)和解包(Unblob)的依赖互相独立,只需要其中一个功能就只装一个。
第 1 步:告诉 REA Binwalk 和 Unblob 在哪
REA 不会去 PATH 里找这两个工具,你要在运行 REA 的 shell(或 MCP 服务器)环境里给出绝对路径:
export REA_BINWALK_COMMAND=/absolute/path/to/binwalk
export REA_UNBLOB_COMMAND=/absolute/path/to/unblob
# 只有 prlimit 不在 /usr/bin/prlimit 时才需要:
export REA_FIRMWARE_PRLIMIT_COMMAND=/absolute/path/to/prlimit
为什么要绝对路径: REA 会把实际执行的程序路径和它的摘要写进 Evidence,这样每个结果都能追溯到具体是哪个工具产出的。每次请求时它还会检查 --version 的输出:Binwalk 必须输出 binwalk <version>,Unblob 必须只输出版本号。同一 3.1.x / 26.6.x 系列的其他构建也能用,但 Evidence 里会多一条限制,说明解析器没有针对这个构建验证过。
如果你是通过 agent 使用 REA,这些变量必须对 MCP 服务器进程可见,只在交互 shell 里 export 是不够的。重新跑一次 setup 或修改注册配置里的环境变量,然后重启 agent。
第 2 步:下载开源镜像并校验
OpenWrt 的 x86/64 镜像附带 sha256sums 文件。写这篇文章时 downloads.openwrt.org 上最新的稳定版是 25.12.5,如果有更新版本就替换掉。
mkdir -p ~/fw/openwrt && cd ~/fw/openwrt
curl -LO https://downloads.openwrt.org/releases/25.12.5/targets/x86/64/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz
curl -LO https://downloads.openwrt.org/releases/25.12.5/targets/x86/64/sha256sums
grep ext4-rootfs.img.gz sha256sums | sha256sum -c -
为什么选 ext4 rootfs 镜像: 它就是 gzip 包着一个 ext4 文件系统,正好对上 REA 验证过的格式(gzip 和 ext4)。SquashFS 版本也可以试,但文档把 SquashFS 列为未验证,那边遇到奇怪的结果属于预期之内,不一定是你环境的问题。
输入文件要小于 128 MiB。REA 会先把输入复制到私有工作区并计算哈希,从不修改源文件。
第 3 步:用 Binwalk 查看区域
rea inspect-firmware-regions ~/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz
对应的 MCP 调用(你让 agent 看这个镜像时它会这么调)需要绝对路径:
{
"name": "inspect_firmware_regions",
"arguments": { "path": "/home/you/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz" }
}
为什么先做这一步: 区域查看开销小、只读,能告诉你 Binwalk 识别出哪些签名、在什么偏移,方便你决定是整包解还是只解一段。
结果怎么看:
- 偏移指的是原始文件里的字节位置。
reported_size是 Binwalk 自己给的数。REA 会标上size_basis=provider_reported_validation_unknown,因为 Binwalk 遇到未知长度时可能直接算到下一个签名或文件末尾。别把它当成验证过的分区边界。- 签名和置信度都不能证明分区有效、是否加密或运行时行为。
- Binwalk 的签名 UUID 只在本次运行内有效。跨次比较请用内容哈希和源范围。
第 4 步:用 Unblob 解包
目标目录必须还不存在,必须是绝对路径,而且父目录要存在:
rea extract-firmware \
~/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz \
~/fw/openwrt/extracted
为什么这么严格: REA 从不覆盖已有目录。它用独占、抗符号链接的方式写文件,写完再回读校验摘要。提交前失败的话,半成品会被回滚。
需要知道的默认值:
| 限制 | 默认值 |
|---|---|
| 总时限 | 120 秒 |
| 单进程地址空间 | 1 GiB(通过 prlimit),120 CPU 秒,不允许 core dump |
| 暂存预算 | 256 MiB、10,000 个条目 |
| 递归深度 | 3(允许 1–10) |
| JSON 报告大小 | 8 MiB,必须是合法 UTF-8 |
| 并发 | Binwalk 一个线程、Unblob 一个 worker |
OpenWrt 的 ext4 rootfs 会先解压成一个完整的文件系统镜像,再由 Unblob 遍历其中的文件,镜像较大时可能接近暂存预算。如果撞到暂存上限,就显式调大:
rea extract-firmware \
~/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz \
~/fw/openwrt/extracted-big \
--max-output-bytes 536870912 --max-output-files 20000
上面的字节数和文件数只是示例,按你的磁盘情况来定。
只解指定范围
Binwalk 显示出你关心的区域后,可以只解那一段。文档里的例子:
rea extract-firmware /path/to/firmware.bin /path/to/selected-output \
--offset 64 --length 4096 --max-depth 3
MCP 写法:
{
"name": "extract_firmware",
"arguments": {
"path": "/path/to/firmware.bin",
"output_directory": "/path/to/new-output",
"range": { "offset": 64, "length": 4096 },
"max_depth": 3
}
}
这样做的好处: 暂存预算占用小,而且该范围内的 chunk 会有确定的 root_file_range(已按你给的偏移校正)。
第 5 步:读懂解包 Evidence
结果里有原始路径和 SHA-256、Unblob 原始报告、执行程序的路径/摘要/版本、独立校验过 SHA-256 的普通文件路径、chunk、诊断信息、任务派生关系和限制说明。
容易误解的几点:
complete不等于“固件已被完整还原”。 它只表示 Unblob 按配置跑完了、没有报告问题。默认跳过规则和格式覆盖范围依然生效。partial表示存在未知或加密的 chunk、报告了解包问题、有未发布的链接或特殊文件,或者到达了深度上限。部分结果和缺失依赖的名字会保留在结果里,所以缺debugfs会直接显示出来,不会悄无声息地失败。- 符号链接只记录、不发布。 OpenWrt 的
/bin、/sbin里很多命令都是指向 BusyBox 的符号链接。你能在 Evidence 里看到它们,但输出目录里只有真正的目标文件(比如bin/busybox)。空目录、属主和可执行权限位也不会还原。 - 解压出来的子文件,原始字节范围未知。 任务链能告诉你哪个文件从哪来,但不能穿过 gzip 给出逐字节映射。
- 逻辑引用用
$input、$output或$output/<relative-path>表示。Unblob 的 blob ID 只在本次运行内有效,比较时用哈希。
Unblob 默认的 Landlock 沙箱和跳过规则保持开启。沙箱起不来时 REA 会报错,不会改用 --no-sandbox 重试。
第 6 步:把提取出的 ELF 交给 Ghidra
任意固件字节都不会被当作原生可执行文件接收。你需要从解包结果里挑一个 ELF 单独打开。具体路径以 Evidence 为准;Unblob 会把输出嵌套在 _extract 目录下,下面的路径仅为示意。
export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC
rea doctor --provider ghidra --json
BIN=~/fw/openwrt/extracted/<path-from-evidence>/usr/sbin/uhttpd
rea analyze "$BIN" --provider ghidra --json
rea search "$BIN" "cgi" --provider ghidra --json
rea function "$BIN" main --provider ghidra --json
为什么先跑 rea doctor: 它不启动分析,只检查 Ghidra 版本、analyzeHeadless、JDK 版本范围和原生反编译器,比等第一次查询失败再排查快得多。
在 MCP 里,agent 会用提取出的路径加 provider_id: "ghidra" 调 open_binary,然后用 analyze_function、inspect_native_api 等原生工具:
{
"name": "open_binary",
"arguments": {
"path": "/path/from/extraction/usr/sbin/uhttpd",
"provider_id": "ghidra"
}
}
第一次 Ghidra 查询会触发导入和自动分析,可能比较慢。大文件超时的话,在服务器环境里调大 REA_GHIDRA_STARTUP_TIMEOUT_MS(默认 330,000 毫秒)。
一个比较好用的 agent 提示词:
Extract /home/you/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz
into /home/you/fw/openwrt/extracted with REA. List the ELF files under usr/sbin,
then open uhttpd with Ghidra and explain how it dispatches CGI requests.
Cite function addresses and state any limitations from the Evidence.
用中文提问也可以,比如“用 REA 解包这个镜像,列出 usr/sbin 下的 ELF,再用 Ghidra 打开 uhttpd,解释它怎么分发 CGI 请求,并给出函数地址和 Evidence 里的限制”。
常见问题和排错
版本输出不被接受。 REA 要求 Binwalk 输出 binwalk <version>、Unblob 只输出版本号。包了一层会多打印内容的脚本会过不了检查,请直接指向真正的可执行文件。
找不到 prlimit。 安装 util-linux,或者把 REA_FIRMWARE_PRLIMIT_COMMAND 设成它的绝对路径。查看和解包都需要它。
结果是 partial,提示缺 extractor。 Unblob 需要每种格式对应的外部工具出现在它从 REA 继承来的 PATH 上,比如 ext4 要 debugfs。要加到 MCP 服务器的环境里,而不只是你的 shell。
输出被截断的报错。 每条命令 REA 最多保留 8 MiB 的 stdout/stderr,超出会明确报错停止。用 --offset/--length 缩小输入,或调小 --max-depth。
时限或内存限制。 文档里的策略写死了 120 秒时限和单进程 1 GiB 地址空间。镜像太大就先解一个小范围。
“目标目录已存在”。 这是设计如此,每次运行换一个新目录名。
Ghidra 拒绝 MIPS 程序。 拿报错里的具体约束对照 ghidra-mips.md。ELF64 MIPS、n32/n64、microMIPS 以及大多数共享库形态目前都不在支持范围内,REA 会直接说明而不是乱猜处理器。
更新后工具对不上。 固件工具已在 npm 6.4.0 里;如果你要用 main 上更新的功能,请查看版本可用性说明。
下一步
- 想在 Ghidra 这一侧深入,看REA + Ghidra 原生二进制分析。
- 想保存结果、对比两个固件版本,看Evidence、快照与版本对比。
- 想先拿小目标练手,看用 REA + AI agent 解 CTF 逆向题。
- 在 REA 和其他逆向 MCP 之间做选择,看REA 与 GhidraMCP、ida-pro-mcp 等的对比。