Back to Articles

Building an Autonomous Reverse Engineering Agent with Dual-LLM Verification Loops

[ View on GitHub ]

Building an Autonomous Reverse Engineering Agent with Dual-LLM Verification Loops

Hook

What if you could point an AI at a compiled game binary and have it automatically reconstruct hundreds of C++ functions—complete with self-correction when it hallucinates the wrong code?

Context

Reverse engineering large C++ binaries like game executables is a war of attrition. Projects like the GTA: San Andreas decompilation involve reconstructing tens of thousands of functions from compiled assembly, matching each one against the original binary to prove correctness. Traditionally, this means a human reads Ghidra decompilation, writes C++ by hand, compiles it, and verifies the reconstruction. For a single AAA game, this can take years and dozens of contributors.

The re-agent tool tackles this grind through automation: it orchestrates two LLMs in a reverser-checker loop, uses Ghidra as a decompilation backend via ghidra-ai-bridge, and validates outputs with an 11-signal parity engine that catches common failure modes like stub functions or floating-point mismatches. Instead of manually reversing each function, you can point it at a class with configurable batch limits and review the logged reconstructions. It’s designed for long-haul decompilation projects where systematic processing matters more than perfection on every function.

Technical Insight

The architecture splits into three layers: the orchestrator, the dual-LLM agent loop, and the parity engine. When you run re-agent reverse --class CTrain, the orchestrator queries ghidra-ai-bridge for all functions in that class, ranks them by caller count (core functions with many callers get priority), and filters out anything already completed in the session state JSON. For each function, it gathers context: decompiled pseudocode from Ghidra, cross-references showing where it’s called, and relevant struct definitions.

This context feeds into the agent loop, which runs up to four rounds by default (configurable via max_review_rounds). In each round, the reverser LLM (Claude Sonnet or OpenAI Codex) proposes a C++ reconstruction based on customizable prompt templates stored as markdown files. The checker LLM then critiques the output, looking for logical errors, missing edge cases, or deviations from the decompiled code. If the checker finds issues, the orchestrator feeds the critique back to the reverser for another attempt. Here’s what a minimal reversal command looks like:

# Reverse a single function at a known address
re-agent reverse --address 0x6F86A0

# Reverse all functions in CTrain, capped at 10 functions (default limit)
re-agent reverse --class CTrain --max-functions 10

# Dry-run to preview what would be processed
re-agent reverse --class CTrain --dry-run

The configuration lives in re-agent.yaml, where you define project-specific heuristics. For example, GTA: San Andreas reverse engineering projects use hook patterns to map reconstructed functions back to original addresses and stub markers like NOTSA_UNREACHABLE to flag incomplete implementations:

project_profile:
  source_root: ./source/game_sa
  hook_patterns:
    - 'RH_ScopedInstall\s*\(\s*(\w+)\s*,\s*(0x[0-9A-Fa-f]+)'
  stub_markers: ["NOTSA_UNREACHABLE"]
  stub_call_prefix: "plugin::Call"

After the agent loop produces a candidate reconstruction, the parity engine runs 11 configurable heuristic signals to verify quality. These aren’t formal verification—they’re pragmatic checks for common failure modes. A RED signal (missing source body, stub markers present, or massive assembly with trivial source) indicates likely problems. YELLOW signals (plugin-call heavy code, low call count, floating-point sensitivity mismatches) flag warnings. The engine checks things like: does the decompiled assembly have 80+ instructions but the reconstructed source only has 12 lines? Does Ghidra show floating-point operations but the C++ has no float arithmetic?

The orchestrator is stateless by design—all progress lives in a JSON session file that gets appended, never overwritten. Every LLM call is logged with timestamps, so you can replay decisions or debug why a particular function failed. This matters for long-running batch jobs where you might reverse multiple functions and need to audit what happened. There’s no auto-commit behavior; re-agent writes reconstructed code to disk but never touches git, so you control when and what gets committed.

The ghidra-ai-bridge dependency is critical here. It’s a separate tool that wraps Ghidra’s headless analysis mode with a CLI, exposing commands to decompile functions, fetch cross-references, and query struct layouts. The re-agent orchestrator shells out to this bridge for all Ghidra interactions, which means you need a pre-configured Ghidra project before running any reversal commands. The bridge reports capability flags (can it fetch structs? can it read enums?), and re-agent degrades gracefully if features are missing—it’ll skip struct context if the bridge doesn’t support it, for instance.

Gotcha

The biggest friction point is setup complexity. You can’t just pip install re-agent and start reversing—you need ghidra-ai-bridge installed, a Ghidra project already configured with your binary loaded, and the bridge CLI path set correctly in re-agent.yaml. The README doesn’t provide detailed Ghidra project setup instructions, so expect to fumble through Ghidra’s headless mode documentation if you’re not already familiar. For first-time users, this is a multi-hour setup process before you see any LLM output.

The parity engine’s heuristics are pragmatic but tuned for specific use cases. Thresholds like “RED if assembly >= 80 instructions but source <= 12 lines” or “YELLOW if source call count differs significantly” are configurable but represent assumptions about code structure. If you’re reversing highly optimized code where instruction counts don’t correlate with source complexity, you may need to adjust these thresholds. The engine checks static properties rather than compiling and comparing outputs, so you’re working with heuristics that approximate correctness.

LLM quality is a hard dependency. If Claude Sonnet generates incorrect pointer arithmetic or Codex produces wrong control flow, the agent loop will use its retry rounds (default 4) and may still produce suboptimal results. There’s no fallback to a different model or ensemble voting specified in the README. The tool assumes your chosen LLM (Claude or OpenAI) is capable of the binary analysis task at hand.

Verdict

Use re-agent if you’re tackling a large-scale C++ binary decompilation project (games, legacy engines) where you need to systematically reverse many functions and have ghidra-ai-bridge already set up. It shines for systematic reconstruction work where you want to automate the reverser-checker loop, log every decision for audit, and catch obvious mistakes with heuristics before review. The dual-LLM loop and configurable retry limits mean you get iterative refinement, which is valuable at scale. Skip it if you’re doing one-off reverse engineering tasks, need quick setup without Ghidra integration overhead, or require formal verification rather than heuristic-based quality gates. The tool is built for long-haul systematic projects, not quick ad-hoc analysis.

// QUOTABLE

What if you could point an AI at a compiled game binary and have it automatically reconstruct hundreds of C++ functions—complete with self-correction when it hallucinates the wrong code?

[ Tweet This ]
// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/developer-tools/dryxio-auto-re-agent.svg)](https://starlog.is/api/badge-click/developer-tools/dryxio-auto-re-agent)