文章总结: 本文介绍一种不焊拆芯片提取SPINORFlash固件的方法:通过夹住芯片并用逻辑分析仪抓取启动期总线流量,利用Scapy解析SPI协议事务,从READ命令重建固件镜像。单线模式可重建bootloader和kernel,QSPI模式可重建rootfs,合并两段数据后可用binwalk提取完整文件系统。文章提供四步实操流程,并强调信号完整性注意事项,对硬件安全评估和供应链审计有参考价值。
综合评分: 85
文章分类: 渗透测试,逆向分析,安全工具,IoT安全
不焊拆芯片也能抽固件:用 Scapy 从 SPI/QSPI 抓包重哈镜像
黑卷
黑卷
赛博安全攻防日记
2026年9月26日 17:19
上海
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
不焊拆芯片也能抽固件:用 Scapy 从 SPI/QSPI 抓包重建镜像
很多人用 Scapy 拆以太网包、甚至汽车总线,却忘了它其实是一套通用二进制协议框架:不管字节从网卡、CAN 还是逻辑分析仪来,字段定义与层叠解析都一样好用。VoidStar 这篇实战写的是:只 clip 一片 SPI NOR、抓开机流量,用 Scapy 建层解析,再把 READ 回填成整片固件镜像——不用焊下 Flash。
为什么不直接拆芯片?
目标是一台「只有一台」的嵌入式设备。焊下 NOR 有物理损伤风险,别的产品还可能触发防拆。于是他们夹住 Winbond W25Q128JV(16 MiB SPI NOR,SOIC8),用 Saleae 逻辑分析仪抓启动期总线流量。
W25Q 族 SOIC8 引脚(含本文 W25Q128JV)
UART/调试口基本静默;在线读 Flash 又撞上经典的总线争用。客户也不给固件——黑盒 SPI 嗅探成了主路径。
SPI 速览(站 Flash 视角)
关心四根线:CLK、CS#(低有效,框定一笔事务)、MOSI/DI、MISO/DO。CS# 拉低开新命令,拉高结束。窗口内主机依次送出 opcode、可选地址、可选 dummy,再收/发数据。SPI 全双工:读的时候 MOSI 仍在移位「无关」字节,真正数据在 MISO。
抓包:先认出哪根是哪根
夹子 + 上电,先看全景:
开机 SPI 抓包(夹子和飞线在高速下容易伤信号完整性)
放大后:约 16 MHz 的周期线是 CLK;另一根线拉低后才有活动——那是 CS#。另外两根在同一窗口里跑数据。
单笔 SPI 事务放大
标注:CS 框事务;一根先发命令,另一根回数据
命令线上有一段「毛刺」,脉宽比一个时钟周期还窄——按当前采样率采不到真数据,当作 SI 噪声即可。
噪声区放大——亚时钟脉宽 ⇒ 不是有效 SPI 采样
挂上 Saleae SPI 分析器,对照手册:首字节 0x03 就是 READ。
| Opcode | 含义 |
| — | — |
| 0x9F | JEDEC_ID |
| 0x03 | READ |
| 0x0B | FAST_READ |
| 0x6B | FAST_READ_QUAD_OUT |
| 0x05 | RDSR1 |
| 0x06 / 0x02 | WREN / PAGE_PROGRAM |
| 0x20 / 0xD8 | SECTOR_ERASE / BLOCK_ERASE |
Saleae 里把 CLK/CS/MOSI/MISO 对上通道
分析器给出的字节流;首字节 0x03 对上 READ
手册里的 Read Data (03h) 时序:opcode → 24 位地址 → 数据
WaveDrom:单线 READ 的结构示意
地址 0 的 READ;响应以 03 00 00 08 开头;阴影 MOSI 字节为 don’t-care
把 Saleae 导出成 CSV(enable / result / disable 行)。每个 CS 窗口就是一笔事务的 MOSI/MISO 字节流——正好喂给 Scapy 模型。
用 Scapy 给 Flash 建层
薄层设计:SPI(哪条 CS)、SPIFlashCmd(opcode + 用 ConditionalField 挂可选 24 位地址 / dummy)、再加上读数据 / 状态寄存器响应层。核心摘录:
class SPIFlashCmd(Packet):
COMMANDS = {
0x03: "READ", 0x0B: "FAST_READ", 0x6B: "FAST_READ_QUAD_OUT",
0x02: "PAGE_PROGRAM", 0x05: "RDSR1", 0x06: "WREN",
0x20: "SECTOR_ERASE", 0xD8: "BLOCK_ERASE", 0x9F: "JEDEC_ID",
}
CMD_HAS_ADDR = {0x03, 0x0B, 0x6B, 0x02, 0x20, 0xD8}
CMD_HAS_DUMMY = {0x0B, 0x6B}
fields_desc = [
ByteEnumField("cmd", 0x03, COMMANDS),
ConditionalField(ThreeBytesField("addr", 0),
lambda p: p.cmd in p.CMD_HAS_ADDR),
ConditionalField(ByteField("dummy", 0),
lambda p: p.cmd in p.CMD_HAS_DUMMY),
]
CSV 拆成 {mosi, miso} 事务后,MOSI 交给 SPIFlashCmd;头部长度直接标出 MISO 有效载荷起点。0x05 状态字节用 FlagsField 解成 BUSY / WEL / BP0…,比魔法数字好读得多。
附带发现:开机在擦写 Flash
流量并不只有读:启动中段出现 两千多次 PAGE_PROGRAM和一次BLOCK_ERASE(0xD8)。把重复的RDSR1轮询折叠后,顺序和手册一致:空闲 →WREN→ 擦0x730000→ 轮询BUSY约 21 万次 → 再WREN→ 每次 32 字节写满该 64 KiB 块(约 400 ms)——典型配置区重写。下文重建只回放READ;若要看写入内容,数据在 MOSI 的Raw 载荷里。
从 READ 重建镜像
16 MiB 缓冲先填 0xFF(擦除态 NOR 默认),把每笔 READ 画到对应地址并记覆盖率:
def reconstruct(path, flash_size=0x1000000, fill=0xFF):
image = bytearray([fill]) * flash_size
coverage = bytearray(flash_size)
for tx in parse_transactions(path):
pkt, resp = transaction_to_packets(tx)
if not isinstance(resp, SPIFlashReadResp):
continue
addr = pkt[SPIFlashCmd].addr
data = resp.data
end = min(addr + len(data), flash_size)
image[addr:end] = data[:end - addr]
coverage[addr:end] = b"\x01" * (end - addr)
return bytes(image), coverage
单线开机抓包能拼出 bootloader + kernel(binwalk 可见 gzip/LZMA、DRAM 初始化字符串),但 没有 rootfs。常见解释:抓包窗口不够长,或控制器已经 切到 QSPI。
QSPI:rootfs 为什么丢了?
Quad 模式下,原来的 /WP、/HOLD 变成 IO2/IO3;命令/地址/数据可走 1~4 线。判据:这两根脚突然开始跑数据,时钟从约 16 MHz → 50 MHz(四线合计吞吐大约 12 倍)。
四线读:opcode/地址仍可走单线,数据阶段占满 IO0–IO3
切换前:标准 SPI,时钟大约 16 MHz
切换后:同一时间尺度下边沿密了约三倍,再乘四线,吞吐大约一个数量级
切换后读命令是 0x6B Fast Read Quad Output(1-1-4)。Saleae 自带 SPI 分析器不懂 QSPI——要挂 QSPI 插件,并把抓包拆成「单线段 + 四线段」。导出表结构不同(command/address/dummy/data + Lines Used),但逻辑事务仍可落进同一套 SPIFlashCmd / reconstruct——Scapy 建的是事务模型,不是线数模型。
只跑四线重建约覆盖 67%,能看到 SquashFS @ 0x2D0000 和 JFFS2 @ 0xDE0000,而 0x2D0000 以下仍是 0xFF(boot/kernel 在切换前用单线读完了)。把单线镜像与四线镜像合并(dd 或 spidump --merge)后 binwalk 齐活;再 unsquashfs 就是正常 Linux 根目录(bin、etc、web…)。
开源工具:wrongbaud/spidump。合并时注意丢掉「用错线宽解出来的脏读」——例如单线分析器误解的四线事务,否则覆盖率和哈希都会漂。
实战里还可以交叉验证:对重建镜像跑 binwalk -e、对比已知 OTA(若有)、或把 SquashFS 里的版本字符串、证书、默认口令清单拉出来做资产盘点。单线阶段若已看到 PAGE_PROGRAM 轨迹,把 MOSI 载荷另存一份「启动期写入日志」,往往比静态固件更能解释「设备为什么每次开机都要改那 64 KiB」。
信号完整性提醒:文中已注明夹子与飞线在高速下容易出问题。若解码偶发错位,优先缩短线长、降低采样前端负载,或在更低时钟的冷启动窗口先抓一轮,再专门抓 QSPI 高速段——两段覆盖合并,比硬扛一次满速长抓更稳。
这条链对评估意味着什么
实操上可以把流程收成四步,方便复用到别的 SOIC8 NOR:
-
夹住再上电
:优先确认 CLK/CS,再辨 DI/DO;亚时钟毛刺先当 SI,不要硬解成协议。
-
导出按 CS 切事务
:
enable→字节→disable就是一帧;MOSI 喂 Scapy,MISO 按头部长度切片。 -
先单线拼 boot/kernel
:覆盖率不够或 binwalk 缺 rootfs 时,拉长窗口看
/WP、/HOLD是否突然有数据、时钟是否跳档。 -
四线另开分析会话再 merge
:同一套
reconstruct,只是输入多了一份 QSPI 导出。
对防守与供应链审计:若产线/现场只能「只读探测」、不能破坏样品,这种 clip + 重建比热风拆片更可控;同时也提醒——「焊盘没露 UART 就安全」并不成立,总线侧仍可能在开机窗口把整片布局读给你看。配置区若在启动时整块擦写,抓包轨迹本身就是一条高价值行为指纹(谁在写、写哪、写多久)。
同一套字段模型以后还能挂到 TPM、显示屏、私有传感器帧;作者也预告了 I2C 方向的 Scapy 插件。对公众号读者更实用的点是:把「协议层」从网线搬到飞线,工具链不用换脑子。
作者:Matthew Alt / VoidStar Security
免责声明:
本人所有文章均为技术分享,均用于防御为目的的记录,请勿用于其他用途,否则后果自负。
更多 IoT / 车联网 / 机器人 / AI 安全资料在星球里,扫码进「车联网攻防日记」。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:赛博安全攻防日记 黑卷
黑卷《不焊拆芯片也能抽固件:用 Scapy 从 SPI/QSPI 抓包重哈镜像》