2、固件提取技术:硬件提取方法与软件提取方法

固件审计的第一步,说白了就是先把固件搞到手。你连二进制都拿不到,后面分析得再漂亮也是白搭。我这些年做过的项目里,大概有七成情况能用软件方法搞定,剩下三成就得动烙铁和逻辑分析仪了。这一章咱们就把两种路子都捋一遍。

核心原则:能用软件就别动硬件,能在线就别拆芯片。但真到了非拆不可的时候,手法一定要稳。

2.1 硬件提取方法

硬件提取,就是直接跟芯片物理层打交道。我刚开始干这行时,总觉得拆芯片是件很酷的事,后来发现拆坏过几块板子就老实了。嗯,这里要注意——硬件提取是最后的手段,但也是最有保障的手段。

2.1.1 SPI Flash 读取

SPI Flash 是嵌入式设备里最常见的固件载体。你想想看,一个 8 脚的小芯片,里面可能装着整个路由器的操作系统。读取它的方法其实很直接:用编程器夹住或者焊线出来,然后通过软件把数据 dump 下来。

操作步骤:

  1. 识别芯片型号——看丝印,比如 Winbond 25Q128、MXIC 25L6406E。记下型号后去查 datasheet,确认电压和引脚定义。
  2. 连接编程器——我建议用 SOP8 夹子夹住芯片,如果夹不住就飞线。注意方向,别把 1 脚搞反了。
  3. 读取数据——用 flashrom、SPI Flash 编程器软件(如 CH341A 配套工具)执行读取命令。
  4. 校验完整性——读两遍,比对 MD5。不一致就重新夹。
我的习惯:读 SPI Flash 之前,先给板子断电,然后量一下 VCC 和 GND 之间有没有短路。我曾经有一次没注意,夹子把相邻引脚短路了,结果芯片直接冒烟——嗯,从那以后我再也不敢偷懒了。

常见 SPI Flash 引脚定义:

引脚编号 名称 功能
1 CS# 片选(低电平有效)
2 DO/IO1 数据输出
3 WP# 写保护
4 GND 地
5 DI/IO0 数据输入
6 CLK 时钟
7 HOLD# 保持
8 VCC 电源(通常 3.3V)
避坑指南:有些设备会在启动后把 SPI Flash 的 CS# 引脚拉高,导致编程器无法正常通信。遇到这种情况,可以试试在设备上电前就夹好夹子,或者用复位引脚把 CPU 按住。

2.1.2 JTAG/SWD 调试接口

JTAG 和 SWD 是调试接口,但也能用来提取固件。我记得有一次遇到一个设备,SPI Flash 被 BGA 封装盖住了,根本没法夹。后来发现板子上有 JTAG 接口,直接用 OpenOCD 把整个 Flash 内容读出来了。

JTAG 与 SWD 对比:

特性 JTAG SWD
引脚数 5(TCK、TMS、TDI、TDO、nTRST) 2(SWCLK、SWDIO)
速度 较慢 较快
适用芯片 ARM、MIPS、FPGA 等 ARM Cortex-M 系列
调试能力 完整(断点、单步、内存读写) 完整
提取固件 通过内存 dump 或 Flash 控制器读取 通过内存 dump 或 Flash 控制器读取

用 OpenOCD 提取固件的典型命令:

# 连接目标板
openocd -f interface/jlink.cfg -f target/stm32f4x.cfg

# 在 OpenOCD 终端中执行
flash read_bank 0 firmware.bin 0x08000000 0x100000

# 或者通过 GDB 连接后 dump 内存
(gdb) target remote :3333
(gdb) dump binary memory firmware.bin 0x08000000 0x08100000
个人经验:用 JTAG 提取固件时,最好先确认芯片的读保护(RDP)级别。如果 RDP 是 Level 2,那 JTAG 接口基本就废了。我遇到过一块 STM32 的板子,RDP 设成了 Level 1,虽然能连接但读 Flash 全是 0xFF——后来用漏洞绕过了保护才拿到固件。

2.2 软件提取方法

软件提取,就是通过设备上运行的操作系统或 Bootloader 来获取固件。这个方法风险低、速度快,但前提是你得有办法进入系统。

2.2.1 系统命令提取

如果你已经拿到了设备的 shell(不管是 telnet、SSH 还是串口),那提取固件就简单多了。说白了,就是找到固件分区,然后把它拷贝出来。

常用命令:

  • cat /proc/mtd——查看 MTD 分区表,确认固件分区名称和大小。
  • dd if=/dev/mtdblock0 of=/tmp/firmware.bin——用 dd 命令把分区 dump 出来。
  • scp /tmp/firmware.bin user@host:/path/——通过 SCP 传到本地。
  • nc -l -p 1234 < /tmp/firmware.bin——用 netcat 发送,本地用 nc target_ip 1234 > firmware.bin 接收。
注意:有些设备会把固件分区设为只读,或者用 mtdblock 设备时要注意对齐。我曾经用 dd 读一个 16MB 的 Flash,结果因为块大小设错了,读出来的文件只有 4MB——嗯,后来对比 MD5 才发现不对。

2.2.2 Bootloader 提取

Bootloader 是设备上电后第一个运行的程序。很多 Bootloader(如 U-Boot、RedBoot)都内置了内存读写和 Flash 操作命令。如果你能通过串口进入 Bootloader 的交互界面,那提取固件就手到擒来了。

U-Boot 常用命令:

# 查看 Flash 信息
U-Boot> flinfo

# 从 Flash 读取到内存
U-Boot> sf read 0x82000000 0x0 0x100000

# 从内存通过串口发送(YMODEM 协议)
U-Boot> loady 0x82000000 0x100000

# 或者用 tftp 上传到服务器
U-Boot> tftp 0x82000000 firmware.bin 0x100000
我的建议:进入 Bootloader 后,先执行 help 或 ? 看看支持哪些命令。不同版本的 U-Boot 命令集差异很大。我遇到过一台老设备,U-Boot 版本太旧,连 sf 命令都没有,最后只能用 cp.b 从 Flash 地址直接拷贝到内存。

2.3 知识体系总览

下面这张图把固件提取的两种路径和关键节点串起来了。你一看就明白什么时候该用什么方法。

固件提取技术知识体系 固件提取技术总览 固件提取 硬件提取 软件提取 SPI Flash 读取 JTAG/SWD 调试 系统命令提取 Bootloader 提取 flashrom / CH341A OpenOCD / J-Link dd / scp / netcat U-Boot / RedBoot 选择原则:优先软件 → 其次 SPI Flash → 最后 JTAG 硬件提取前务必确认芯片型号、电压和引脚定义

2.4 方法选择决策树

在实际项目中,怎么选最省事?我一般按这个顺序来:

  1. 先试软件——有 shell 吗?有就 dd 或 scp。能进 Bootloader 吗?有就用 U-Boot 命令。
  2. 再试 SPI Flash——软件进不去?拆壳看 Flash 芯片,用夹子或编程器读。
  3. 最后 JTAG——Flash 被封装盖住了?或者有读保护?上 JTAG 试试。
记住:提取固件只是第一步。拿到二进制之后,还要做完整性校验(MD5/SHA256),确认没有在传输过程中损坏。我习惯在 dump 前后各算一次哈希,确保万无一失。