All posts
7 min read

Reverse engineering across domains

What happens when you point one AI-assisted decompilation workbench at firewall firmware, two pinball machines, a 2001 MMO client, and a home server — and let bytes, not vibes, decide who is right.

reverse engineeringfirmwarerendering

Most of my first half of 2026 went into one thing: byte-exact decompilation of a handful of old console games, driven by fleets of AI agents wired into a disassembler and a battery of verification tools. The interesting question was never “can an agent write plausible C?” Plausible is cheap. The question was whether the machine around the agent — the tooling, the orchestration, and especially the verification oracles that can prove a guess wrong — would generalize to problems that had nothing to do with games.

So I pointed it at four unrelated things. This post is about what happened.

The through-line: trust the bytes

Every project below shares one obsession: a verification oracle that trusts bytes over rendered output. An AI agent will happily hand you decompiled code that looks correct. The only defense is an independent check that can disprove it — assembly that has to match instruction-for-instruction, an emulator that has to produce identical behavior, a framebuffer that has to be pixel-identical to the original. Without that, you are just generating confident-sounding text. With it, you have a research tool.

The second shared thread is an honesty ethos I care about more than any single result: catching your own over-claim is a win, not a loss. A finding you talk yourself out of, or a “match” you reject because it cheated, is a first-class outcome. That discipline turns out to matter enormously once AI agents are optimizing against whatever metric you give them, because they will find every hollow way to satisfy it.

Firmware security: methodology, not exploits

The first retarget was the highest-stakes one: taking the game-decomp workbench and aiming it at the stripped firmware of enterprise network appliances — the kind of boxes that sit at the edge of corporate networks. This part I will keep deliberately high level, because the point here is method and posture, not anything actionable.

The workbench collected and indexed a large corpus of firmware images across many vendors, defeated several proprietary container formats to extract the userspace binaries inside, and ran a managed fleet of disassembler workspaces against them. Individual targets were enormous — stripped, position-independent executables tens of megabytes in size, each taking many minutes just to auto-analyze.

The engineering I am proud of is the verification harness. Game decompilation always has original assembly to diff against. Firmware does not. So the guiding creed became “trust the bytes, not the tool’s labels” — and it was hard-won, because at one point the disassembler’s reported addresses were skewed from the real on-disk offsets, which had already produced false analysis. The fix was a layer that checks against the actual instruction bytes rather than the tool’s rendering: cross-checking decompiled pseudo-C against a raw disassembly to catch dropped calls and arguments the C silently hides, and running functions under CPU emulation to prove or disprove behavioral equivalence against a candidate reimplementation.

The findings themselves went through responsible-disclosure triage, and the most important discipline was self-downgrade. Several promising leads got walked back — one all the way from “confirmed issue” to “not a finding” — once closer analysis showed the code was actually safe. That candor is the entire posture. A vulnerability you disprove is as valuable as one you confirm, because the alternative is crying wolf at a vendor. The work also fed a real upstream bug fix back into the open-source disassembler bridge it was built on. It ran as one intense multi-day burst and has been paused since, with the methodology intact.

Pinball firmware, and a research payoff

Next I reverse-engineered the firmware of two pinball machines I physically own — which changes the stakes, because the goal is eventually to re-flash the real hardware.

The newer machine runs embedded Linux on an ARM processor. Because I own it, I treated byte-exact matching as the method for learning an undocumented platform, and the product as a buildable homebrew modding kit. Verified milestones included several hundred of a few thousand tracked functions matched byte-for-byte, every peripheral node-firmware image decrypted, and a full-system emulator boot of the real firmware all the way to a shell — which required defeating a cascade of graphics-stub, device-shim, and filesystem-remap blockers before it would come up.

The research payoff lives here. I built a headless, cost-guarded LLM decompilation loop: take several independent first-shot attempts, keep the best, then run a series of refinement turns each conditioned on the diff against the target, and stop on a match, a plateau, or a budget cap. The counter-intuitive finding, which I think generalizes: non-reasoning models beat reasoning models at byte-exact decompilation, because the reasoning models burn their whole budget “thinking” and return nothing. That loop and its cost guard got generalized into reusable infrastructure and flowed back into the flagship console work as a new, cross-architecture source of training data.

Equally important was an honesty audit partway through. I discovered that earlier campaigns had scored inline-assembly transcriptions as byte-exact “matches” — hollow wins that prove nothing about reconstructing real source. I quantified the taint, shipped a guard that rejects any candidate containing raw assembly, and split the metric into honest tiers. That is the same self-correcting discipline as the firmware disclosure walk-backs, applied to the very number the agents were optimizing against.

The older machine, on a bare-metal ARM platform, had no known original compiler, so byte-exact matching was off the table. Correctness there was validated behaviorally: rebuild the firmware, run it in an emulator, and diff runtime behavior — display frames, solenoids, lamps, sound — against the original. The rebuild matched the original’s exact byte count, and a scenario-driven A/B harness reached zero display-frame mismatches across every shared frame. A self-contained cryptanalysis side-quest was needed just to recover the period-correct compiler in the first place. This project is paused but reached its goal.

Render parity on a totally different substrate

Then something genuinely different: a three-day sprint inside a collaborator’s research project where hundreds of agents inhabit a sandboxed 2001-era MMO world. To let bots see and act faithfully, the project needed a trustworthy client — and the only one that existed was a signed, closed-source, heavily obfuscated JAR.

The obfuscator’s main trick was injecting bogus exception-handling ranges that broke the modern Java decompiler’s control-flow analysis; hundreds of methods across dozens of classes simply would not decompile. Rather than hand-patch them, I wrote a custom bytecode pass that strips the bogus ranges before decompilation, which cut the failures to a handful, cross-validated against a second independent decompiler.

The acceptance bar was byte-identical framebuffers across three independently built engines: the deobfuscated client, an untouched reference client as an oracle, and a from-scratch port of a software 3D renderer in another language. The crucial call was to preserve the original’s exact arithmetic quirks — shift precedence, unsigned right shifts, integer overflow wraparound — rather than clean them into idiomatic code, because any “correction” would have broken parity. A permanent pixel-diff regression harness was the mergeable proof of equivalence. Same discipline as the console work; completely different toolchain.

The mundane counterpart

The last one is not reverse engineering as its purpose — it is proof that the same spec-first, safety-first rigor produces a clean product. It is a small home-server web app that reads a household’s music-listening history to decide which game content stays active on a drive versus gets moved — never deleted — into a backup folder.

Designed, built, tested, and deployed in a single evening, it opened not with code but with a live survey of the drive committed as pinned facts, then a full design and interface contract, and only then the implementation and a large test suite. The safety-critical core is the only module allowed to mutate the drive, an isolation enforced by a dedicated architectural test: every move is dry-run by default, type-to-confirm, reversible, and guarded by a reconcile pass that halts on any drift between the database and physical reality. The rationale is blunt — a wrong move silently deletes a song from a family’s game — which is why it has correctly never fired for real. It also carries a genuine thread of the same craft: a game console’s package format parsed from scratch, byte offsets and all, in service of a completely mundane task.

What it adds up to

None of these are detours. Taken together they are the actual argument: one AI-assisted reverse-engineering discipline, and one person directing agent swarms against it, carried credibly across firewall-class firmware, two pinball platforms, a JVM MMO client, and a household web app. What made that travel was never the agents. It was the insistence, everywhere, on an oracle that can tell you when you are wrong — and the willingness to listen to it.

Reverse engineering across domains | Free Wortley