
Werkzeug zur Rekonstruktion von SPI-Flash-Images mittels Logic-Analyzer-Aufzeichnungen
spidump parst Logic-Analyzer-Aufzeichnungen eines Geräts, das beim Boot seinen SPI/QSPI-NOR-Flash liest, und rekonstruiert das Flash-Image aus dem Leseverkehr — ohne direkten Chip-Zugriff. Der Flash-Befehlssatz ist mit Scapy modelliert, sodass eine Lese-Transaktion dasselbe logische Objekt ist, egal ob sie über eine Leitung oder vier übertragen wurde.
| Format | Header | Hinweise |
|---|---|---|
| Saleae Logic 2 "SPI"-Analyzer-Tabelle | name,type,start_time,...,mosi,miso | enable/result/disable-Zeilen, ein result pro Byte |
| Saleae-Roh-Tabelle pro Byte | Time [s],Packet ID,MOSI,MISO | eine Zeile pro Byte, gruppiert nach Packet ID |
| QSPI-Analyzer-Export | Time [s],Packet ID, Transaction State, DATA, Lines Used | Zustandsautomat: 1=cmd, 2=addr, 3=dummy, 4=data |
Das QSPI-Analyzer-Plugin für Saleae Logic / KingstVIS dekodiert Quad-Mode-Framing (z. B. 0x6B Fast Read Quad Output, 1-1-4), das ein einfacher SPI-Analyzer nicht verarbeiten kann.
# Standard-Single-Lane-SPI-Aufzeichnung (Logic 2 SPI-Analyzer-Export)
python main.py examples/bigger-boot.csv -o recovered_flash.bin -v
# Quad-SPI-Aufzeichnung (QSPI-Analyzer-Export) — gleicher Befehl, Format automatisch erkannt
python main.py big-quad-spi-analyzed-dump.txt -o quad.bin -v
# Einen Single-Lane-Abschnitt (U-Boot) mit einem Quad-Abschnitt (rootfs) zu einem Image zusammenführen.
# Geräte, die Single-Lane booten und dann auf QSPI umschalten, hinterlassen jede Region in einer
# anderen Aufzeichnung; --merge fügt sie zusammen. Reads, deren Daten-Lane-Breite nicht
# zum Opcode passt (z. B. Quad-Reads, die ein Single-Lane-Analyzer falsch dekodiert hat), werden
# automatisch verworfen, sodass eine gemischte Aufzeichnung das Ergebnis nicht verfälschen kann.
python main.py single_lane_boot.csv --merge quad_boot.txt -o full.bin -v
# Roh-Exporte ohne Chip-Select-Framing (Packet ID konstant) werden automatisch an
# Idle-Lücken aufgeteilt (4x mediane Byte-Periode); den Schwellenwert mit
# --gap SECONDS überschreiben, oder --gap 0, um die Gruppierung nach Packet ID zu erzwingen
python main.py raw_boot.csv --gap 2e-6 -o raw.bin -v
# Eine Flash-Größe / ein Füllbyte erzwingen, falls die automatische Größenbestimmung falsch rät
python main.py capture.txt --flash-size 0x1000000 --fill 0xFF -o out.bin
-v gibt das erkannte Format, ein Transaktions-Histogramm pro Opcode, die gewählte Flash-Größe und die Leseabdeckung aus. Ungelesene Regionen werden mit 0xFF gefüllt (der gelöschte NOR-Zustand), sodass die Ausgabe direkt in binwalk eingespeist werden kann.
Ein Flash-Read ist dieselbe logische Transaktion, unabhängig von der Leiterbahnbreite: ein Opcode, eine Adresse und eine Folge von Datenbytes. Die beiden folgenden Traces lesen dieselben vier Bytes (die SquashFS-Magic hsqs bei 0x2D0000, entnommen aus bigger-boot.csv) im Single-Lane- und im Quad-Modus:
(Neu generieren mit python docs/diagrams/mkwave.py; benötigt wavedrom-cli und rsvg-convert für die PNGs.)
{cmd, addr, data, lines} um.SPIFlashCmd (Scapy) modelliert den Opcode-/Adress-/Dummy-Header; Lese-Opcodes (Single und Quad: 0x03 0x0B 0x6B 0xEB 0x6C 0x0C …) sind alle nur Einträge in READ_COMMANDS.reconstruct_image spielt jeden Read in ein bytearray ein und verfolgt dabei die Abdeckung.Ein Boot liest nur, was es benötigt, daher ist die Abdeckung konstruktionsbedingt partiell — man gewinnt die Regionen zurück, die das Gerät tatsächlich berührt hat (Bootloader, Kernel, gemountetes rootfs). Bei einem Gerät, das Single-Lane bootet und dann auf Quad umschaltet, sollten beide Abschnitte aufgezeichnet und zusammengefügt werden.