<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://mitsuakki.github.io/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mitsuakki.github.io/blog/" rel="alternate" type="text/html" /><updated>2026-07-17T13:50:55+00:00</updated><id>https://mitsuakki.github.io/blog/feed.xml</id><title type="html">blog de mitsuakki</title><subtitle>Notes on reverse engineering, deobfuscation, OS internals, and tooling. By Alexis M. Daugé.</subtitle><author><name>Alexis M. Daugé</name></author><entry xml:lang="en"><title type="html">reverse-mcp: wiring Ghidra and radare2 into AI agents</title><link href="https://mitsuakki.github.io/blog/reverse-mcp-wiring-ghidra-radare2-into-ai-agents/" rel="alternate" type="text/html" title="reverse-mcp: wiring Ghidra and radare2 into AI agents" /><published>2026-07-16T00:00:00+00:00</published><updated>2026-07-16T00:00:00+00:00</updated><id>https://mitsuakki.github.io/blog/reverse-mcp-wiring-ghidra-radare2-into-ai-agents</id><content type="html" xml:base="https://mitsuakki.github.io/blog/reverse-mcp-wiring-ghidra-radare2-into-ai-agents/"><![CDATA[<p>I got tired of switching windows. Disassemble in one terminal, decompile in
another, grep xrefs somewhere else, then shell out for <code class="language-plaintext highlighter-rouge">strings</code> or <code class="language-plaintext highlighter-rouge">checksec</code>.
Every tool has its own CLI, its own output format, its own annoyances.</p>

<p>So I built <a href="https://github.com/mitsuakki/reverse-mcp">reverse-mcp</a>. One Docker
container. Ghidra, radare2, angr, and a shell. All exposed as MCP servers behind
a single HTTP endpoint. Claude Code connects, and every RE tool is one function
call away.</p>

<h2 id="how-it-works">How it works</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌──────────────┐     HTTP      ┌──────────────┐
│  Claude Code │ ────────────→ │   Gateway    │
│  (MCP client)│               │   :3100      │
└──────────────┘               └──┬───┬───┬──┘
                                  │   │   │
                    ┌─────────────┘   │   └─────────────┐
                    ▼                 ▼                 ▼
              ┌──────────┐   ┌──────────────┐   ┌──────────┐
              │  r2mcp   │   │  Ghidra      │   │  shell   │
              │  (r2pm)  │   │  headless    │   │  mcp.py  │
              └──────────┘   │  :8089       │   └──────────┘
                             └──────────────┘
</code></pre></div></div>

<h3 id="the-gateway">The gateway</h3>

<p><code class="language-plaintext highlighter-rouge">gateway.py</code>, port 3100. Spawns each MCP back-end as a child process,
composites their tools into one namespace. Each back-end gets a prefix:</p>

<table>
  <thead>
    <tr>
      <th>Prefix</th>
      <th>Back-end</th>
      <th>What you get</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">r2__</code></td>
      <td>radare2 (r2mcp)</td>
      <td>disassemble, decompile (r2ghidra), hexdump, xrefs, emulation</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ghidra__</code></td>
      <td>Ghidra headless :8089</td>
      <td>project management, import, auto-analysis, 200+ tools: decompiler, patcher, BinDiff, debugger</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">shell__</code></td>
      <td>shell-mcp</td>
      <td>anything: strings, checksec, ent, gdb, lldb, apktool, whatever</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">angr__</code></td>
      <td>angr MCP</td>
      <td>CFG, symbolic execution, VFG, dependency analysis</td>
    </tr>
  </tbody>
</table>

<p>If a back-end dies, the gateway logs it and keeps running. Tools from that
back-end just don’t appear. No cascading failure.</p>

<h3 id="the-ghidra-situation">The Ghidra situation</h3>

<p>Ghidra tools are split in two. Static tools (<code class="language-plaintext highlighter-rouge">import_file</code>, <code class="language-plaintext highlighter-rouge">list_instances</code>,
<code class="language-plaintext highlighter-rouge">create_project</code>) are always available. Instance tools (<code class="language-plaintext highlighter-rouge">decompile</code>, <code class="language-plaintext highlighter-rouge">rename</code>,
<code class="language-plaintext highlighter-rouge">xrefs</code>, <code class="language-plaintext highlighter-rouge">patch</code>) only show up after you load a program.</p>

<p>Flow: import file → auto-connect → gateway fetches the instance schema →
200+ tools unlock. A bit clunky but it holds together.</p>

<h2 id="getting-started">Getting started</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/mitsuakki/reverse-mcp
<span class="nb">cd </span>reverse-mcp
docker compose build
docker compose up <span class="nt">-d</span>
</code></pre></div></div>

<p>Drop binaries in <code class="language-plaintext highlighter-rouge">./workspace/</code>. They show up at <code class="language-plaintext highlighter-rouge">/workspace</code> inside the
container.</p>

<p>MCP client config:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"reverse-mcp"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http://localhost:3100/mcp"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="agents">Agents</h2>

<p>Five Claude Code agents in <code class="language-plaintext highlighter-rouge">.claude/agents/</code>. Each does one thing:</p>

<table>
  <thead>
    <tr>
      <th>Agent</th>
      <th>Model</th>
      <th>Role</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">binary-triage</code></td>
      <td>Haiku</td>
      <td>Fast surface recon: radare2 + shell</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ghidra-importer</code></td>
      <td>Sonnet</td>
      <td>Import into Ghidra, auto-analysis</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ghidra-analyst</code></td>
      <td>Sonnet</td>
      <td>Static RE: decompile, xrefs, rename</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ghidra-debugger</code></td>
      <td>Sonnet</td>
      <td>Dynamic: attach, breakpoints, trace</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">re-orchestrator</code></td>
      <td>Opus</td>
      <td>Runs the whole pipeline, triage → report</td>
    </tr>
  </tbody>
</table>

<p>The orchestrator chains them. Triage first for a quick surface look. If the
binary seems interesting, it hands off to Ghidra. Sometimes you just want a
fast answer and don’t need the heavyweight pipeline.</p>

<h2 id="whats-inside">What’s inside</h2>

<p>Everything in the container. No GUI, no VNC. Terminal or MCP only.</p>

<p>radare2 + r2ghidra decompiler. Ghidra headless + analyzeHeadless + a helper
script. BinDiff CLI. angr, pwntools, ropper, ROPgadget, capstone, unicorn,
keystone, z3, LIEF, r2pipe. Fuzzing: AFL++ with QEMU mode, honggfuzz.
Android: apktool, jadx, Frida, objection. Standard: gdb, lldb, strace,
ltrace, nasm, objdump, patchelf, gcc, clang.</p>

<p>Basically everything I use day to day, containerized so I don’t have to think
about dependencies.</p>

<h2 id="security">Security</h2>

<p>No auth on the gateway. Local dev tool, don’t expose it. Run untrusted
binaries in a VM. The compose file adds <code class="language-plaintext highlighter-rouge">SYS_PTRACE</code> and drops seccomp
for gdb/AFL to work.</p>

<h2 id="ci">CI</h2>

<p>GitHub Actions on push and PR. Tagged versions push to GHCR. MIT license.</p>

<h2 id="next">Next</h2>

<p>Binary Ninja MCP bridge using BN headless mode. Frida bridge for mobile RE.
Import Ghidra CFGs into angr for hybrid analysis.</p>

<p>I’ll write up how to build a custom MCP server for an RE tool. The pattern
is simple: subprocess the tool, parse output, expose as typed MCP tools with
JSON schemas. Under 200 lines of Python for a basic disassembler bridge.</p>]]></content><author><name>Alexis M. Daugé</name></author><category term="mcp" /><category term="ghidra" /><category term="radare2" /><category term="docker" /><category term="ai-agents" /><category term="tooling" /><summary type="html"><![CDATA[I got tired of switching windows. Disassemble in one terminal, decompile in another, grep xrefs somewhere else, then shell out for strings or checksec. Every tool has its own CLI, its own output format, its own annoyances.]]></summary></entry><entry xml:lang="en"><title type="html">Building a Binary Ninja deobfuscation plugin in C++</title><link href="https://mitsuakki.github.io/blog/building-binary-ninja-deobfuscation-plugin/" rel="alternate" type="text/html" title="Building a Binary Ninja deobfuscation plugin in C++" /><published>2026-07-15T00:00:00+00:00</published><updated>2026-07-15T00:00:00+00:00</updated><id>https://mitsuakki.github.io/blog/building-binary-ninja-deobfuscation-plugin</id><content type="html" xml:base="https://mitsuakki.github.io/blog/building-binary-ninja-deobfuscation-plugin/"><![CDATA[<p>Binary Ninja’s C++ API is kind of a hidden gem. Everyone defaults to Python
because it’s easy. But deobfuscation means iterating over every instruction
in a function, pattern matching against obfuscator signatures, rewriting IL.
Python just dies. Especially on real binaries.</p>

<p>So I wrote <a href="https://github.com/mitsuakki/deobfninja">deobfninja</a> in C++.</p>

<h2 id="why-not-python">Why not Python</h2>

<p>The Python API is fine for quick scripts. I still use it for one-off analysis.
But deobfuscation is one of those problems where you feel every millisecond
of interpreter overhead.</p>

<p>C++ gives you direct access to BN’s IL. No GIL, so you parallelize across
functions. Zero-copy buffer manipulation for pattern matching. On a 5 MB binary
with moderate obfuscation, the C++ plugin runs in under a second. The Python
equivalent took 42 seconds.</p>

<p>That’s not an optimization. That’s the difference between “usable” and
“I’m going to make coffee while this runs.”</p>

<h2 id="architecture">Architecture</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌──────────────────────────────────┐
│         Binary Ninja             │
│  ┌────────────────────────────┐  │
│  │    Plugin Loader           │  │
│  └──────────┬─────────────────┘  │
│             │                    │
│  ┌──────────▼─────────────────┐  │
│  │  deobfninja core           │  │
│  │  ┌───────────┐ ┌────────┐  │  │
│  │  │ Pattern   │ │ IL     │  │  │
│  │  │ Matcher   │ │ Rewrite│  │  │
│  │  └───────────┘ └────────┘  │  │
│  │  ┌───────────┐ ┌────────┐  │  │
│  │  │ Recipe    │ │ Metrics│  │  │
│  │  │ Engine    │ │        │  │  │
│  │  └───────────┘ └────────┘  │  │
│  └────────────────────────────┘  │
└──────────────────────────────────┘
</code></pre></div></div>

<h3 id="pattern-matching-on-mlil">Pattern matching on MLIL</h3>

<p>I work on BN’s Medium Level IL, not raw assembly. MLIL is high enough that
patterns survive across compiler versions, but low enough that obfuscator
tricks don’t get optimized away.</p>

<p>Each obfuscation technique registers as a detector:</p>

<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">class</span> <span class="nc">ObfuscationDetector</span> <span class="p">{</span>
<span class="nl">public:</span>
    <span class="k">virtual</span> <span class="o">~</span><span class="n">ObfuscationDetector</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>
    <span class="k">virtual</span> <span class="kt">bool</span> <span class="n">match</span><span class="p">(</span><span class="n">mlil_inst</span> <span class="n">inst</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">virtual</span> <span class="n">mlil_inst</span> <span class="n">rewrite</span><span class="p">(</span><span class="n">mlil_inst</span> <span class="n">inst</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">virtual</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">name</span><span class="p">()</span> <span class="k">const</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">virtual</span> <span class="kt">float</span> <span class="n">confidence</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="mf">1.0f</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>All detectors get chained. Every function gets piped through every detector.
Match → rewrite → next. Dead simple, but composing detectors is where
the interesting stuff happens.</p>

<h3 id="control-flow-flattening">Control flow flattening</h3>

<p>CFG flattening is everywhere. The obfuscator turns a normal function into a
state machine: each basic block updates a state variable, jumps to a central
dispatcher. Original logic is still there, structure is gone.</p>

<p>My detector looks for three signals:</p>

<ol>
  <li>A loop containing a dispatcher block with abnormally high in-degree</li>
  <li>A state variable updated before each indirect jump</li>
  <li>All original basic blocks present as switch cases, just scattered</li>
</ol>

<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="kt">bool</span> <span class="n">CFFDetector</span><span class="o">::</span><span class="n">match</span><span class="p">(</span><span class="n">mlil_inst</span> <span class="n">inst</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">auto</span><span class="o">*</span> <span class="n">func</span> <span class="o">=</span> <span class="n">inst</span><span class="p">.</span><span class="n">function</span><span class="p">();</span>
    <span class="k">auto</span> <span class="n">cfg</span> <span class="o">=</span> <span class="n">func</span><span class="o">-&gt;</span><span class="n">mlil</span><span class="p">();</span>

    <span class="k">auto</span> <span class="n">dispatcher</span> <span class="o">=</span> <span class="n">findDispatcher</span><span class="p">(</span><span class="n">cfg</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">dispatcher</span><span class="p">)</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">auto</span> <span class="n">stateVar</span> <span class="o">=</span> <span class="n">traceStateVariable</span><span class="p">(</span><span class="n">dispatcher</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">stateVar</span><span class="p">)</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="k">auto</span> <span class="n">edges</span> <span class="o">=</span> <span class="n">recoverEdges</span><span class="p">(</span><span class="n">cfg</span><span class="p">,</span> <span class="n">dispatcher</span><span class="p">,</span> <span class="n">stateVar</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">edges</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">traceStateVariable</code> is the fun part. You follow the state value backwards
through phi nodes, which gets messy when the obfuscator sprinkles in fake
assignments. I do a simple use-def walk, bail when I hit a constant or
function argument. Works most of the time.</p>

<h3 id="recipes">Recipes</h3>

<p>Not everything fits a template. Some people hit custom protectors. So there’s
a recipe system: JSON files describing a deobfuscation pass. No recompilation.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xor_strings"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Decode XOR-obfuscated string references"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"detectors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"constant_xor_loop"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"max_iterations"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
  </span><span class="nl">"requires"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"function_analysis"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The engine runs up to <code class="language-plaintext highlighter-rouge">max_iterations</code> passes. Some transforms only make sense
after a previous layer gets peeled. Run MBA simplification before CFF untangling
and the output is garbage. Order matters a lot.</p>

<h2 id="build-setup">Build setup</h2>

<p>CMake + Ninja. BN API vendored as a git submodule, pinned to a specific
revision. The API changes between BN versions and I learned this the hard way.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/mitsuakki/deobfninja
<span class="nb">cd </span>deobfninja
git submodule update <span class="nt">--init</span>
cmake <span class="nt">-B</span> build <span class="nt">-G</span> Ninja <span class="nt">-DCMAKE_BUILD_TYPE</span><span class="o">=</span>RelWithDebInfo
cmake <span class="nt">--build</span> build
cmake <span class="nt">--install</span> build
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">$BN_INSTALL_DIR</code> points to your BN install.</p>

<h2 id="numbers">Numbers</h2>

<p>5 MB obfuscated binary, 12-core machine:</p>

<table>
  <thead>
    <tr>
      <th>Approach</th>
      <th>Time</th>
      <th>Memory</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Python (BN API)</td>
      <td>42s</td>
      <td>1.2 GB</td>
    </tr>
    <tr>
      <td>C++ single thread</td>
      <td>3.1s</td>
      <td>180 MB</td>
    </tr>
    <tr>
      <td>C++ parallel</td>
      <td>0.8s</td>
      <td>240 MB</td>
    </tr>
  </tbody>
</table>

<p>~4× from parallelization on top of the C++ baseline. I’ll take it.</p>

<h2 id="things-that-broke">Things that broke</h2>

<p>BN’s IL changes between versions. Pin your submodule. I’m serious.</p>

<p>MLIL lifting fails sometimes on heavily obfuscated code. Always check
<code class="language-plaintext highlighter-rouge">mlil_inst.operation</code> before casting. I lost an evening to a segfault
that was just a null operation node.</p>

<p>Recipe ordering isn’t smart. It won’t warn you if two passes conflict.
You notice when the output gets worse instead of better.</p>

<h2 id="whats-missing">What’s missing</h2>

<p>More detectors: opaque predicate patterns, MBA simplification passes.
Maybe a pattern DSL so people can write detection rules without touching C++.
The <code class="language-plaintext highlighter-rouge">tests/</code> directory has sample binaries. PRs welcome, or open an issue
if something breaks.</p>]]></content><author><name>Alexis M. Daugé</name></author><category term="binaryninja" /><category term="c++" /><category term="deobfuscation" /><category term="plugin-dev" /><category term="deobfninja" /><summary type="html"><![CDATA[Binary Ninja’s C++ API is kind of a hidden gem. Everyone defaults to Python because it’s easy. But deobfuscation means iterating over every instruction in a function, pattern matching against obfuscator signatures, rewriting IL. Python just dies. Especially on real binaries.]]></summary></entry></feed>