Un dump de flash a une promesse simple : lire toutes les adresses et repartir avec une copie du composant.
Matthew « wrongbaud » Alt n’avait pas cette option. Pour une analyse black-box, son équipe ne disposait que d’un exemplaire du matériel. Les interfaces de debug utiles étaient désactivées, la lecture de la flash en circuit échouait, et dessouder la puce risquait d’endommager une cible irremplaçable.1
Faute de pouvoir interroger proprement la mémoire, il écoute le processeur le faire à sa place.
Un analyseur logique posé sur le bus SPI capture les commandes émises au démarrage et les réponses de la flash Winbond W25Q128JV de 16 Mio. Chaque transaction de lecture contient déjà les deux pièces nécessaires à une reconstruction : une adresse demandée par le processeur et les octets renvoyés depuis cette adresse.1
L’image obtenue peut ensuite être ouverte dans Binwalk comme un firmware ordinaire.15
Le résultat ressemble à un dump sans en avoir la garantie d’exhaustivité. Toute la méthode tient dans cet écart.
Une transaction vaut une petite preuve
Sur le bus, il n’existe aucun gros objet « firmware.bin ». Le contrôleur sélectionne la flash, envoie une commande et une adresse, puis laisse la puce répondre.
Sur la première capture d’Alt, la ligne d’horloge tourne autour de 16 MHz. Une autre ligne descend pour marquer le Chip Select. Puis apparaît 0x03, opcode standard READ de la W25Q128JV.1
La transaction observée lit l’adresse zéro. Les octets qui reviennent commencent par 03 00 00 08. Pris comme mot 32 bits little-endian, cela donne 0x08000003.1
À cet instant, la trace électrique retrouve une adresse dans la mémoire.
Un READ à 0x001000 suivi de 256 octets fournit donc un fragment localisable. Ces octets peuvent reprendre leur place à 0x001000 dans l’image de 16 Mio.
Alt initialise donc son image reconstruite entièrement à 0xFF, valeur d’un NOR flash effacé, puis remplit uniquement les offsets pour lesquels il possède une vraie lecture. Une deuxième structure marque en parallèle les octets effectivement couverts.1
Scapy sert ici à autre chose qu’au réseau
La capture brute vient de Saleae Logic. L’analyseur SPI sait déjà exporter les octets décodés en CSV, avec le début des transactions, les valeurs MOSI et MISO et leur timing.1
Il reste à transformer ces colonnes en transactions exploitables.
Alt utilise Scapy, outil Python connu pour construire, décoder et manipuler des paquets réseau. Mais Scapy est plus général que son usage le plus visible : sa documentation explique comment définir de nouveaux protocoles à partir de champs et de couches.34
Il crée donc une représentation d’une transaction SPI et une couche SPIFlashCmd. L’opcode devient un champ énuméré ; les commandes 0x03, 0x0B ou 0x6B peuvent porter une adresse 24 bits ; les variantes rapides ajoutent leurs cycles dummy. La réponse de lecture devient simplement un blob de longueur connue.1
Une fois les transactions structurées, reconstruire l’image devient très mécanique :
- lire une transaction ;
- vérifier qu’il s’agit d’une réponse de lecture ;
- récupérer son adresse ;
- copier les octets reçus à cet offset ;
- marquer cette zone comme couverte.
C’est là que Scapy paie son loyer : le code suivant manipule des transactions logiques, plus des colonnes CSV qui imitent encore les fils.
C’est ce qui permettra ensuite de réutiliser la même reconstruction quand le bus change physiquement de forme.
Le premier firmware est troué
Le fichier produit par la capture SPI standard est assez bon pour que Binwalk y retrouve des structures familières : données gzip, bloc LZMA, chaînes de démarrage et initialisation DDR. Alt identifie ainsi le bootloader et l’image du noyau Linux.1
Mais le root filesystem manque.
C’est là qu’un fichier .bin devient trompeur. Les zones non observées ont été remplies avec 0xFF; visuellement, elles sont identiques à de la mémoire réellement effacée.
Or « aucun octet observé ici » ne veut pas dire « la flash contient réellement 0xFF ici ».
La carte de couverture empêche de confondre les deux.
Alt envisage d’abord deux possibilités : la capture s’est arrêtée trop tôt, ou le contrôleur a changé de mode d’accès à la flash.1
La deuxième est la bonne.
Le bus change au milieu du boot
Au début du boot, IO0 et IO1 transportent les données en SPI classique. IO2 et IO3 restent encore dans leur rôle de broches de contrôle.1
Puis elles commencent elles aussi à commuter.
En parallèle, l’horloge passe d’environ 16 MHz à 50 MHz. Le noyau a reconfiguré le contrôleur pour utiliser le Quad-SPI.1
Les lectures suivantes utilisent notamment l’opcode 0x6B, FAST_READ_QUAD_OUT. C’est une commande dite 1-1-4 : opcode et adresse partent encore sur une seule ligne, huit cycles dummy suivent, puis les données reviennent en parallèle sur quatre lignes.1
À fréquence plus élevée et avec quatre voies de données, Alt estime le débit de lecture environ douze fois supérieur à celui du début du boot.1
Le décodeur SPI standard de Saleae ne sait pas suivre ce mode. Si IO2 et IO3 restent ignorées, la capture perd justement les données lues lorsque le système charge son filesystem.
67,3 % est déjà énormément
Alt refait donc la capture avec un analyseur Quad-SPI et adapte l’import à son format.
Cette fois, l’outil compte 36 839 transactions FAST_READ_QUAD_OUT. Sur les 16 777 216 octets de la W25Q128JV, il replace 11 288 528 octets, soit 67,3 % de couverture.1
Binwalk retrouve alors un SquashFS à 0x2D0000, d’environ 3,16 Mio, puis un JFFS2 à 0xDE0000.1
Mais le début du fichier Quad-SPI est rempli de 0xFF.
Encore une fois, ce n’est pas la preuve que le début de la flash est vide. La capture Quad a commencé après le changement de mode. Bootloader et noyau avaient déjà été lus en SPI simple.
Les deux captures se complètent, mais pas aux mêmes endroits :
- capture SPI simple : bonnes données du début, bootloader et noyau ;
- capture Quad-SPI : filesystem et grandes régions lues plus tard ;
- image finale : superposition des fragments à leurs offsets réels.
Alt montre un dd qui copie tout depuis 0x2D0000 de l’image quad vers l’image initiale ; son outil spidump propose aussi un mode --merge pour superposer plusieurs captures.1
On n’est plus vraiment dans la copie de fichier. Chaque capture documente une autre portion de l’espace d’adresses, puis les morceaux se superposent.
Ce que le processeur ne lit pas reste invisible
La limite n’est pas un détail de l’implémentation. Elle découle directement de la méthode.
Un programmateur externe peut demander arbitrairement chaque adresse de la flash. Un analyseur logique passif ne commande rien. Il voit uniquement ce que le système choisit de lire pendant la fenêtre observée.12
Une partition de secours jamais montée peut donc rester absente. Une clé stockée dans une région que le boot ne touche pas restera inconnue. Une configuration liée à une autre fonction du produit restera absente si cette fonction n’est jamais déclenchée pendant la capture.
On peut toutefois provoquer davantage de lectures : plusieurs boots, recovery, configuration, réseau, mises à jour. Chaque scénario peut ajouter des régions à la carte sans dessouder la puce.
C’est aussi pourquoi la carte de couverture est aussi importante que le fichier .bin. Sans elle, 0xFF mélange deux états très différents : valeur réellement observée et donnée absente remplacée par une valeur par défaut.
Observer avant d’extraire
La technique ne bat pas un vrai dump dans tous les cas.
Si la puce peut être lue proprement avec un programmateur, un dump exhaustif est plus direct. Le sniffing ajoute un analyseur logique, le décodage du protocole, des questions de signal integrity, plusieurs captures et éventuellement plusieurs modes de bus.1
Mais il possède une propriété précieuse : il est passif du point de vue du protocole. Il n’a pas besoin de convaincre la flash de parler à un second maître pendant qu’elle reste soudée sur la carte. Il observe la conversation qui fonctionne déjà entre le processeur et sa mémoire.
Avec un exemplaire unique ou une carte illisible en circuit, ce caractère passif devient une raison sérieuse de choisir cette voie.
Elle donne en revanche une bonne règle de travail en reverse engineering matériel.
Quand l’objet ne vous laisse pas lire directement son état, observez les décisions qu’il prend à partir de cet état.
Ici, chaque adresse demandée au boot révèle quelques octets de plus. Pas toute la flash. Juste ce que le processeur a eu besoin de savoir.
Cela suffit déjà à reconstruire un firmware exploitable.