SPI
- SPI
Why this matters
SPI is where the firmware lives.
I2C’s two-wire economy costs speed, so bulk storage went to SPI, and that decision is the single most consequential fact in hardware security. The 8-pin part sitting beside the SoC on almost every embedded board is SPI-NOR flash holding the entire firmware image, and it speaks a command set that is close to universal across manufacturers. Read that chip and you have the device’s code, its filesystem, its credentials, and its keys.
Everything in Dumping flash depends on what is on this page.
The four wires
| Line | Also called | Direction |
|---|---|---|
| SCK | CLK, SCLK | Controller → target |
| MOSI | SDI, DI, COPI | Controller → target |
| MISO | SDO, DO, CIPO | Target → controller |
| CS | SS, CE, NSS | Controller → target, active low |
Plus one CS per additional device. SCK, MOSI, and MISO are shared; chip select is what picks who is listening.
SPI is full duplex: every clock edge shifts one bit out on MOSI and one bit
in on MISO, simultaneously. There is no idle direction. When the controller
sends a command and expects a reply, it clocks out dummy bytes to generate the
clock the target needs to reply on — which is why you see 9F 00 00 00 going
out while -- EF 40 18 comes back.
The consequence that matters for capture
Chip select is out of band. A capture of SCK, MOSI, and MISO alone cannot tell you which device was being addressed, because the addressing information is on a wire you did not capture.
⚠️ Always capture CS. On a multi-device bus a capture without it is nearly worthless: your decoder cannot find transaction boundaries and you cannot attribute any byte to any device. This is the most common mistake in SPI analysis and it costs an entire lab session.
Clock polarity and phase
SPI has no single standard for when data is valid. Two bits define four modes:
| Mode | CPOL | CPHA | Clock idles | Data sampled on |
|---|---|---|---|---|
| 0 | 0 | 0 | Low | Rising edge |
| 1 | 0 | 1 | Low | Falling edge |
| 2 | 1 | 0 | High | Falling edge |
| 3 | 1 | 1 | High | Rising edge |
Mode 0 is overwhelmingly the most common and the right first guess. Mode 3 is second. Modes 1 and 2 are rare enough that if your decode fails in mode 0, try 3 before either of them.
Determining the mode from a capture takes one look: note whether the clock idles high or low between transactions (that is CPOL), then try both phases. The wrong phase produces bytes shifted by one bit, which reads as plausible garbage rather than obvious nonsense — so verify against something you can recognise, like a JEDEC ID.
The SPI-NOR flash command set
This is the payoff. Flash parts from Winbond, Macronix, Micron, GigaDevice, ISSI and others share a command set that is close enough to identical that one set of tools reads all of them.
| Command | Name | What it does |
|---|---|---|
0x9F |
Read JEDEC ID | Returns 3 bytes identifying the part |
0x03 |
Read Data | Read from a 24-bit address, unlimited length |
0x0B |
Fast Read | As above with a dummy byte, higher clock |
0x05 |
Read Status Register | Busy bit, write-enable latch, protection bits |
0x06 |
Write Enable | Required before any write or erase |
0x02 |
Page Program | Write up to one page (usually 256 bytes) |
0x20 |
Sector Erase | Erase 4 KB |
0xD8 |
Block Erase | Erase 64 KB |
0xC7 |
Chip Erase | Erase everything |
0x5A |
Read SFDP | Read the parameter table — see below |
For reading firmware you need exactly two: 0x9F to identify the part and
0x03 to read it.
Decoding a JEDEC ID
The three bytes are manufacturer, memory type, and capacity:
CS ‾‾‾\_______________________________/‾‾‾
MOSI 9F 00 00 00
MISO -- EF 40 18
| Byte | Value | Meaning |
|---|---|---|
| Manufacturer | 0xEF |
Winbond (JEDEC JEP106) |
| Memory type | 0x40 |
SPI NOR, W25Q series |
| Capacity | 0x18 |
2^0x18 = 16 MiB = 128 Mbit |
So: a Winbond W25Q128, 16 MiB. The capacity byte is a power of two, which
makes the arithmetic trivial once you notice it — 0x17 is 8 MiB, 0x18 is
16 MiB, 0x19 is 32 MiB.
Manufacturer codes worth recognising:
| Code | Manufacturer |
|---|---|
0xEF |
Winbond |
0xC2 |
Macronix |
0x20 |
Micron / ST |
0xC8 |
GigaDevice |
0x9D |
ISSI |
0x01 |
Spansion / Cypress / Infineon |
0xBF |
SST / Microchip |
SFDP: the part describing itself
0x5A reads the Serial Flash Discoverable Parameters table, standardised
as JEDEC JESD216 (current revision JESD216F, December 2021). It is a
structured description of the part’s own geometry, command set, and timing —
address width, erase sizes and their opcodes, supported read modes.
This is why modern tools can handle a flash part they have never heard of: they
ask it. When flashrom reports a chip it does not have in its database but
still reads it correctly, SFDP is why.
For an assessment it is a shortcut: if the JEDEC ID is unfamiliar, read SFDP rather than hunting for a datasheet.
Sniffing a live bus
Capture SCK, MOSI, MISO, and CS, then power-cycle the board. Nearly every bootloader begins by identifying the flash, so the first few milliseconds contain the JEDEC exchange and the start of the firmware read.
What to look for, in order:
0x9Fnear the start. Decode the reply, identify the part, and you now know the size of the dump you are about to take.0x03followed by three address bytes. The bootloader reading. The first bytes returned are the start of whatever is at that address — often a recognisable magic number.0x05polling. Status register reads in a tight loop mean the host is waiting for a write or erase to finish. If you see this on a device that should not be writing, that is interesting.
Worked example: reading the boot sequence
t=0.4ms CS↓ MOSI: 9F 00 00 00 MISO: -- EF 40 18 CS↑
t=0.6ms CS↓ MOSI: 03 00 00 00 MISO: -- 27 05 19 56 ... CS↑
The first transaction identifies a W25Q128. The second reads from address
0x000000, and the first four bytes returned are 27 05 19 56 — the U-Boot
legacy image magic. So the bootloader is checking for a valid uImage header at
the start of flash before deciding whether to boot it.
That single observation tells you the image format, which tells you what
binwalk will find, before you have dumped anything.
Bus contention, and why sniffing is not enough
To read the flash yourself you must become the controller — and the SoC already is one.
If both drive SCK or MOSI simultaneously in opposite directions, you have a low-impedance path between the supply rails through two output drivers. The mild outcome is a corrupted transaction. The severe one is a destroyed driver.
Remedies, in order of preference:
| Method | Risk |
|---|---|
| Hold the SoC in reset (assert NRST) | Some SoCs do not tri-state pins on reset; a watchdog may release reset mid-read |
| Power the flash from the programmer, board unpowered | Back-powering through the SoC’s ESD diodes: partially powers the SoC, which may then drive the bus, and can damage it |
| Desolder the flash | Thermal damage, pad lift, and rework afterwards |
If you spend more than an hour fighting contention, desolder. It is faster than being clever, and Dumping flash covers the mechanics.
Other things on the SPI bus
Flash is the prize, but not the only occupant:
- Displays. High-volume, one-directional traffic. Easy to identify by volume and by the absence of anything on MISO.
- Radios (nRF24, RFM95, CC1101). Register reads and writes, and the packet payloads pass across this bus in cleartext even when the radio link is encrypted — a genuinely useful attack position.
- Accelerometers and sensors. Small, periodic reads.
- SD cards, in SPI mode on simpler devices.
A radio on SPI is worth noting: it means you can read plaintext before it is encrypted and after it is decrypted, without touching the crypto at all.
Key takeaways
- SPI carries the firmware, which makes it the highest-value bus on most boards.
- Chip select is out of band; a capture without CS cannot attribute bytes to devices. Always capture it.
- Mode 0 first, mode 3 second; the wrong phase yields bit-shifted plausible garbage rather than obvious nonsense.
0x9Fthen0x03is all you need to identify and read a flash part, and the command set is near-universal across manufacturers.- The capacity byte of a JEDEC ID is a power of two —
0x18means 16 MiB. - SFDP lets an unknown part describe its own geometry, which is why tools can read chips they have never seen.
- A radio on SPI exposes plaintext on both sides of the link encryption.
References
- JEDEC JESD216F, Serial Flash Discoverable Parameters — https://www.jedec.org/standards-documents/docs/jesd216b
- JEDEC JEP106, Standard Manufacturer’s Identification Code — https://www.jedec.org/standards-documents/docs/jep-106ab
- Winbond W25Q128 datasheet — https://www.winbond.com/hq/product/code-storage-flash-memory/serial-nor-flash/
- sigrok SPI protocol decoder — https://sigrok.org/wiki/Protocol_decoder:Spi
- flashrom supported hardware — https://flashrom.org/supported_hw/index.html
- Bus Pirate SPI guide — https://buspirate.com/
- U-Boot legacy image format (
include/image.h) — https://github.com/u-boot/u-boot/blob/master/include/image.h
Related course pages: Protocols · Schedule · I2C · UART · Dumping flash · Tools of the Trade
🛠️ Maintenance note: the flash command set is stable and the JEDEC manufacturer codes change only by addition. What moves is
flashrom’s supported-chip list and the SFDP revision (JESD216F, December 2021, at time of writing). Re-check the manufacturer code table if a new vendor appears in the course stock — GigaDevice and ISSI parts have become much more common in cost-reduced consumer gear.