rea-reverse

Firmware Analysis with REA: Binwalk, Unblob and Ghidra

Written for REA 6.4.0Updated 9 min read
On this page

Short version: REA, the open-source project by morluto, does not ship its own firmware unpacker. It wraps the Binwalk and Unblob executables you already have, runs them under fixed resource limits, and returns the results as Evidence: region offsets, extracted file paths, SHA-256 digests, and a list of limitations. You then pick one extracted ELF and open it with the Ghidra provider like any other native binary. The firmware step is verified only on Linux x64, so plan to work on a Linux x64 host or VM.

This guide is for developers and security researchers who want to take an open-source firmware image from a single blob to decompiled functions without writing glue scripts. You will:

  1. Check that your host and tools meet REA’s firmware requirements.
  2. Survey an OpenWrt image with Binwalk (inspect-firmware-regions).
  3. Extract it with Unblob (extract-firmware), including a bounded sub-range.
  4. Read the Evidence correctly, including what “complete” does and does not mean.
  5. Hand an extracted ELF to Ghidra through rea analyze or the open_binary MCP tool.

All commands and limits below come from REA’s firmware analysis guide for REA 6.4.0. Where this article shows output, it is labeled illustrative.

Only analyze firmware you are allowed to examine: open-source builds such as OpenWrt, images you built yourself, or CTF targets. See REA’s DISCLAIMER.md.

Platform limits first

Read this before installing anything. REA’s firmware support has a narrower scope than most of its other features.

Component Supported scope (REA 6.4.0 docs)
Firmware inspection and extraction Verified on Linux x64. Binwalk/Unblob are listed as “on Linux” in the README.
Binwalk 3.1.x accepted; parser verified with 3.1.0
Unblob 26.6.x accepted; parser verified with 26.6.4
Resource limiting Requires util-linux prlimit (normally /usr/bin/prlimit)
Ghidra provider (next step) Linux x64/arm64, macOS x64/arm64; Windows x64 is an experimental, PE-only P0
Verified formats Generated gzip, USTAR and ext4 with a Linux x64 ELF child
Not verified SquashFS, UBIFS, proprietary firmware, other hosts and architectures

Two consequences matter in practice:

  • Don’t expect the firmware tools to work on macOS or Windows. prlimit is a util-linux tool, and the verification lane runs on Linux x64. If you are on a Mac, use a Linux x64 VM or a remote box for the whole workflow.
  • Most consumer router firmware is MIPS or ARM. REA’s Ghidra lane for MIPS is deliberately narrow: ELF32 ET_EXEC/ET_REL with explicit o32 and MIPS32r2 flags, plus a separate bounded profile for MIPS16 shared images. Many real MIPS userland binaries fall outside that boundary, and REA refuses them with a specific constraint rather than guessing. See ghidra-mips.md. That is why this walkthrough uses OpenWrt’s x86/64 target: its userland is x86-64 ELF, which the Ghidra provider handles on the same Linux x64 host.

Also note what the firmware step does not do: no device access, no root, no mounting, no emulation, and no execution of extracted programs. It is pure static unpacking.

Prerequisites

  • REA 6.4.0 or later. Follow Getting started with REA if you have not installed it. REA needs Node.js 22.x (>=22.19), 24.x (>=24.11) or 26+.
  • A Linux x64 host with util-linux prlimit.
  • Binwalk 3.1.x and/or Unblob 26.6.x, installed by you using the upstream instructions (Binwalk, Unblob). REA does not install them, their dependencies, or system packages.
  • debugfs (from e2fsprogs) on your PATH, because Unblob calls it for ext2/3/4 filesystems.
  • Ghidra 12.1.x with a 64-bit JDK (21 or newer for current 12.1 releases), for the final step. See REA + Ghidra for native binaries.

Inspection (Binwalk) and extraction (Unblob) have independent prerequisites. You can set up only one of them if that is all you need.

Step 1: Point REA at your Binwalk and Unblob executables

REA does not search PATH for these tools. You give it absolute paths in the environment of the shell (or MCP server) that runs REA:

export REA_BINWALK_COMMAND=/absolute/path/to/binwalk
export REA_UNBLOB_COMMAND=/absolute/path/to/unblob
# Only if prlimit is not at /usr/bin/prlimit:
export REA_FIRMWARE_PRLIMIT_COMMAND=/absolute/path/to/prlimit

Why absolute paths: REA records the actual executable path and its digest in the Evidence, so a result can be traced back to the exact tool that produced it. On each request it also checks the --version banner: Binwalk must print binwalk <version>, Unblob must print the version alone. A different build on the same 3.1.x / 26.6.x line is accepted, but the Evidence will carry a limitation saying the parser was not verified against that build.

If you use REA through an agent, these variables must be visible to the MCP server process, not only to your interactive shell. Re-run setup or edit the registration’s environment, then restart the agent.

Step 2: Download an open-source image and verify it

OpenWrt publishes x86/64 images with a sha256sums file. At the time of writing the latest stable release listed on downloads.openwrt.org is 25.12.5; substitute a newer version if one exists.

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 -

Why the ext4 rootfs image: it is gzip wrapping an ext4 filesystem, which matches the formats REA’s verification actually covers (gzip and ext4). The SquashFS variants are fine to try, but REA’s docs list SquashFS as unverified, so treat any oddities there as expected rather than as a bug in your setup.

Keep the input under 128 MiB. REA copies and hashes the input into a private workspace before analysis and never modifies the source file.

Step 3: Survey regions with Binwalk

rea inspect-firmware-regions ~/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz

The equivalent MCP call, which your agent will make when you ask it to look at the image, takes an absolute path:

{
  "name": "inspect_firmware_regions",
  "arguments": { "path": "/home/you/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz" }
}

Why start here: region inspection is cheap and read-only. It tells you what signatures Binwalk sees and at what offsets, which helps decide whether to extract the whole file or only a range.

How to read the result:

  • Offsets refer to bytes in the original file.
  • reported_size is Binwalk’s own number. REA tags it with size_basis=provider_reported_validation_unknown, because Binwalk can replace an unknown length with “up to the next signature or EOF”. Don’t treat a size as a validated partition boundary.
  • A signature and its confidence value do not prove a valid partition, encryption, or runtime behavior.
  • Binwalk signature UUIDs are local to that run. To compare across runs, use content hashes and source ranges instead.

Step 4: Extract with Unblob

The destination directory must not exist yet, must be an absolute path, and its parent must exist:

rea extract-firmware \
  ~/fw/openwrt/openwrt-25.12.5-x86-64-generic-ext4-rootfs.img.gz \
  ~/fw/openwrt/extracted

Why the strict destination rule: REA never overwrites an existing destination. It publishes files with exclusive, symlink-resistant writes and re-reads each digest after writing. If extraction fails before commit, the partial output is rolled back.

Defaults you should know about:

Limit Default
Wall-clock deadline 120 seconds
Per-process address space 1 GiB (via prlimit), 120 CPU seconds, no core dumps
Staging budget 256 MiB and 10,000 entries
Recursion depth 3 (allowed 1-10)
JSON report size 8 MiB, valid UTF-8
Workers One Binwalk thread, one Unblob worker

The OpenWrt ext4 rootfs is first decompressed into a full filesystem image before Unblob walks it, and a large image can approach the staging budget. If you hit the staging limit, raise it explicitly:

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

The byte and file values above are examples; pick numbers that fit your disk.

Extracting only a range

When Binwalk shows a region you care about, extract only that slice. The docs’ example:

rea extract-firmware /path/to/firmware.bin /path/to/selected-output \
  --offset 64 --length 4096 --max-depth 3

MCP form:

{
  "name": "extract_firmware",
  "arguments": {
    "path": "/path/to/firmware.bin",
    "output_directory": "/path/to/new-output",
    "range": { "offset": 64, "length": 4096 },
    "max_depth": 3
  }
}

Why bother: a selected range keeps the staging budget small and gives you a known root_file_range for chunks in that range, adjusted by the offset you selected.

Step 5: Read the extraction Evidence

The result includes the original path and SHA-256, the raw Unblob report, the executable path, digest and version, regular file paths with independently verified SHA-256 values, chunks, diagnostics, task derivations, and limitations.

Points that trip people up:

  • complete is not “the firmware is fully reconstructed”. It means Unblob finished its configured extraction without reported problems. Default skip rules and format coverage still apply.
  • partial marks unknown or encrypted chunks, reported extraction problems, unpublished links or special files, or a reached depth limit. Partial results and missing-dependency names are kept inline, so a missing debugfs shows up there instead of failing silently.
  • Symlinks are recorded but not published. On OpenWrt, many commands in /bin and /sbin are BusyBox symlinks. You will see them in the Evidence, but only the real target file (for example bin/busybox) exists in the output tree. Empty directories, ownership, and executable bits are not reproduced either.
  • Decompressed children have unknown original byte ranges. Task lineage tells you which file came from which, but not a byte-for-byte mapping through gzip.
  • Logical references use $input, $output, or $output/<relative-path>. Unblob blob IDs are local to the run; compare by hash.

Unblob’s default Landlock sandbox and skip rules stay on. If the sandbox cannot start, REA reports the error and does not retry with --no-sandbox.

Step 6: Hand an extracted ELF to Ghidra

Arbitrary firmware bytes are never admitted as a native executable. You choose an ELF from the extraction result and open it separately. Take the exact path from the Evidence; Unblob nests output under _extract directories, so the path below is illustrative.

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

Why rea doctor first: it checks the Ghidra version, analyzeHeadless, the JDK range and the native decompiler without starting an analysis, which is faster to debug than a failed first query.

In MCP, the agent calls open_binary with the extracted path and provider_id: "ghidra", then uses analyze_function, inspect_native_api and the other native tools:

{
  "name": "open_binary",
  "arguments": {
    "path": "/path/from/extraction/usr/sbin/uhttpd",
    "provider_id": "ghidra"
  }
}

The first Ghidra-backed query triggers import and auto-analysis, which can take a while. If a large binary times out, raise REA_GHIDRA_STARTUP_TIMEOUT_MS in the server environment (the default is 330,000 ms).

A prompt that works well with an 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.

Troubleshooting

Version banner rejected. REA expects binwalk <version> from Binwalk and a bare version from Unblob. A wrapper script that prints extra lines will fail the check. Point the variable directly at the real executable.

prlimit not found. Install util-linux or set REA_FIRMWARE_PRLIMIT_COMMAND to its absolute path. Inspection and extraction both need it.

Extraction is partial with a missing extractor. Unblob needs the external tool for each format on the PATH it inherits from REA, for example debugfs for ext4. Add it to the MCP server’s environment, not just your shell.

Truncated-output error. REA keeps at most 8 MiB of combined stdout/stderr per command. Hitting that stops the analysis explicitly. Narrow the input with --offset/--length or reduce --max-depth.

Deadline or memory limits. The 120-second deadline and 1 GiB per-process address space are fixed in the documented policy. For a large image, extract a smaller range first.

“Destination exists.” By design. Choose a new directory name for each run.

Ghidra refuses a MIPS binary. Check the specific constraint in the error against ghidra-mips.md. ELF64 MIPS, n32/n64, microMIPS and most shared-object shapes are outside the current lane. REA reports this instead of guessing a processor.

Wrong tools after an update. Firmware tools are in the 6.4.0 npm release, but if you rely on something newer from main, check release availability.

Next steps