
Инструмент для восстановления образов SPI flash по захватам логического анализатора
spidump разбирает захваты логического анализатора при чтении устройством своей SPI/QSPI NOR
флеш-памяти при загрузке и реконструирует образ флеш-памяти из трафика чтения — без
необходимости прямого чтения чипа. Набор команд флеш-памяти смоделирован с помощью Scapy, поэтому
транзакция чтения — это один и тот же логический объект независимо от того, пришла ли она по одному проводу или
по четырём.
| Формат | Заголовок | Примечания |
|---|---|---|
| Таблица анализатора "SPI" Saleae Logic 2 | name,type,start_time,...,mosi,miso | строки enable/result/disable, один result на байт |
| Сырая побайтовая таблица Saleae | Time [s],Packet ID,MOSI,MISO | одна строка на байт, сгруппировано по Packet ID |
| Экспорт QSPI-Analyzer | Time [s],Packet ID, Transaction State, DATA, Lines Used | конечный автомат: 1=cmd, 2=addr, 3=dummy, 4=data |
Плагин QSPI-Analyzer для Saleae Logic / KingstVIS декодирует кадрирование в quad-режиме
(например, 0x6B Fast Read Quad Output, 1-1-4), которое обычный SPI-анализатор не может.
# Стандартный захват однопроводного SPI (экспорт анализатора Logic 2 SPI)
python main.py examples/bigger-boot.csv -o recovered_flash.bin -v
# Захват Quad SPI (экспорт QSPI-Analyzer) — та же команда, формат определяется автоматически
python main.py big-quad-spi-analyzed-dump.txt -o quad.bin -v
# Объединение однопроводного плеча (U-Boot) с quad-плечом (rootfs) в один образ.
# Устройства, которые загружаются по одному проводу, а затем переключаются на QSPI, оставляют каждый регион в
# отдельном захвате; --merge сшивает их. Чтения, ширина линии данных которых не
# соответствует опкоду (например, quad-чтения, неверно декодированные однопроводным анализатором),
# отбрасываются автоматически, поэтому смешанный захват не может испортить результат.
python main.py single_lane_boot.csv --merge quad_boot.txt -o full.bin -v
# Сырые экспорты без кадрирования по chip-select (Packet ID постоянный) разбиваются по
# паузам простоя автоматически (4x медианного периода байта); переопределите порог с помощью
# --gap SECONDS, или --gap 0 для принудительной группировки по Packet ID
python main.py raw_boot.csv --gap 2e-6 -o raw.bin -v
# Принудительно задать размер флеш-памяти / байт заполнения, если автоопределение размера ошибочно
python main.py capture.txt --flash-size 0x1000000 --fill 0xFF -o out.bin
-v выводит определённый формат, гистограмму транзакций по опкодам, выбранный
размер флеш-памяти и покрытие чтения. Непрочитанные регионы заполняются 0xFF (состояние
стёртой NOR), поэтому вывод можно безопасно подавать прямо в binwalk.
Чтение флеш-памяти — это одна и та же логическая транзакция независимо от ширины шины:
опкод, адрес и последовательность байтов данных. Две трассировки ниже читают
одни и те же четыре байта (магию SquashFS hsqs по адресу 0x2D0000, взятые из
bigger-boot.csv) в однопроводном и quad-режиме:
(Перегенерировать с помощью python docs/diagrams/mkwave.py; требуется wavedrom-cli, а для PNG — rsvg-convert.)
{cmd, addr, data, lines}.SPIFlashCmd (Scapy) моделирует заголовок опкода / адреса / dummy; опкоды
чтения (одиночные и quad: 0x03 0x0B 0x6B 0xEB 0x6C 0x0C …) — все просто
записи в READ_COMMANDS.reconstruct_image воспроизводит каждое чтение в bytearray, отслеживая покрытие.Загрузка читает только то, что ей нужно, поэтому покрытие частичное по замыслу — вы восстанавливаете регионы, которых устройство действительно касалось (загрузчик, ядро, смонтированный rootfs). Для устройства, которое загружается по одному проводу, а затем переключается на quad, захватите оба плеча и сшейте их.