A Disassembler for ROM Recovery

Travis Goodspeed

REcon 2025 · Day 3 · Main Track · Reverse Engineering

Overview

Mask ROM recovery — photographing a chip's die, identifying row and column lines, and reading out the physical bit array — gives you the bits in physical order. Getting from physical bits to executabl

Watch on YouTube

Visual summary for A Disassembler for ROM Recovery by Travis Goodspeed
Visual summary for A Disassembler for ROM Recovery by Travis Goodspeed

Key moments

  1. 2:36 Motivation: why build a custom disassembler instead of adding to existing tools
  2. 9:23 Gap in tooling: decompilers exist but assemblers/disassemblers are scarce
  3. 16:42 Origin story: writing a Z80 plugin on a bus — binaryAssembler framework
  4. 24:00 Practical application: patching ROM code via the disassembler
  5. 31:18 Code vs data: using global variable access patterns to identify code
  6. 38:05 ISA analysis: CISC word-addressed instruction encoding
  7. 48:31 Limitations: lack of training data for obscure architectures

A Disassembler for ROM Recovery

Speakers: Travis Goodspeed

Conference: REcon 2025

YouTube: https://www.youtube.com/watch?v=rpQQ6bSHCWw

Overview

Mask ROM recovery — photographing a chip's die, identifying row and column lines, and reading out the physical bit array — gives you the bits in physical order. Getting from physical bits to executable code requires solving the bit ordering problem: determining how the ROM's physical layout maps to the logical byte ordering that the CPU actually reads. In this REcon 2025 talk, Travis Goodspeed describes a novel approach to solving this problem: an assembler (and by design, a bidirectional disassembler) that scores candidate decodings for correctness by asking whether the result looks like valid code in the target architecture. The tool is simultaneously an assembler and disassembler, self-testing, and trivially retargetable to new architectures — addressing a gap in the embedded security tooling ecosystem that has persisted for decades.

Background

▶ Watch: Motivation: why build a custom disassembler instead of adding to existing tools (2:36)

Goodspeed is known in the hardware security community for tools including maskrom_tool — a CAD GUI for reverse engineering photographs of mask ROMs into logically ordered bytes — and the book Microcontroller Exploits from No Starch Press. Half the motivation for the assembler described in this talk came directly from maskrom_tool's need to verify when a candidate bit ordering is correct.

The core problem: mask ROMs store bits in a physical layout that may bear little obvious relationship to the logical byte ordering the CPU reads. The physical layout varies by chip, by manufacturer, and sometimes by customer (as with scrambled opcode tables on smart cards). After extracting the raw bit image from die photographs, maskrom_tool can enumerate many candidate orderings — different combinations of left-to-right vs. right-to-left row/column traversal, MSB on one side or the other, row-major vs. column-major layout, and so on — but identifying the correct one has traditionally required either domain knowledge or tedious manual comparison.

The Gap in Tooling

Goodspeed observes a significant asymmetry in the state of reverse engineering tooling: disassemblers have advanced enormously over the past 30 years (IDA Pro, Ghidra, Radare2, Binary Ninja, with decompilers, lifters, and sophisticated static analysis), while assemblers have barely progressed. For desktop architectures, we have NASM (x86 only) and YASM; for embedded targets, options are sparse. Macro assemblers can handle many languages but are difficult to extend accurately and do not anticipate unusual architectures.

The deeper problem: assemblers and disassemblers are typically implemented independently, even though they are logically inverses of the same operation. Goodspeed argues that the correct approach is to implement them from a single shared definition — one that describes each instruction in terms of both its assembly text and its machine code encoding. This yields a tool that automatically operates bidirectionally, can self-test every instruction definition, and can be retargeted to a new architecture in a few hours rather than days or weeks.

Key Findings

▶ Watch: Origin story: writing a Z80 plugin on a bus — binaryAssembler framework (16:42)

A Bidirectional, Self-Testing Assembler/Disassembler

The core design principle is: every instruction defined in the system is defined once, specifying both its assembler syntax and its machine code encoding simultaneously. From a single definition you get:

  • Assembly: text → machine code
  • Disassembly: machine code → text
  • Self-testing: assembling the canonical form of an instruction and disassembling the result must return to the canonical text; any mismatch catches encoding bugs immediately.
  • Likelihood scoring: given a block of candidate bytes, score the probability that it represents valid code in the given language.

This last capability is the key innovation for ROM recovery: maskrom_tool uses the scorer to evaluate candidate bit orderings and identify which ones produce plausible programs.

Architecture-Specific Decoding Heuristics

Different CPU architectures leave characteristic signatures that help validate a candidate decoding:

  • SM83 / Z80 (Game Boy ROM): The stack pointer is not initialized in hardware, so every valid program begins with a LD SP, nn instruction. If the first decoded instruction is not a load with the stack pointer as destination (first byte 0x31 for LD SP, nn), the decoding is almost certainly wrong. This constraint alone reduces valid candidates to three or fewer.
  • ARM 6 / ARM 32-bit (Clipper chip / MYK82): The most significant nibble of every 32-bit instruction word is the condition code. In practice, nearly every instruction is unconditional (AL), encoding as 0xE. Visible uniformity in the upper nibble of 32-bit words (faint/dark pattern in the die photo corresponding to 1110) provides a strong visual and computational confirmation of correct bit ordering and word alignment.
  • 6805 with scrambled opcodes (NDS smart card): The first byte of every instruction (the opcode) is scrambled, while operand bytes are not. A working knowledge of the scrambling table plus the observation that valid real code shows comparison-then-branch pairs at low offsets between them, repeating access to the same addresses (loop iteration), and contiguous subroutines within the mask ROM address range all serve as confirmation heuristics.

Retargeting Process

Adding support for a new architecture involves:

  1. Inheriting from the base assembler class in C++.
  2. Declaring register names.
  3. Adding instruction definitions using insert calls on Mnemonic objects, providing: instruction name, byte length, encoding value, encoding mask, help string, and a canonical example (used for self-testing).
  4. Using bit-mask fields to encode parameters (register selectors, immediate values, etc.) in the order they appear in the assembly syntax.

The bitwise OR of all masks for a given instruction must equal all-ones (0xFF for 8-bit instructions); any undefined bits produce a compile-time error rather than a silent bug. Ambiguity between overlapping instruction definitions is resolved explicitly via prioritize calls with optional numeric priority levels; the self-test framework detects unresolved ambiguity.

Goodspeed estimated adding Z80 support in approximately two hours on a bus to Logan Airport, sufficient to handle TI-85 exploit shellcode.

Output Formats

The assembler produces multiple output formats, all preserving original assembly comments:

  • Raw binary — for direct executable use.
  • Assembly listing — annotated bytes alongside source, useful for exploit development verification.
  • NASM-compatible output — enables collaboration with colleagues who lack the custom tool; any constant can be edited and reassembled without the original toolchain.
  • Go byte array with symbol hashmap — for embedding shellcode in Golang exploit programs with automatic symbol relocation; magic constants like target addresses move automatically as code changes.

Technical Deep Dive

▶ Watch: Practical application: patching ROM code via the disassembler (24:00)

The Game Boy Boot ROM (SM83 / Z80-like)

The Nintendo Game Boy's boot ROM enforces trademark protection: it reads the Nintendo logo from the cartridge, renders the sliding-logo animation, and only jumps into the game code if the logo matches. If you dump the boot ROM die photo and mark row and column lines in maskrom_tool, you get the physical bit array. The logical ordering problem then asks: which of the many possible traversal orderings produces valid SM83 code?

The key constraint is that every SM83 program must begin with LD SP, nn (opcode 0x31) because the stack pointer has no hardware reset value. Filtering candidate decodings to those beginning with 0x31 reduces the search space to three or fewer candidates, all of which a human reviewer can verify manually within minutes.

The Clipper Chip (ARM 6 / MYK82)

The Clipper chip (MYK82) was a U.S. government key-escrow encryption device from the Clinton era, manufactured for PCMCIA form factor. Goodspeed has been reverse engineering it since graduate school. The mask ROM is encoded in a lower metal layer; recovery requires decapping with nitric acid and delayering with hydrofluoric acid to expose the bit cells.

The MYK82 uses a 32-bit ROM with 16 major columns, each subdivided into two groups of 8 bit-columns. The bit ordering within each instruction word was determined through ARM architectural knowledge: in 32-bit ARM, the top nibble of every instruction is the condition code, and nearly all instructions are AL (0xE = 0b1110). Looking at the die photo, a column that is consistently "faint faint faint dark" from MSB to LSB encodes 1110 — this directly reveals both the MSB position and the intra-word bit ordering.

The word ordering within the ROM (top-to-bottom, left-to-right within each group of columns) was determined by the observation that the bottom of the image becomes uniformly zero (the program end) and a column-and-a-half at the far right is slightly fainter at the beginning (corresponding to the high-frequency 1110 pattern in the condition code field, which starts immediately at the beginning of the program).

Smart Card Opcode Scrambling (NDS F-Card / 6805 Variant)

The NDS satellite TV smart card uses a variant of the Motorola 6805 in which the opcode byte is scrambled: bit order 7, 6, 4, 5, 3, 0, 1, 2 with an additional MSB flip. Operand bytes are unscrambled. The scrambling is implemented in the via layer of the chip — the layer that also encodes mask ROM bits — allowing each customer to receive a uniquely scrambled variant with no changes to any other layer, from a single mask set for all other layers.

Implementing support in Goodspeed's assembler required only subclassing the base 6805 module and adding a scramble() method. Because the scrambling is symmetric (applying it twice returns the original), the same function serves for both encoding and decoding.

Validating the decoding of a dumped NDS exploit payload relied on structural observations:

  • A CMP A, 0x11 at address 7 followed immediately by a BNE at address 8 (compare-before-branch, unlikely in random data)
  • Read, increment, and write-back of a global at address 0x88 — a classic loop iteration pattern
  • A NOP sled (common in embedded exploits for alignment)
  • All subroutine references landing within the mask ROM address region

maskrom_tool Integration and Scoring

maskrom_tool uses the assembler as a C++ library, passing candidate decoding buffers and requesting either the assembly listing or a likelihood score — a measure of how much the candidate looks like valid code in the target language. The score is based on the fraction of bytes that decode as valid instructions (rather than undefined encodings), combined with structural features like the presence of expected initial instructions for the architecture.

This scoring enables maskrom_tool to automatically rank hundreds or thousands of candidate orderings and surface the most promising ones for human review, turning what was previously a manual search into a guided enumeration.

Demo / Proof of Concept

▶ Watch: Code vs data: using global variable access patterns to identify code (31:18)

The talk includes several concrete demonstrations:

  1. Game Boy ROM: Candidate decodings of the SM83 boot ROM, filtering by the LD SP, nn constraint, narrowing from many candidates to a handful. The correct decoding shows the well-known Game Boy boot sequence.
  1. TI-85 Shellcode: A working Z80 payload written in Goodspeed's assembler for the TI-85 graphing calculator exploit (exploiting a menu entry function pointer overwrite to achieve native code execution, with shellcode placed in the LCD buffer which is hardware-fixed at a constant address). The assembler output is shown in NASM format for distribution, as a Go byte array for the exploit harness, and as an annotated assembly listing.
  1. NDS Smart Card: Disassembly of a 3 KB EPROM dump obtained via Chris Gerlinsky's five-byte command exploit, confirmed as real code by structural analysis (compare-then-branch pairs, loop iteration on global 0x88, NOP sled, subroutines in the mask ROM range).
  1. Clipper Chip (MYK82): Die photo markup and bit ordering determination for the 32-bit ARM 6 mask ROM, with the arm condition-code field visible as a 1110 pattern in the upper bit columns.

Defensive Implications

▶ Watch: ISA analysis: CISC word-addressed instruction encoding (38:05)

While the primary application of this work is offensive security research (chip reverse engineering, exploit development for embedded targets), the defensive implications are significant:

  • Security-by-obscurity in mask ROM encoding is weak. The opcode scrambling on smart cards, varied bit orderings across chips, and other obfuscation measures can be defeated with moderate effort once the ROM is physically extracted. The real barriers are decapsulation and delayering, not the encoding itself.
  • Key escrow hardware (Clipper) can be fully reverse engineered given physical access, undermining any assurance model based on the secrecy of the cryptographic implementation.
  • Embedded systems with mask ROMs have highly predictable code structure (fixed address space, no ASLR, deterministic memory layout), making code reuse and return-to-mask-ROM attacks highly reliable once the ROM is recovered.
  • Shared tooling for ROM recovery reduces the cost of entry for researchers auditing embedded device security, which benefits both legitimate security research and chip authentication validation.

Key Takeaways

  • Assemblers and disassemblers should be the same tool, derived from the same instruction definitions. The bidirectional approach enables self-testing, ambiguity detection, and automatic likelihood scoring for ROM recovery.
  • Architecture-specific invariants (stack pointer initialization, condition code uniformity, compare-before-branch) are powerful constraints for validating candidate ROM decodings.
  • Retargeting to a new architecture takes hours, not days, if the tooling is designed with a shared parser and a clean inheritance model for instruction definitions.
  • Opcode scrambling in smart cards is implemented in the via layer — the same layer as the ROM data — meaning different customers receive uniquely scrambled variants without changes to any other manufacturing mask.
  • maskrom_tool + the assembler form a complete pipeline from die photograph to disassembled code, covering the full physical-to-logical bit ordering problem that has historically required expert manual work.
  • The self-testing discipline of automatically assembling and disassembling every defined instruction catches copy-paste encoding errors that would otherwise manifest as subtle bugs in shellcode or exploit payloads — bugs that Goodspeed notes cost him in his earlier smart card exploit work.

About the Speaker(s)

▶ Watch: Limitations: lack of training data for obscure architectures (48:31)

Travis Goodspeed is an independent hardware security researcher best known for tools including maskrom_tool (a CAD GUI for reverse engineering mask ROM die photographs) and the book Microcontroller Exploits published by No Starch Press. He is a recurring contributor to REcon and the International Journal of PoC‖GTFO. His work spans mask ROM recovery, smart card exploitation, microcontroller reverse engineering, and embedded system security research. He is the author of the bidirectional assembler/disassembler framework described in this talk.

Reviews

Dr. Zero (Offensive Security Researcher) — MUST SEE

Goodspeed builds a bidirectional assembler/disassembler from a single instruction definition set that scores candidate ROM decodings for validity, applies it to the Game Boy, the Clipper chip, and smart card opcode scrambling, and makes the entire mask ROM recovery pipeline tractable — this is exactly what REcon exists for.

Heather Calloway (CISO) — PASS

Exceptional hardware research scholarship — Goodspeed rebuilding the Clipper chip's ROM from die photographs is the kind of foundational work the security community needs, and it is entirely outside my assessment lane.

→ Top-rated talks at REcon 2025

All talks from REcon 2025