A flash dump makes a simple promise: read every address and leave with a copy of the chip.

Matthew “wrongbaud” Alt did not have that option. During a black-box assessment, his team had one specimen of the device. Useful debug interfaces were disabled, reading the flash in circuit had failed, and desoldering the chip risked damaging a target they could not replace.1

Unable to query the memory cleanly, he listened to the processor doing it for him.

A logic analyzer attached to the SPI bus captures the commands sent during startup and the responses from a 16 MiB Winbond W25Q128JV flash. Each read transaction already carries the two pieces needed for reconstruction: an address requested by the processor and the bytes returned from that address.1

The resulting image can then be opened in Binwalk like an ordinary firmware file.15

The result looks like a dump without carrying its guarantee of completeness. The whole method lives in that gap.

One transaction is a small piece of evidence

There is no large firmware.bin object moving over the bus. The controller selects flash, sends a command and address, then lets the chip answer.

In Alt's first capture, the clock runs at about 16 MHz. Another line drops to mark Chip Select. Then 0x03 appears, the W25Q128JV's standard READ opcode.1

The observed transaction reads address zero. The returned bytes begin 03 00 00 08, which form 0x08000003 when interpreted as a little-endian 32-bit word.1

At that point, an electrical trace regains a location in memory.

A READ at 0x001000 followed by 256 bytes therefore gives a fragment with a known home. Those bytes can go back at 0x001000 in the 16 MiB image.

Alt therefore initializes the reconstructed image to 0xFF, the erased state of NOR flash, and fills only offsets backed by captured reads. A second array records which bytes are genuinely covered.1

Diagram showing SPI address and data transactions placed into a 16 MiB image with unknown gapsThe final file preserves flash addresses, but its coverage is strictly limited to transactions that were observed. IRZ illustration from Matthew Alt / VoidStar Security

Scapy is doing something other than networking

The raw capture comes from Saleae Logic. Its SPI analyzer can export decoded bytes as CSV, including transaction timing and MOSI/MISO values.1

The remaining job is turning those columns back into usable transactions.

Alt uses Scapy, the Python tool best known for constructing and dissecting network packets. Scapy is more general than its most visible use: its documentation explains how new protocols can be defined from fields and layers.34

He defines an SPI transaction and a SPIFlashCmd layer. The opcode becomes an enumerated field; commands such as 0x03, 0x0B and 0x6B can carry a 24-bit address; fast-read variants add their dummy cycles. A read response is represented as a length-delimited byte blob.1

Once transactions have structure, rebuilding the image becomes mechanical:

  1. read a transaction;
  2. keep read responses;
  3. retrieve the address;
  4. copy returned bytes at that offset;
  5. mark the region as covered.

This is where Scapy earns its keep: downstream code gets logical transactions, not CSV columns that still resemble the wires.

That becomes important when the physical bus changes shape later in the same boot.

The first firmware has a hole

The file reconstructed from standard SPI is good enough for Binwalk to find familiar structures: gzip data, an LZMA block, startup strings and DDR initialization. Alt identifies the bootloader and Linux kernel image.1

The root filesystem is missing.

This is where a .bin file can lie by appearance. Unobserved regions were filled with 0xFF, making them visually identical to genuinely erased flash.

But “nothing was observed here” is not the same claim as “the flash really contains 0xFF here.”

The coverage map prevents those states from being confused.

Alt considers two possibilities: the capture did not cover the full boot, or the controller changed how it accessed the flash.1

The second answer is correct.

The bus widens during boot

Early in boot, IO0 and IO1 carry ordinary SPI data while IO2 and IO3 remain in their control-pin roles.1

Then those pins start switching too.

At the same time, the clock rises from about 16 MHz to 50 MHz. The kernel has reconfigured its flash controller for Quad-SPI.1

Later reads use opcode 0x6B, FAST_READ_QUAD_OUT. It is a 1-1-4 command: opcode and address still travel on one line, eight dummy clocks follow, then returned data uses all four lanes in parallel.1

With a higher clock and four data lanes, Alt estimates read throughput at roughly twelve times the early-boot rate.1

Saleae's default SPI decoder cannot follow this mode. Ignore IO2 and IO3 and the capture loses exactly the reads that begin loading the filesystem.

Diagram comparing 16 MHz standard SPI on one data lane with 50 MHz Quad-SPI on four lanes when loading the filesystemThe hole in the first file was not necessarily erased flash: the target had started reading over four lanes. IRZ illustration from Matthew Alt / VoidStar Security

67.3 percent is already a lot

Alt captures again with a Quad-SPI analyzer and adapts the import to its format.

This time the tool counts 36,839 FAST_READ_QUAD_OUT transactions. Out of the W25Q128JV's 16,777,216 bytes, it places 11,288,528 bytes, or 67.3 percent coverage.1

Binwalk now finds a SquashFS at 0x2D0000, around 3.16 MiB, and a JFFS2 filesystem at 0xDE0000.1

The beginning of the Quad-SPI image is still 0xFF.

Again, that does not prove the start of flash is empty. The Quad capture begins after the mode switch. Bootloader and kernel had already been read over single-lane SPI.

The captures complement each other in different address ranges:

  • standard SPI capture: early data, bootloader and kernel;
  • Quad-SPI capture: filesystem and large regions read later;
  • final image: fragments layered at their actual addresses.

Alt shows a dd command copying everything from 0x2D0000 upward from the quad image into the first image. His spidump tool can also merge multiple captures by layering observed reads.1

At this point it is no longer much like copying a file. Each capture documents another region of the address space, and the fragments are layered together.

What the processor never reads stays invisible

This limitation is not an implementation detail. It follows directly from the method.

An external programmer can arbitrarily request every flash address. A passive logic analyzer commands nothing. It only sees what the system chooses to read while observation is active.12

A recovery partition that is never mounted can remain absent. A key stored in a region untouched during boot remains unknown. Configuration tied to another product feature remains absent unless that feature runs during a capture.

More behaviours can still be provoked: several boots, recovery, configuration, networking, updates. Each scenario may add new regions to the map without desoldering the chip.

This is why the coverage map matters as much as the .bin file. Without it, 0xFF conflates two states: a value that was genuinely observed and missing data filled with a default.

Observe before extracting

The technique does not beat a physical dump in every situation.

If the flash can be read reliably with a programmer, a complete dump is more direct. Bus sniffing adds a logic analyzer, protocol decoding, signal-integrity problems, multiple captures and perhaps multiple bus modes.1

It has one valuable property, though: it is passive at the protocol level. It does not need to persuade the flash to talk to a second master while remaining soldered on the board. It watches a conversation that already works between processor and memory.

With a one-off specimen or a board that refuses in-circuit reads, that passive property becomes a serious reason to use the method.

It does offer a useful hardware reverse-engineering rule.

When a device will not let you read its state directly, watch the decisions it makes from that state.

Here, every boot-time address request reveals a few more bytes. Not all of flash. Only what the processor needed to know.

That can still be enough to rebuild a useful firmware image.