SnackOnAI Engineering | Senior AI Systems Researcher | Technical Deep Dive | October 12, 2026
The promise: by the end of this issue you will know exactly where an AI agent can be trusted inside a binary, and where it is guessing.
The fence: we cover REA's MCP architecture, its evidence contract, a full binary to C reconstruction, and the two papers that measure how good LLM decompilation really is (LLM4Decompile and SALT4Decompile). We do not cover malware workflows, exploit development, or license circumvention. REA's own disclaimer scopes it to software you own or are authorized to examine, and so do we.
What It Actually Does
REA ("Reverse Engineer Anything") is a TypeScript MCP server plus CLI that gives a coding agent (Claude Code, Codex, Cursor, Gemini CLI, Grok Build) the ability to inspect software it has no source for. One command, npx rea-agents setup, registers the server with your agent, installs a matching agent skill, and backs up your existing config.
It ships no model. Instead it wraps existing engines behind one contract:
Native binaries: pseudocode, assembly, strings, symbols, xrefs through Hopper, Ghidra, or IDA
Managed and web targets: JavaScript/Electron (modules, IPC, source maps), .NET (CIL), Go build info, WASM, Android APKs via JADX, live websites via Chrome DevTools Protocol
Odd corners: EVM bytecode selectors, firmware via Binwalk/Unblob, Linux core dumps, 16-bit DOS through Ghidra
Community signal is extreme: 143.7k stars, 33.2k forks, 2,524 commits, 148 open issues at time of writing, MIT licensed. For comparison, LLM4Decompile, the most cited open decompilation model, sits at 7k stars.
The pitch is "see a feature you like, understand how it works down to the binary level, then build your own." The interesting question is not whether an LLM can read assembly. It is whether you should believe what it tells you.
The Architecture, Unpacked
REA is a broker with three jobs: pick one analysis engine and never silently swap it, turn every result into a hashable Evidence record, and cache results by the exact bytes of the target.

Caption: Focus on the return arrow. Every call comes back as a result plus a canonical Evidence record, not as prose the agent can embellish.
Four design decisions carry the system, ranked by how much they matter:
Evidence over answers. Every evidence producing tool returns { result, evidence_id, evidence }. The Evidence record carries artifact identity, source locations, observations, inferences, and unresolved findings as separate fields (MCP contracts). The agent can still hallucinate in its summary, but the raw facts it summarizes are pinned and comparable. analyze_function Evidence can be passed straight into compare_functions.
One provider per session, never auto replaced. open_binary binds an immutable provider, version, and analysis profile. If Ghidra fails, REA reports the failure; it does not quietly fall back to Hopper and hand you pseudocode from a different engine mid investigation. This is boring, and it is the right call: mixing engines inside one chain of reasoning makes addresses, names, and type guesses inconsistent.
Content addressed snapshots. A snapshot reuses a result only when target bytes, operation, parameters, provider, and settings all match. A CLI cache hit is served before any provider process starts, which matters because a cold Ghidra import is the slowest thing in the stack.
Owned process discipline. REA "never kills a process it cannot prove it owns," records launcher PIDs and process groups, and reports cleanup_incomplete rather than pretending shutdown succeeded. When an agent is launching Electron apps and debuggers on your laptop, this is the difference between a tool and a liability.
The Code, Annotated
Snippet one: the timeout trap every first time user hits
REA's docs measured a cold first binary_overview at about 64 seconds (Ghidra 12.1.4, two CPUs, 512 MiB heap). The pinned MCP client SDK defaults to 60,000 ms. Do the arithmetic: out of the box, the first native query can time out before Ghidra finishes importing.
// Source: REA docs/mcp-contracts.md, annotated
const overview = await client.callTool(
{ name: "binary_overview", arguments: {} }, // first Ghidra-backed query: triggers import + auto-analysis
{
timeout: 240000, // ← THIS is the trick. SDK default is 60_000 ms; measured cold start was ~64 s.
// REA's own startup budget is 330_000 ms, but the CLIENT owns its wait.
// A longer server deadline does nothing if the client gives up first.
onprogress: ({ progress, total, message }) => {
// REA never fabricates percentages: `total` is omitted when unknown.
// Progress proves the server is alive, NOT that Ghidra is ready.
console.error({ progress, total, message });
},
},
);
// Recovery if you already timed out: close_binary → open_binary(provider_id: "ghidra") → retry.
// Re-calling open_binary alone may reuse the half-started client.
Caption: Deadlines have three owners (client, server, engine). REA documents all three instead of hiding them, which is rarer than it should be.
Snippet two: what an LLM decompiler actually sees
REA hands an agent instructions plus decompiler pseudocode. Dedicated decompilation models take a narrower input. This is LLM4Decompile's preprocessing, the format behind most numbers in this issue:
# Source: LLM4Decompile README (V1.5 "End" models), annotated
import subprocess
func_name, src, opt = "func0", "samples/sample", "O2"
subprocess.run(f"gcc -o {src}_{opt}.o {src}.c -{opt} -lm", shell=True, check=True)
subprocess.run(f"objdump -d {src}_{opt}.o > {src}_{opt}.s", shell=True, check=True)
asm = open(f"{src}_{opt}.s").read()
# Slice out ONE function. The model never sees .rodata, globals, or callers.
asm = f"<{func_name}>:" + asm.split(f"<{func_name}>:")[-1].split("\n\n")[0]
clean = []
for line in asm.split("\n"):
cols = line.split("\t")
if len(cols) < 3 and "00" in line: # drop raw byte-only lines
continue
ins = "\t".join(cols[min(len(cols) - 1, 2):]) # strip address + machine bytes
clean.append(ins.split("#")[0].strip()) # ← THIS is the trick, and the flaw:
# objdump's "# 0x4020 <.rodata>" comment is
# discarded, so constants become unreachable.
prompt = "# This is the assembly code:\n" + "\n".join(clean) + "\n# What is the source code?\n"
# Max context 4,096 tokens. Long real-world functions get truncated or skipped entirely.
Caption: Function-at-a-time, constants stripped, 4K window. SALT4Decompile's biggest single fix is putting the data segment back.
Snippet three: the SALT idea in twenty lines
SALT4Decompile's insight is that loops survive compilation as back edges in the control flow graph (CFG). Find the strongly connected regions, nest them, and feed the model a tree instead of a flat jump soup. Here is a runnable toy of the jumping unit step:
# Toy reconstruction of SALT's loop-tree step (Algorithm 1 + 2, simplified). Runs as-is.
import networkx as nx
cfg = nx.DiGraph()
cfg.add_edges_from([
("bb_00", "bb_10"), ("bb_10", "bb_20"), ("bb_20", "bb_30"),
("bb_30", "bb_20"), # back edge → inner loop
("bb_30", "bb_40"), ("bb_40", "bb_10"), # back edge → outer loop
("bb_10", "bb_50"), # loop exit
])
def loop_tree(g):
# Each cycle = a candidate source-level loop. Bigger cycles enclose smaller ones.
loops = sorted({frozenset(c) for c in nx.simple_cycles(g)}, key=len, reverse=True)
tree = {}
for i, body in enumerate(loops):
parents = [j for j in range(i) if body < loops[j]] # ← THIS is the trick:
parent = f"LOOP_{parents[-1]}" if parents else "root" # set containment = nesting depth
tree[f"LOOP_{i}"] = {"parent": parent, "blocks": sorted(body)}
return tree
for name, node in loop_tree(cfg).items():
print(name, node)
# Output:
# LOOP_0 {'parent': 'root', 'blocks': ['bb_10', 'bb_20', 'bb_30', 'bb_40']}
# LOOP_1 {'parent': 'LOOP_0', 'blocks': ['bb_20', 'bb_30']}
Caption: The model receives <<LOOP_0>> and <<LOOP_1>> markers with their instructions grouped, so "two nested loops" is given, not inferred from jump offsets.
It In Action: DX-Ball, From Executable to Byte-Exact C
The best public REA case study is the DX-Ball reconstruction: a 1996 Windows i386 game rebuilt in maintainable C, one function at a time. We follow one function end to end.
Input. Prompt to the agent, with DXBALL.EXE 1.07 (hash pinned) on disk:
Use REA to find how DX-Ball calculates sound panning. Explain the calculation and show the code.
Step one: the decompiler lies by omission. analyze_function {"procedure": "0x406400"} returns Ghidra-style pseudocode:
longlong FUN_00406400(void) // "void": the decompiler lost the argument entirely
{
longlong lVar1;
lVar1 = __ftol(); // a float-to-int call with no visible input
return lVar1;
}
Step two: the instructions tell the truth. The same result includes assembly. MOV EAX, [EBP+0x8] then FILD proves an integer stack argument exists. A pseudocode-only tool would have stopped here with a wrong signature.
Step three: follow the caller. analyze_function {"procedure": "0x411f40"} shows the brick-hit function computing x * 2, then * 3, then * 5 (three cheap ADD/LEA ops for ×30), then ADD EAX, 0x14. The argument is 20 + 30 × tile_x, a screen X coordinate.
Step four: read the constants. Two read_bytes calls decode little-endian doubles: 0x420068 → 1.5625, 0x420070 → 500.0, 0x4210a0 → 1.0 (a mutable pan scale).
Output. The reconstructed function:
DxBallInt dxball_screen_pan(DxBallInt x)
{
double pan;
pan = (double)x;
pan = pan * 1.5625; /* 640 px × 1.5625 = 1000 units of pan range */
pan = pan - 500.0; /* recenter: 320 px → 0 */
pan = pan * dxball_pan_scale;
return (DxBallInt)pan; /* __ftol truncates toward zero */
}
Caption: Every constant and operand traces to a specific address the agent read, not a guess.
Real numbers, checked two ways:
Check | Result |
|---|---|
Differential test vs original x86 | 3,205 cases pass (every x from 0 to 640 × five scales: 0, 0.5, 1, 20, −1) |
Compiler replay with pinned VC4.0 | 63 of 63 function bytes match after relocations |
Sample outputs at scale 1 | x=0 → −500, x=320 → 0, x=640 → 500 |
Leftmost brick (tile_x=0, x=20) | 20×1.5625−500 = −468.75 → −468 |
Project to date (Oct 7 checkpoint) | 55 C functions, 45,380 comparisons, 33 byte-matched functions |
That last row is the real story: 33 of 55 functions (60%) are byte-identical recompilations. The other 22 are behaviorally tested but not bit-proven. Honest progress reporting, and a useful baseline for what "agent-assisted decompilation" yields on a real, small, unobfuscated binary.
Why This Design Works, and What It Trades Away
Why it works. REA puts the LLM where LLMs are strong (connecting facts, naming things, writing the reconstruction) and keeps it away from what deterministic tools do better (disassembly, xrefs, byte reads). It is the refine-based approach from the research literature, but with one critical upgrade: the agent gets raw instructions alongside pseudocode, so it can catch the decompiler's mistakes, as in step two above.
The numbers back this split. On SALT4Decompile's Decompile-Eval benchmark, Ghidra alone produces re-executable code only 26.7% of the time. A general frontier model reading assembly (Claude 3.5 Sonnet) reaches 50.5%. A specialized 6.7B model with structural hints reaches 58.7%. No single component is trustworthy alone. The verification loop is the product.
What it trades away.
Native depth is rented. Without Hopper, Ghidra, or IDA installed, native analysis does not exist. REA grants no IDA or Hopper license, and the README is blunt that cracked copies are not authorized.
Cold start latency. 330 seconds of allowed Ghidra startup is a lot of dead air inside an agent loop. Snapshots fix repeat queries, not the first one.
Context economics. Every Evidence record is returned inline in full. Pseudocode plus assembly plus callers for a large function burns context fast; REA does not summarize for you, by design.
Your data leaves anyway. Analysis is local, but the agent's model provider sees every result. For proprietary binaries, "runs locally" is only half true.
Technical Moats
The evidence contract, not the tool count. The tool catalog grew from 42 MCP tools in early npm releases (Socket listing) to 70+ today. Wrappers are easy to copy. A canonical, comparable Evidence schema that spans Ghidra functions, Electron IPC traces, HAR captures, and .NET CIL is not.
Operational scar tissue. Documented first query deadlines, provider ambiguity errors with candidate_ids, integrity quarantine for mismatched artifacts, owned process lineage. This is two thousand five hundred commits of edge cases, and it is the part a weekend clone skips.
Distribution. One npx wizard configuring a dozen agents with backups and a matching skill is a flywheel: 33k forks means the showcase library (DX-Ball, Notion's clipboard bridge, a PC-98 bullet ring) keeps growing without the maintainers writing it.
Insights
Insight One: the 143k star tool contains zero machine learning, and that is the point. The research community spent 2024 and 2025 training decompilation models from 1.3B to 33B parameters. The tool that won developer mindshare trains nothing; it routes facts between existing engines and a general agent. LLM4Decompile's best end-to-end checkpoint re-executes 45.4% of benchmark functions; its Ghidra-refining 9B model reaches 64.9%. Meanwhile frontier agents keep improving for free. Building the pipe was the better bet than building the model, at least until a specialized model can be plugged in as just another REA provider.
Insight Two: "refine the decompiler" collapses on real code, so agent-over-Ghidra stacks need instructions, not just pseudocode. On ExeBench (real GitHub functions with user-defined types), Ghidra's re-execution rate is 3.2%, and LLM4Decompile-Ref, which refines Ghidra output, falls to 11.9% against 26.4% for SALT4Decompile working from assembly. Garbage in, polished garbage out. Any agent that reads only pseudocode inherits this ceiling. REA's habit of returning instructions next to pseudocode is not a convenience; it is the only reason the DX-Ball void signature got caught.
Takeaway
On the hardest real-world benchmark in this issue, the best LLM decompiler correctly explained only 39.7% of functions (102 of 257, from binaries like WeChatWin.dll and cmd.exe), and that was a 12.1% improvement over prior work. Agents can reverse engineer, but more than half the time the first answer is wrong. Every claim needs an oracle: a differential test, a byte replay, or a second engine.
TL;DR For Engineers
REA is an MCP broker, not a model: Hopper/Ghidra/IDA plus a dozen built-in analyzers behind one
{ result, evidence_id, evidence }contract.Set your MCP client timeout above 60 s before the first Ghidra query; measured cold start was ~64 s, REA allows up to 330 s.
Decompiler pseudocode drops arguments and constants; ask for instructions and
read_bytes, as the DX-Ball case did.SALT4Decompile hits 70.4% test pass rate on Decompile-Eval by feeding a 6.7B model a loop tree plus inlined
.rodata, for $0.49 per full eval vs $3.74 for Claude.Treat 39.7% real-world accuracy as the honest baseline. Verify with tests or byte matching, always.
Verification Is the Product, Agents Are the Interface
REA did not solve decompilation. It solved the part nobody was solving: giving agents a disciplined, evidence-backed way to ask questions of a binary and get facts back instead of vibes. The research shows LLM decompilation is real but unreliable (26% to 70% depending on benchmark and method, far lower on real code). REA's architecture assumes that unreliability and builds around it with pinned providers, hashable evidence, and reconstructions that must pass the original binary's behavior. Use it the way the DX-Ball project does: one function, one oracle, one byte-matched commit at a time. Use it the way the hype suggests ("clone any app's feature") and you will ship confident, wrong C.
References
Tan et al., LLM4Decompile: Decompiling Binary Code with Large Language Models, EMNLP 2024 (code and models)
Wang et al., SALT4Decompile: Inferring Source-level Abstract Logic Tree for LLM-Based Binary Decompilation (retitled ALT4Decompile in its August 2026 revision)
Basque et al., Ahoy SAILR! A Compiler-Aware Structuring Algorithm for Binary Decompilation, USENIX Security 2024
Tan et al., Decompile-Bench: Million-Scale Binary-Source Function Pairs, 2025
Explain It Like I'm New
Imagine you bought a gadget and want to know how one feature works, but the manufacturer only ships the finished product, never the blueprints. Software is the same. Apps arrive as compiled programs: millions of tiny machine instructions with the original names, comments, and structure stripped out. Figuring out what they do from that form is called reverse engineering, and it has always been slow, expert work.
Specialized tools called decompilers try to turn machine instructions back into readable code. They help, but they make mistakes, sometimes dropping whole inputs or misreading loops. An expert then spends hours cross-checking.
REA hands that cross-checking work to an AI coding assistant. Think of it as giving the assistant a toolbox and a lab notebook. The toolbox holds the existing decompilers and inspectors. The notebook forces every finding to be written down with exactly where it came from, so the assistant cannot just make things up and you can check its work.
What is new is not a smarter AI model. It is the discipline around the model: real tools, recorded evidence, and tests that compare the rebuilt code against the original program.
That matters because research shows AI decompilation is genuinely useful but wrong surprisingly often. Systems that pair AI with verification, rather than trusting it blindly, are what will make this practical for everyday engineers.
See It In Action
GhidraMCP demo (Laurie Wired, repository with demo video). The single-engine predecessor pattern REA generalizes; useful contrast for what a provider abstraction adds.
Community Conversation
@reatools on X (link). The project's own channel for release notes; worth following because the README warns REA "changes quickly" and many bug reports are fixed by
rea update.SkipCI comparison, REA vs ida-pro-mcp (link). Frames the tradeoff clearly: REA is free with optional Hopper/Ghidra and a one-command setup across 12 agents, while ida-pro-mcp needs paid IDA Pro and its author now points users to Hex-Rays' own MCP.
Digg AI discussion (link). Raises the question this issue keeps returning to: where the line falls between analyzing a feature and reproducing someone else's product.
AIToolly trending coverage (link). Positions REA around legacy interoperability and defensive security rather than cloning, a framing practitioners in enterprise modernization will recognize.
LLM4Decompile maintainers (repo updates). Their 100k-sample training subset reproduces a 0.26 re-executability model in about 3.5 hours on one A100 for under $20, a reminder that the model side of this stack is now cheap to replicate.
REA is an MCP server that lets coding agents reverse engineer binaries, apps, and websites through existing engines, returning every finding as pinned, comparable evidence. Its key architectural bet is that verification beats intelligence: research puts LLM decompilation between 26% and 70% correct on benchmarks and under 40% on real software, so the winning design is the one that makes every answer checkable.
Sponsored Ad If you enjoy practical AI insights, check out SnackOnAI and support the newsletter by subscribing, sharing, and exploring our sponsored ad—it helps us keep building and delivering value 🚀 If you enjoy practical AI insights, check out SnackOnAI and support the newsletter by subscribing, sharing, and exploring our sponsored ad—it helps us keep building and delivering value 🚀
State of Product reveals what AI still hasn’t solved
80% of product professionals say AI helps them ship faster, but customers aren’t seeing value any sooner. Atlassian’s State of Product 2027 explores the gains AI is delivering and the challenges that remain, from decision-making that hasn’t kept pace to gut instinct overriding customer evidence.


