Lab Journal: Setting Up REA + Ghidra 12.1.4 on Debian — and What it Taught me about AWS and Black Hat Python

I set up REA, an open-source reverse-engineering tool, to drive headless Ghidra 12.1.4 on Debian 13. It took three Java versions, one verified download and a six-line shell function. Along the way it taught me S3 presigned URLs, ELF internals and a few habits I'll keep.

TL;DR

On October 6, 2026, I got REA (“Reverse Engineer Anything”) running headless Ghidra 12.1.4 on my Debian 13 desktop and used it to analyze my first Linux binary. It took one installer, three Java versions, one verified 543 MB download, and a six-line shell function.

The tool works, but the real value was the path to get there. Almost every snag taught me something I’m also studying for the AWS DVA-C02 exam or working through in Black Hat Python. This Lab Journal walks through what I did, why each step mattered, and where those connections showed up.

What REA is, and why I tried it

REA is an open-source (MIT-licensed) command-line tool and MCP server, published on npm as rea-agents and on GitHub as morluto/rea. It gives you, or an AI coding agent, one consistent set of commands for investigating software, from “what does this app do?” down to decompiled functions.

REA doesn’t do the heavy analysis itself. It’s a coordinator: it drives real reverse-engineering engines behind the scenes, mainly Ghidra (the NSA’s free disassembler and decompiler) and Hopper (a commercial Mac/Linux disassembler). You ask REA a question; it picks an engine, runs it, and hands back a structured report.

I wanted it for two reasons:

  • Learning. Reverse engineering has a steep start. You have to pick a tool, learn its interface, and decide what to look at next. REA standardizes that first layer.
  • Agents. REA can plug into Claude Code, Cursor, VS Code and others. I deliberately chose none during setup. I wanted to understand the tool by hand before letting an agent drive it.

How the pieces fit together

When I type one rea command, it passes through four layers before Ghidra ever touches the file. Each layer was a separate thing I had to get right.

Figure: One rea command passes through four layers before Ghidra reads the file.

The highlighted path is the one my first analysis took. Ghidra and REA’s built-in providers work on Debian; Hopper isn’t installed, and the macOS tools can’t run on Linux at all. The report travels back up the same chain to my terminal.

Step by step: what I actually did

Each step below is a command I ran, followed by why it mattered.

  1. Ran the installer:npx rea-agents setup
    • npx downloads an npm package temporarily and runs it without a permanent install. npm asked permission first, then REA asked again before changing anything, with “No” as the default.
    • I installed only the guided REA skill (instructions an agent can read) and connected no agents.
  2. Checked my Node.js:node --version → v22.23.2
    • REA needs Node 22 or newer.
    • which node showed it comes from mise, a version manager that uses small “shim” programs to route node to the right version. My (base) prompt is conda, but conda wasn’t supplying Node.
  3. Asked REA what works here:npx rea-agents capabilities
    • Every macOS-only feature reported unsupported_host. The Ghidra engine reported two problems: no GHIDRA_INSTALL_DIR set, and an unsupported Java.
  4. Found my existing Ghidra installs:find ~ /opt /usr/local /usr/share -maxdepth 3 -iname "*ghidra*" 2>/dev/null
    • I had two copies of Ghidra 12.1.2, one in ~/Downloads and one in /opt/tools/ghidra. grep application.version on each copy’s application.properties confirmed the real version.
  5. Ran the health check with temporary settings:GHIDRA_INSTALL_DIR=... JAVA_HOME=... npx rea-agents doctor
    • Putting NAME=value before a command sets it for that one command only. It’s a safe way to experiment.
    • Doctor said exactly what was wrong: Ghidra 12.1.2 was too old (REA wants 12.1.4) and my Java was wrong (more on that below).
  6. Downloaded Ghidra 12.1.4 from the terminal
    • Queried GitHub’s API with curl to get the exact filename and its published SHA-256 digest.
    • Downloaded with wget.
    • Verified with sha256sum -c, which printed ghidra_12.1.4_PUBLIC_20260921.zip: OK.
  7. Extracted it next to the old copy:unzip -q ... -d /opt/tools
    • ls -ld /opt/tools showed I own the folder, so no sudo was needed. The old 12.1.2 stayed as a fallback.
  8. Made it permanent with a shell function in ~/.bashrc
# REA reverse-engineering CLI: uses Java 21 + Ghidra 12.1.4 only for REA
rea() {
  GHIDRA_INSTALL_DIR=/opt/tools/ghidra_12.1.4_PUBLIC \
  JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 \
  PATH=/usr/lib/jvm/java-21-openjdk-amd64/bin:$PATH \
  npx rea-agents@4.0.1 "$@"
}

The function applies Java 21 and Ghidra 12.1.4 only while REA runs. My system default Java stayed on Corretto 26, which java -version confirmed afterward. Pinning @4.0.1 means a future REA release can’t silently demand a newer Ghidra and break my setup.

The final rea doctor showed every Ghidra check healthy and available: true for the first time.

The Java detour: 26, then 25, then 21

REA accepts exactly one Java major version for Ghidra: 21. Getting there took three tries.

AttemptJavaWhy I tried itREA’s verdict
1Amazon Corretto 26My system defaultunsupported_version
2Amazon Corretto 25Ghidra 12.1’s own docs list Java 25unsupported_version: “Update JAVA_HOME to the root of a 64-bit JDK 21 installation”
3Debian OpenJDK 21What REA’s doctor asked forhealthy

The lesson: a wrapper tool can be stricter than the tool it wraps. Ghidra itself runs on 21 or newer, but REA pins 21 because that’s what its developers test against. Attempt 2 was a reasonable guess based on Ghidra’s documentation, but the authoritative answer was in REA’s own error message all along. Read the remediation line first.

I didn’t uninstall anything. /usr/lib/jvm/ now holds Java 8, 21, 25 and 26 side by side, and that’s normal. Debian’s alternatives system decides the default, and the rea function overrides it for REA alone.

First analysis: what’s inside /usr/bin/true

My first target was a copy of /usr/bin/true, a program whose only job is to exit successfully. Ghidra found 255 functions, 157 strings and 32 sections in it.

mkdir -p ~/rea-lab
cp /usr/bin/true ~/rea-lab/
rea analyze ~/rea-lab/true --provider ghidra --format md

I analyzed a copy in a lab folder, not the system file. REA reported import_mode: ephemeral-read-only, meaning Ghidra opened the file read-only in a throwaway project that was deleted afterward.

Why 255 functions in a program that “does nothing”? Debian’s true comes from GNU coreutils, so it still supports --help, --version and translations. It also carries startup and shutdown code the compiler adds to every program. Even trivial binaries are full of hidden machinery.

The sections worth knowing, since they appear in every Linux ELF binary:

SectionSize in trueWhat it holds
.text17,118 bytesThe machine code itself
.rodata2,895 bytesRead-only data, mostly text strings
.dynsym / .dynstr1,464 / 791 bytesNames of functions imported from shared libraries like libc
.plt / .got720 / 416 bytesThe lookup table used to find those library functions at runtime
.data128 bytesGlobal variables with starting values
.bss448 bytesGlobal variables that start at zero

The report also included the file’s SHA-256 (b381cc8c…). Running sha256sum ~/rea-lab/true and matching it proves the report belongs to the exact file I meant to analyze.

How this connects to Black Hat Python

Black Hat Python teaches offensive tooling by building it from scratch in Python. REA is the other side of that coin: understanding what compiled software actually does. Five concrete overlaps showed up in one afternoon.

What I did with REAWhere it shows up in BHPThe shared skill
Ran file to identify the binary as ELF from its header bytesDecoding raw IP/ICMP packet headers with struct and ctypesFormats are identified by known bytes at known offsets, not by names
Read .plt / .got in the section tableMonitoring and intercepting what processes doHooking often works by changing what a program’s lookup table points to
Read drwxr-xr-x on /opt/toolsHunting privilege-escalation paths on a targetA world-writable tools folder (rwxrwxrwx) would be a red flag
Ran Ghidra headless instead of the GUIAutomating every attack step in a scriptSecurity work scales when tools run without a human clicking
Analyzed a copy, in a read-only throwaway projectWorking inside a lab, never on live systemsIsolation first, before anything gets interesting

The biggest connection is mindset. BHP keeps asking “how would I build this?” Reverse engineering asks “how did someone else build this?” You need both to understand malware, and both to write tools that hold up.

How this connects to AWS DVA-C02

None of this touched AWS directly, yet five exam topics showed up in the terminal output.

What happened on my desktopThe DVA-C02 concept
GitHub’s download answered 302 Found and redirected to a long URL with se= (expiry) and sig= (signature)S3 presigned URLs: temporary, signed access to a private object without making the bucket public
I queried api.github.com with curl and got JSON backAWS service APIs: every AWS service is a REST API, and the AWS CLI and SDKs are signed API calls underneath
sha256sum -c confirmed the download byte for byteIntegrity checks: S3 can verify uploads with checksums so the stored object matches what was sent
Amazon Corretto showed up as one of my JDKsLambda Java runtimes are built on Amazon Corretto
The rea function scoped environment variables to one commandLambda environment variables: configuration injected per function, not globally

The presigned URL was the clearest. If an exam question asks how to give users temporary download access to a private S3 object, I’ve now seen the answer pattern working, not just read about it.

Lessons learned and security habits

  • Read the tool’s own error message first. REA’s doctor named the exact Java and Ghidra versions it wanted. Outside documentation was a reasonable guess; the remediation line was the answer.
  • Verify every download. curl the API for the published digest, then sha256sum -c. It costs ten seconds and proves nothing was swapped in transit.
  • Scope configuration as narrowly as possible. A global export JAVA_HOME would have switched every Java program on my machine. A shell function changed only REA. It’s the same least-privilege thinking as IAM, applied to environment variables.
  • Pin versions of tools that pin their dependencies. REA demands an exact Ghidra build, so I pinned REA itself to @4.0.1. I’ll upgrade both together, on purpose.
  • Don’t paste several commands when one might ask a question. npx asked for approval, and the next pasted line got swallowed as the answer. One command at a time when a prompt is possible.
  • “Unsupported” isn’t “broken,” but it is a risk. REA officially supports Ubuntu 24.04+, not Debian 13. It works here, but if something breaks, I’m off the tested path.
  • Never analyze unknown binaries on your daily machine. /usr/bin/true was safe. Anything from the wild goes into an isolated VM in my cyber range.

What’s next

The overview gave counts; next I’ll look inside. That means listing the 157 strings in true and decompiling its main function into C-like pseudocode with REA’s Ghidra-backed commands.

  • Run rea --help to map REA’s 69 CLI commands
  • List and search strings in ~/rea-lab/true
  • Decompile main and compare it with the coreutils source
  • Stand up an Ubuntu 24.04 VM in the cyber range (REA’s supported host) for anything untrusted
  • Clean up the two old Ghidra 12.1.2 copies and the 543 MB zip
  • Decide whether to connect REA to an agent, now that I understand it by hand