
Outil de reconstruction d'images SPI flash via des captures d'analyseur logique
spidump analyse les captures d'analyseur logique d'un appareil lisant sa mémoire flash SPI/QSPI NOR au démarrage et reconstruit l'image de la flash à partir du trafic de lecture — aucune lecture directe de la puce n'est nécessaire. Le jeu de commandes de la flash est modélisé avec Scapy, de sorte qu'une transaction de lecture est le même objet logique qu'elle soit passée sur un fil ou sur quatre.
| Format | En-tête | Remarques |
|---|---|---|
| Table de l'analyseur « SPI » de Saleae Logic 2 | name,type,start_time,...,mosi,miso | lignes enable/result/disable, un result par octet |
| Table brute par octet de Saleae | Time [s],Packet ID,MOSI,MISO | une ligne par octet, regroupées par Packet ID |
| Export QSPI-Analyzer | Time [s],Packet ID, Transaction State, DATA, Lines Used | machine à états : 1=cmd, 2=addr, 3=dummy, 4=data |
Le plugin QSPI-Analyzer pour Saleae Logic / KingstVIS décode le framing en mode quad (par ex. 0x6B Fast Read Quad Output, 1-1-4) qu'un simple analyseur SPI ne peut pas décoder.
# Standard single-lane SPI capture (Logic 2 SPI analyzer export)
python main.py examples/bigger-boot.csv -o recovered_flash.bin -v
# Quad SPI capture (QSPI-Analyzer export) — same command, format auto-detected
python main.py big-quad-spi-analyzed-dump.txt -o quad.bin -v
# Merge a single-lane leg (U-Boot) with a quad leg (rootfs) into one image.
# Devices that boot single-lane then switch to QSPI leave each region in a
# different capture; --merge splices them. Reads whose data lane width doesn't
# match the opcode (e.g. quad reads a single-lane analyzer mis-decoded) are
# dropped automatically, so a mixed capture can't poison the result.
python main.py single_lane_boot.csv --merge quad_boot.txt -o full.bin -v
# Raw exports with no chip-select framing (Packet ID constant) are split on
# idle gaps automatically (4x median byte period); override the threshold with
# --gap SECONDS, or --gap 0 to force Packet ID grouping
python main.py raw_boot.csv --gap 2e-6 -o raw.bin -v
# Force a flash size / fill byte if the auto-sizing guesses wrong
python main.py capture.txt --flash-size 0x1000000 --fill 0xFF -o out.bin
-v affiche le format détecté, un histogramme des transactions par opcode, la taille de flash choisie et la couverture de lecture. Les régions non lues sont remplies avec 0xFF (l'état NOR effacé), de sorte que la sortie peut être directement fournie à binwalk.
Une lecture de flash est la même transaction logique quelle que soit la largeur du bus : un opcode, une adresse et une suite d'octets de données. Les deux traces ci-dessous lisent les mêmes quatre octets (le magic SquashFS hsqs à 0x2D0000, tiré de bigger-boot.csv) en mode single-lane et en mode quad :
(Régénérez avec python docs/diagrams/mkwave.py ; nécessite wavedrom-cli, et rsvg-convert pour les PNG.)
{cmd, addr, data, lines}.SPIFlashCmd (Scapy) modélise l'en-tête opcode / adresse / dummy ; les
opcodes de lecture (single et quad : 0x03 0x0B 0x6B 0xEB 0x6C 0x0C …) ne sont que
des entrées dans READ_COMMANDS.reconstruct_image rejoue chaque lecture dans un bytearray, en suivant la couverture.Un démarrage ne lit que ce dont il a besoin, donc la couverture est partielle par conception — vous récupérez les régions que l'appareil a réellement touchées (bootloader, noyau, rootfs monté). Pour un appareil qui démarre en single-lane puis bascule en quad, capturez les deux segments et épissez-les.