Skip to content

Reverse engineering with a coding agent and headless Ghidra

A coding agent reads source, runs commands, and edits files. Static reverse engineering is a different job: the thing you are studying has no source, only a shipped binary or package, and the questions are about behaviour its authors never documented. This page describes giving an agent a decompiler it can drive as tools - a reverse-engineering MCP server backed by headless Ghidra - and the workflow that makes it answer questions like “which protocol messages does this client handle” and “does the public documentation miss anything”.

The framing is static analysis of software the analyst is entitled to study: interoperability, protocol documentation, and one’s own tooling. Every mechanism below is read from the harness source and the reverse-engineering skill as of 2026-10-20; versions and checksums are copied from the installer, not recalled. The worked example is read from the author’s Soulseek parity research notes of 2026-10-19.

TL;DR:

  • Static reverse engineering is a question about a shipped artifact; a decompiler answers it. Wire the decompiler up as agent-callable tools rather than asking the model to explain a binary from a strings dump.
  • The server exposes ~170 tools. They are deferred, found via tool search when a task actually needs them, instead of being declared on every turn - the same reason a worker’s first request cost about 77.5k prompt tokens carrying 139 tool schemas (see Which model runs which agent work).
  • Ghidra is installed by a pinned, checksum-verified script: version 12.1.4 with a sha256 that is checked before the zip is unzipped, on top of a JDK 21 requirement. Re-running is idempotent.
  • The build instructions ride along as a vendored skill, copied verbatim from the server’s package and bumped at the same time as the server version, so the guidance never describes capabilities the running server lacks.
  • Headless analysis is the agent’s workflow: analyzeHeadless needs no GUI, so an agent can bind a binary, pull pseudocode, symbols and cross-references, and summarise.
  • The worked example built a feature-parity matrix by static-reversing the official Soulseek client’s binary against two open-source clients, and found a server message code the public protocol documentation omits.
  • A worker that must stop a helper process it started kills it by a recorded PID. A pattern kill (pkill -f) matches the worker’s own command line and kills the worker.

agent sidepinned toolchaincoding agent(session or worker)tool searchfind RE toolswhen neededREA MCP serverrea-agents@6.4.0~170 tools, deferredopen_binary, analyze_function,symbol/string searchvendored skill(routing + recipes)version bumpedwith the serverheadless Ghidra 12.1.4analyzeHeadlessGHIDRA_INSTALL_DIRinstallerversion + sha256verify,unzipJDK 21+JAVA_HOMEtarget artifact(shipped binary / package)static analysis only(never executed)

The same thing as text:

  1. The agent starts with no reverse-engineering tools loaded. When a task needs them, it finds them through tool search and reaches the MCP server.
  2. The MCP server wraps the REA package (rea-agents@6.4.0) and exposes roughly 170 tools, deferred so they are not declared on every turn.
  3. The server locates Ghidra through GHIDRA_INSTALL_DIR and the JDK through JAVA_HOME.
  4. Ghidra 12.1.4 was installed by a script that pins a version and checks a sha256 before unzipping, and requires a JDK 21+ on PATH.
  5. A vendored skill carries the routing recipes; its server version is bumped at the same time as the MCP pin so the two stay in step.
  6. The agent drives Ghidra headlessly against the target artifact - static analysis only, the binary is never executed.

You haveReach forWhy
A question about a shipped binary or package (what protocol codes it handles, what a function does), for software you may studyThe RE MCP server over headless GhidraPseudocode, assembly and cross-references are the raw evidence, and the agent can query them as tools
An ordinary source repository, asking how the code behavesNormal repo tools: read, grep, LSPSource is authoritative and cheaper; the skill’s own routing says to skip RE for complete source
A rarely-used tool capability that only some sessions needexposure: deferred, found via tool searchDeclaring ~170 schemas is a per-turn prompt cost paid by every request, not just the ones that use them
A worker that must stop a helper process it started (a headless analysis run gone long)Record the PID to a file and kill by PIDpkill -f matches the worker’s own command line and kills the worker

Reverse engineering is the fallback when the artifact is the only source of truth. In the worked example the question was “what can the official Soulseek client do, and which protocol messages does each feature use” - none of which the vendor publishes. An agent without a decompiler can only read strings and guess; one with a decompiler can name the symbol a message code reaches and read pseudocode for what that code does.

The line this page draws is the same one the skill draws: static analysis of software the analyst is entitled to study. Never execute the target binary. No license-circumvention work, no copied or pirated commercial software. The worked example holds to this - the official client’s Linux build is analysed as a shipped artifact (it happens to ship unstripped, with debug symbols), and the binary is never run.

The REA server exposes roughly 170 tools1. Every declared tool adds its name, description and parameter schema to each outgoing request’s prompt. A worker’s first request used to carry about 77.5k prompt tokens once 139 tool schemas were loaded across every MCP server, before any work happened - and a local single-lane engine re-prefilled all of that on every cache miss (measured in Which model runs which agent work).

Reverse engineering is a capability most turns do not need, so declaring it on every turn would tax every conversation to serve the few that do. The fix is "exposure": "deferred": the server’s tools stay out of the per-turn tool list, and the model fetches them through tool search when a task actually calls for them. The deferred entry still carries a description so the search can match it (“native binaries via Ghidra (pseudocode, asm, xrefs), JS/Electron, .NET, APKs, SQLite, websites”).

Installing Ghidra: pinned and checksum-verified

Section titled “Installing Ghidra: pinned and checksum-verified”

A decompiler the agent will rely on should be a known version, installed reproducibly. The installer script pins both:

  • Ghidra 12.1.42, matching the 12.1.x line the server’s provider accepts. The zip is ghidra_12.1.4_PUBLIC_20260921.zip, with sha256 ddac49f903da9d5bac833e5cc79395098b9c33cfd3279be5f31bd00387d2d4db, checked before unzipping.
  • A JDK 21+ on PATH, enforced up front (command -v javac).

The script downloads to a pinned directory, verifies the checksum, unzips into an install tree, and symlinks a stable path so the server’s GHIDRA_INSTALL_DIR points at whichever version is current. Re-running skips what is already in place, so the install is idempotent. The same script pins a second tool on the same pattern - a Lua 5.x bytecode decompiler, checksum-pinned the same way - which matters when the target bundles Lua plugins rather than native code.

The vendored skill, kept in step with the server

Section titled “The vendored skill, kept in step with the server”

The server’s package ships a skill - a set of routing instructions telling the agent which tool to open for which target (native ELF vs APK vs managed assembly vs archive, and so on). The skill is vendored verbatim into the harness’s skill tree, with a provenance file recording where it came from and how to update it.

The step that keeps them from drifting: the skill and the server pin carry the same version, and bumping one means bumping both, then re-copying the skill. The provenance file says so explicitly - “bump the version in mcp.json, then re-run the copy”. The rationale is exactly the deferred-tools rationale from the other direction: a skill edited out of band can describe capabilities the running server lacks, and a stale skill quietly points the agent at tools that changed. Versioning them together makes the guidance describe the server that is actually connected.

Three things the skill’s routing makes explicit, worth carrying into any agent RE setup:

  • Connect only when needed. Installing the skill supplies instructions; it does not register the server, install the engines, or add tools to a running session. Those are separate steps with separate failure modes.
  • Route the target first. The first tool depends on the artifact: open_binary for a native binary or archive, a JS/Electron path for an extracted app, a managed-assembly path for a .NET artifact. Target-free tools take their own explicit path.
  • Summarise before drilling. Big artifacts get a summary view first, then a focused follow-up on the specific item that answers the question, rather than a full dump.

A GUI disassembler is a human’s tool; an agent’s tool is one it can invoke by command and read back as text. Headless Ghidra (analyzeHeadless) runs the analysis and dumps results with no window, so the whole loop fits inside the agent’s normal tools: bind the binary, ask for an overview, then pull pseudocode, symbols, strings and cross-references for the function a message code reaches.

For the worked example, the flow was:

  1. Bind the official client’s build through open_binary, then take the binary_overview metadata and symbol inventory.
  2. Search symbols for the names the questions point at - in this case, the dispatch table entries each protocol message code reaches.
  3. Decompile the reached function to read what it does, and cross-reference callers to see who relies on it.
  4. Distil each finding into a row of a coverage matrix, with the evidence (symbol, code, offset) attached.

The one rule that stays constant: the binary is analysed, never executed. Every conclusion is marked as observation, inference, or unknown - static analysis establishes that a symbol exists and what its decompiled body does, not that a code path runs at a particular time.

Worked example: a Soulseek feature-parity matrix

Section titled “Worked example: a Soulseek feature-parity matrix”

The author builds a Soulseek client, and wanted it to reach feature parity with the official desktop client rather than staying an automation-only tool. The reference is the official client’s shipped Linux build3 - a closed binary, analysed statically. Two open-source clients (Nicotine+4 and slskd5) were inventoried from source for the same feature list, to see what a complete client does with each protocol message.

Because the official build ships unstripped, the symbol table carries 10,894 symbols - names, not just addresses - which turns “what does code 128 do” into a direct symbol lookup. The result is a parity matrix with one column per client (the official reference, the two open-source clients, and the author’s own) and one row per feature, marked Y / partial / missing, with the protocol codes each feature still needs.

Two findings came from the binary alone, absent from the public protocol documentation6:

  • Server code 128, Distributed Delivery Report. The public protocol document numbers the server messages it covers and skips 128 entirely. The binary has a Server::DNetDeliveryReport(int) symbol that the dispatch table routes code 128 to, decoding a single 32-bit value. The reference client only logs it, so the author’s client can ignore it.
  • An obfuscated peer connection. Decompilation of the connect path recovered how peers advertise a second real port and XOR the traffic on it with a 4-byte key rotated per block. The open-source clients do not implement it. Recovered from decompilation but not tested against a live peer, so the implementation is gated on a live handshake test.

The matrix is the deliverable, because it is what an implementation can consume: each “codes / notes” cell names the exact server or peer message an incomplete feature needs next. The worked example also demonstrates the skill’s boundary discipline - the binary was downloaded for a pinned checksum and only strings-scanned or symbol-read, never executed.

The operational lesson: never kill by a pattern your own command line matches

Section titled “The operational lesson: never kill by a pattern your own command line matches”

A worker running the headless analysis needed to stop an analyzeHeadless run it had started. It ran pkill -f analyzeHeadless. The worker’s own process command line contains its prompt - and the prompt named the Ghidra tool - so pkill -f matched the worker itself and killed it, exit 143 (SIGTERM). The work was lost.

The fix is to treat the process ID as an argument, not a search string: record the PID to a file when the helper starts (... & echo $! > pidfile), and kill by that PID. Pattern kills (pkill -f, killall) are safe only when the pattern cannot appear in the killer’s own prompt - which is almost never, since an agent’s prompt usually names the tool it is about to run.

This generalises: any background process a worker starts and later stops should be stopped by its recorded PID, because the worker’s prompt travels in its own argv and becomes part of what a pattern match scans.


The server name, the 170-tool count, the Ghidra 12.1.4 pin and the Soulseek specifics belong to this setup. These properties do not:

  1. Give the agent the decompiler, not a strings dump. A decompiler as tools turns “what does this binary do” into a query the agent can run and read back. Prose about the binary is a poor substitute.
  2. Pin and check the toolchain. A reproducible install means a pinned version plus a checksum verified before use, and an idempotent script. The decompiler’s behaviour moves between versions; a recorded version is what lets the findings be re-checked.
  3. Defer rarely-used tool surfaces. A capability a minority of turns need should not tax every request’s prompt. Gate it behind search with a description the search can match.
  4. Keep the instructions in step with the server. Vendor the guidance alongside the server and bump both together, so the guidance never describes a server that is not connected.
  5. Prefer headless for agent work. A decompiler the agent can invoke by command and read back as text beats a GUI it cannot operate. Headless Ghidra’s analyzeHeadless is the difference.
  6. Distil to a matrix. RE findings become actionable when they land as a coverage matrix with per-row evidence, not narrative. A “codes / notes” cell names the next concrete step.
  7. Stop started processes by PID. A background helper is killed by its recorded PID, never by a pattern that could match the killing agent’s own command line.

Question about howsoftware behavesSourceavailable?Entitled tostudy it?noRead, grep, LSP.No RE server.yesStop. No RE.noLoad the RE tools via tool search;static analysis on headless Ghidra(pinned version + checksum)yesWorker starts ahelper process?Record its PID; kill by PID,never pkill -fyesDistil findings intoa coverage matrixno

The same decision as a list:

  1. Source is available: use normal repository tools (read, grep, LSP). Reverse engineering is the heavier fallback, reserved for when the artifact is the only source of truth.
  2. No source, and you are not entitled to study the artifact: stop.
  3. No source, and you may study it: load the RE tools through tool search and analyse the artifact statically on headless Ghidra installed from a pinned version plus a verified checksum. Overview first, then focused decompilation; the target is never executed.
  4. A worker that starts a helper process records its PID and kills by PID. A pkill -f pattern matches the worker’s own argv and kills the worker instead.
  5. Findings end as a coverage matrix with per-row evidence.

ClaimHow checkedStatus
The REA server runs bunx -y rea-agents@6.4.0 --mcp and is deferredRead from the MCP config’s rea entry (command, args, exposure)Read from source
The server exposes ~170 toolstool_count: 170 in the skill frontmatter, “170 tools” in the harness AGENTS.md rea entryRead from source
Ghidra is pinned to 12.1.4, zip ghidra_12.1.4_PUBLIC_20260921.zip, sha256 ddac49f903da9d5bac833e5cc79395098b9c33cfd3279be5f31bd00387d2d4db, JDK 21+ requiredRead from the installer script’s GHIDRA_VER, GHIDRA_ZIP, GHIDRA_SHA256 and the javac checkRead from source
The skill is vendored verbatim from rea-agents@6.4.0 (morluto/rea, MIT) and bumped in step with the MCP pinRead from the skill’s VENDORED.md provenance and update procedureRead from source
The official Soulseek build ships unstripped with 10,894 symbolsRead from the parity research notes (sources table, SoulseekQt row)Read from source
Server code 128 (Distributed Delivery Report) is absent from the public protocol documentationServer::DNetDeliveryReport(int) symbol confirmed by nm at offset 0x2aa4a0; code 128 absent from the public SLSKPROTOCOL.md (checked against the Nicotine+ copy, which documents 120-127 and 129 onward)Measured, and re-checked against the public doc 2026-10-20
The obfuscated peer connection (second port, XOR 4-byte key rotated per block) is recovered from decompilation but untested against a live peerRead from the parity research notes (open questions section)Read from source; live test not yet run
A pkill -f analyzeHeadless worker killed itself, exit 143Session history, 2026-10-19 incidentRecorded

  1. morluto, “REA (Reverse Engineering Agent),” MIT. https://github.com/morluto/rea ↩

  2. National Security Agency, “Ghidra,” software reverse engineering framework. https://github.com/NationalSecurityAgency/ghidra ↩

  3. Soulseek, official site and desktop client downloads. https://www.slsknet.org ↩

  4. Nicotine+ contributors, “Nicotine+,” the open-source Soulseek client. https://github.com/nicotine-plus/nicotine-plus ↩

  5. slskd, “A modern client-server application for the Soulseek file sharing network.” https://github.com/slskd/slskd ↩

  6. Nicotine+ contributors, “Soulseek Protocol Documentation,” Nicotine+. https://github.com/nicotine-plus/nicotine-plus/blob/master/doc/SLSKPROTOCOL.md ↩