rea-reverse

Free Native Binary Analysis with REA and Ghidra, Step by Step

Written for REA 6.4.0Updated 8 min read
On this page

Ghidra is a free, open-source reverse-engineering suite. REA, the open-source project by morluto, can drive it headlessly as a provider: REA imports your binary into a throwaway Ghidra project, waits for auto-analysis, and then answers focused questions (search, decompile, references, traces) with Evidence attached. You get Ghidra’s decompiler without opening the Ghidra GUI, from either a terminal or an AI coding agent.

This guide is for developers who already have REA installed (if not, start with Install REA and connect your agent) and want a repeatable native workflow at zero licence cost. We will:

  1. configure Ghidra as an REA provider,
  2. write and compile a tiny C program to use as the target,
  3. walk through analyze → search → decompile → xrefs → trace, showing both the CLI command and the equivalent question to ask your agent at every step.

The target is a program you write and compile yourself, so there is no question about whether you are allowed to analyze it.

Prerequisites

REA’s Ghidra provider has specific requirements. These come from the installation guide, and REA checks every one of them:

  • Host: Linux x64/arm64 or macOS x64/arm64. Windows x64 is an experimental, read-only P0 that only admits native x86/x86-64 PE files on local NTFS volumes (Windows Ghidra guide).
  • Ghidra 12.1.x, already extracted. REA never downloads or installs Ghidra. The bridge is verified with Ghidra 12.1.4.
  • A 64-bit full JDK 21 or newer (with javac), matching the range the Ghidra installation declares.
  • Ghidra’s native decompiler for your host architecture under Ghidra/Features/Decompiler/os/<platform>/. Official archives may lack it on some platforms (macOS and Linux arm64 in particular); see troubleshooting below.
  • REA 6.4.0 or later and Node.js 22.19+/24.11+/26+.
  • A C compiler (cc, clang or gcc) to build the sample target.

Step 1: Point REA at Ghidra

Export absolute paths to your existing installations, then let REA check them:

export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC
export JAVA_HOME=/absolute/path/to/jdk-21   # optional if java/javac are on PATH
rea doctor --provider ghidra --json

Why the scoped --provider ghidra: a plain rea doctor audits every agent and engine, and can report “unhealthy” because of something unrelated, such as a missing Hopper. The scoped report’s status reflects only Ghidra. It validates the Ghidra version, support/analyzeHeadless, Java version and bitness, javac and the native decompiler. Fix whatever it names before moving on.

Then run setup so your agents launch REA with the same settings:

rea setup

Setup shows the exact GHIDRA_INSTALL_DIR/JAVA_HOME entries it will write into each agent’s REA registration, and writes them only after you approve. This step matters because a GUI agent such as Cursor does not inherit the variables you just exported in a terminal. Restart your agent afterwards.

Finally confirm REA sees Ghidra as a provider:

rea providers --json

If you also have Hopper or IDA configured, more than one provider may support the same file. Add --provider ghidra to each command (as below), or set REA_ANALYSIS_PROVIDER=ghidra once.

Step 2: Build a small target

Save this as vault.c. It is a deliberately tiny “passphrase vault”: one string compare loop hidden behind a byte-mixing function, two output strings and a usage message. That is just enough structure to make every analysis step meaningful.

/* vault.c - a tiny practice target for REA + Ghidra */
#include <stdio.h>
#include <string.h>

#define KEY_LEN 8

/* "rea-demo" after mix_byte() */
static const unsigned char kExpected[KEY_LEN] = {
    0x28, 0x42, 0x41, 0x80, 0x4A, 0x4E, 0x49, 0x4A
};

static unsigned char mix_byte(unsigned char c, int i) {
    return (unsigned char)((c ^ 0x5A) + i * 3);
}

int check_passphrase(const char *input) {
    if (strlen(input) != KEY_LEN)
        return 0;
    for (int i = 0; i < KEY_LEN; i++) {
        if (mix_byte((unsigned char)input[i], i) != kExpected[i])
            return 0;
    }
    return 1;
}

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s <passphrase>\n", argv[0]);
        return 2;
    }
    if (check_passphrase(argv[1])) {
        puts("Vault opened");
        return 0;
    }
    puts("Wrong passphrase");
    return 1;
}

Build it without optimisation and with symbols, so the function boundaries in Ghidra match the source:

cc -O0 -g -o vault vault.c
BIN="$PWD/vault"

Two notes on the build:

  • With -O2, the compiler will usually inline mix_byte into check_passphrase. That is a good second exercise: rebuild with -O2 -s (stripped) and repeat the walkthrough to see how much harder it gets without names.
  • On macOS the binary is Mach-O and C symbols carry a leading underscore (_check_passphrase). REA’s docs advise using names exactly as the inventory returns them, including platform prefixes. The commands below use the Linux/ELF spelling; adjust if your search results show the underscore.

CLI sessions vs agent sessions

Each rea CLI command is a separate process: it imports the binary into Ghidra, waits for default auto-analysis, answers one question and closes the session. That is quick for a file this size, but on a large binary the import alone can take minutes. An agent using REA over MCP opens the binary once (open_binary) and reuses the analyzed program for every follow-up question. Use the CLI for scripting and quick checks, and the agent for real investigations.

Step 3: Analyze (overview)

CLI:

rea analyze "$BIN" --provider ghidra --json > vault-overview.json

analyze and inspect run the same native overview workflow (binary_overview in MCP). Expect the result to describe the program: format and architecture, functions, and the Evidence record behind it. The default terminal format is TOON; --json is better when you save output or pipe it to jq.

Agent:

Use REA with the Ghidra provider to open /absolute/path/to/vault and give me an
overview: format, architecture, the functions it defines, and anything that looks
like input validation.

Mentioning “Ghidra provider” is useful if several engines are configured; the agent will pass provider_id: "ghidra" to open_binary. The first Ghidra-backed query in a session is the slow one, because that is when import and auto-analysis actually happen.

Step 4: Search strings and procedures

The fastest way into an unfamiliar binary is usually a string the user can see. Our program prints Wrong passphrase.

CLI:

rea search "$BIN" "passphrase" --provider ghidra --json
rea search "$BIN" check --kind procedures --provider ghidra --json

search defaults to --kind strings and literal, case-insensitive matching; add --case-sensitive for exact letter case. --kind procedures searches function names instead, and --mode regex switches to a Java-regex pattern on Ghidra. Results are complete: there is no paging or result limit.

From the string search, note the address of Wrong passphrase. You will need it for xrefs. From the procedure search, note the exact name of check_passphrase as Ghidra reports it.

One Ghidra-specific caveat from the provider docs: string search only covers what Ghidra defined as string data. Packed or undefined literals (Rust strings are a known case) may be missing, so an empty result does not prove the text is absent.

Agent:

Search the vault binary for strings containing "passphrase" and for procedures
whose names contain "check". Give me the exact addresses and names.

Step 5: Decompile

Now read the validation function as C-like pseudocode.

CLI:

rea decompile "$BIN" check_passphrase --provider ghidra --json
rea function "$BIN" check_passphrase --provider ghidra --json
rea instructions "$BIN" check_passphrase --provider ghidra --json

The three commands answer different questions:

  • decompile returns Ghidra’s pseudocode for one procedure.
  • function returns a function dossier: pseudocode plus assembly, callers and callees, references, control-flow blocks and bounded p-code value flow, all with Evidence.
  • instructions returns every raw instruction without invoking the decompiler, which is useful when pseudocode hides something.

You can pass a name or an address. On Ghidra, an explicit 0x prefix selects an address; otherwise an exact symbol name takes precedence. Names that start with a dash need the equals form, for example --procedure=-name.

At -O0, expect pseudocode with the same shape as the source: a strlen check against 8, a loop, a call to mix_byte, and a comparison against a global byte array. Ghidra’s output will use its own variable names and types. Treat it as an analysis result, not the original source; REA’s docs are explicit that provider pseudocode is never treated as original source.

Agent:

Decompile check_passphrase and mix_byte. Explain what input passes the check,
and show the instructions or data bytes that support each step of your reasoning.

That last sentence matters. The bytes in kExpected live in data, not code. A good agent reads them (REA has a read_bytes operation) instead of guessing, then inverts mix_byte to recover rea-demo. Because you have the source, you can check its answer.

Step 6: Xrefs (who uses this?)

Cross-references answer “where is this used?”. Use the string address from step 4:

CLI:

rea xrefs "$BIN" 0xSTRING_ADDRESS --provider ghidra --json
rea xrefs "$BIN" check_passphrase --provider ghidra --json

Replace 0xSTRING_ADDRESS with the address your search returned. xrefs accepts an address or a symbol name (--address=<value> for values that start with a dash) and returns the source addresses that reference it. The first command should lead back into main; the second should show the call from main to check_passphrase.

On Ghidra, reference edges keep their exact type: call, jump, data read, write, indirect, computed and so on. Callers and callees include only resolved functions. Computed calls with no known target stay unknown rather than being guessed, which is what you want when you later analyze code with function pointers.

Agent:

Which functions reference the "Wrong passphrase" string, and who calls
check_passphrase? Resolve each reference to its containing function.

Step 7: Trace a feature

trace combines the previous steps. Give it a literal feature query and it finds every matching string and procedure, follows their xrefs and resolves each to its containing procedure, returning observations plus residual unknowns.

CLI:

rea trace "$BIN" "Wrong passphrase" --provider ghidra --json

The query is matched literally, spaces included (MCP tool trace_feature). Use --query=<text> if the text begins with a dash.

For value-level questions, Ghidra also supports a bounded static data-flow trace built from decompiler p-code across resolved calls:

rea trace-native-values "$BIN" check_passphrase --provider ghidra --json

This one is useful for questions like “which constant ends up in this comparison?”, but read its limitations: alias effects, persistent state and missing bindings remain unknown, and it does not establish runtime behaviour.

Agent:

Trace the "Wrong passphrase" feature: from the string to the code that prints it,
back to the decision that chooses between "Vault opened" and "Wrong passphrase".
Show the call path from main and list anything you could not resolve.

Behind the scenes the agent can combine trace_feature, xrefs, analyze_function and trace_call_path in one session. On Linux and macOS it may also rename functions or add comments through annotate_native_function. These edits live only in the ephemeral Ghidra database: your executable bytes never change, and the edits are discarded when the session closes.

Saving work: snapshots

Because each CLI call re-imports the binary, repeated scripted queries benefit from a snapshot:

rea analyze "$BIN" --provider ghidra --snapshot /absolute/path/to/analysis/vault.json

Repeat the same query with the same snapshot and REA returns the saved result without starting Ghidra. Snapshots cache exact results (same bytes, operation, parameters, provider and settings); they are not a saved Ghidra database, so a new question still starts the provider. See Evidence, snapshots and comparisons for the full workflow.

Troubleshooting

native_decompiler reported missing on macOS. The archive may ship decompiler sources without the macOS executable. The installation guide gives a build recipe using Xcode Command Line Tools, make and bison. On Apple Silicon:

cd "$GHIDRA_INSTALL_DIR/Ghidra/Features/Decompiler/src/decompile/cpp"
make ghidra_opt ARCH_TYPE="-arch arm64" OSDIR=mac_arm_64
decompiler_dir="$GHIDRA_INSTALL_DIR/Ghidra/Features/Decompiler/os/mac_arm_64"
mkdir -p "$decompiler_dir"
test ! -e "$decompiler_dir/decompile" && test ! -L "$decompiler_dir/decompile" &&
  install -m 755 ghidra_opt "$decompiler_dir/decompile"
rea doctor --json

On Intel Macs, use plain make ghidra_opt and the mac_x86_64 directory. REA never builds this component for you.

The agent times out on the first question. REA allows Ghidra 330,000 ms by default for import, auto-analysis and bridge readiness, but your MCP client may cancel a single tool call sooner. For large binaries, set REA_GHIDRA_STARTUP_TIMEOUT_MS in the REA server’s environment and raise the client’s tool timeout if it has one. See first-query deadlines and recovery.

Out of memory or a slow small machine. REA sets Ghidra’s JVM heap from GHIDRA_HEADLESS_MAXMEM (default 2G). export GHIDRA_HEADLESS_MAXMEM=512M was verified on small targets. For an agent, the variable must be in the REA server entry’s environment, not just your shell.

ambiguous provider error. More than one engine supports the file. Choose from details.candidate_ids with --provider ghidra, or provider_id in MCP.

Wrong architecture or language. Ghidra detects the language from the header. For unusual images, set REA_GHIDRA_LANGUAGE_ID (for example x86:LE:32:default). The installation guide also covers analysis seeds and disabling a stalled analyzer.

artifact_changed. You rebuilt vault while a session was open. Reopen the target so REA picks up the new bytes and digest.

Responsible use

Practise on binaries you built, open-source programs, CTF challenges or the targets in REA’s official showcases. Before analyzing anything else, make sure you are authorized, and read the project’s DISCLAIMER.md.

Next steps