2、固件提取技术:硬件调试接口(JTAG/SWD)、SPI Flash读取、BGA芯片拆焊与读取
好,我们进入第二章。这一章讲的是固件提取,说白了就是怎么把芯片里的程序“掏”出来。你可能会想,直接读Flash不就行了?嗯,事情没那么简单。不同的芯片、不同的封装、不同的保护机制,决定了你得用不同的手段。我这些年折腾下来,总结出三条路:调试接口、SPI Flash直读、以及BGA拆焊。咱们一条一条说。
2.1 硬件调试接口:JTAG与SWD
JTAG和SWD,是嵌入式世界里最常用的调试接口。JTAG是老祖宗,SWD是ARM后来搞的简化版。我个人习惯,能走SWD就走SWD,因为线少,稳定,不容易出错。
2.1.1 JTAG接口
JTAG全称是Joint Test Action Group,标准是IEEE 1149.1。它用了5根线:
- TMS:模式选择
- TCK:时钟
- TDI:数据输入
- TDO:数据输出
- TRST:复位(可选)
JTAG的核心思想是通过边界扫描链,把芯片内部的寄存器串起来。你可以通过TDI往里面塞数据,再从TDO读出来。说白了,就是一条串行高速公路。
我在项目中遇到过一款路由器,JTAG接口被厂家物理割断了。嗯,他们以为这样我就读不了固件了。其实只要找到CPU的JTAG引脚,飞几根线就能搞定。你想想看,厂家能割断PCB走线,但割不断芯片内部的扫描链。
2.1.2 SWD接口
SWD是Serial Wire Debug的缩写,ARM Cortex-M系列标配。它只需要两根线:
- SWDIO:数据线(双向)
- SWCLK:时钟线
为什么ARM要搞SWD?因为省引脚啊!很多Cortex-M芯片只有20个引脚,JTAG占5个太奢侈了。SWD两根线就能完成调试和编程,效率还比JTAG高。
我记得有一次,客户拿了个变砖的智能门锁过来。主控是STM32F103,SWD接口被固件锁死了。我试了常规的拉低BOOT0引脚进系统存储器模式,结果发现厂家把BOOT0引脚也禁用了。最后怎么搞的?我用高压编程器,直接给芯片的复位引脚送了一个12V脉冲,强行解锁了SWD接口。嗯,这招有点暴力,但管用。
2.2 SPI Flash读取
SPI Flash是嵌入式系统里最常见的固件存储介质。它便宜、容量大、接口简单。但读取它,你得先找到它。
2.2.1 识别SPI Flash
SPI Flash通常是8个引脚的小芯片,封装有SOIC-8、WSON-8等。引脚定义很固定:
| 引脚号 | 名称 | 功能 |
|---|---|---|
| 1 | CS# | 片选(低电平有效) |
| 2 | DO | 数据输出 |
| 3 | WP# | 写保护 |
| 4 | GND | 地 |
| 5 | DI | 数据输入 |
| 6 | CLK | 时钟 |
| 7 | HOLD# | 保持 |
| 8 | VCC | 电源 |
你拿到一块板子,先找这种8脚芯片。用万用表量一下第8脚对地电压,如果是3.3V或1.8V,基本就是SPI Flash了。
2.2.2 在线读取 vs 离线读取
读取SPI Flash有两种方式:
- 在线读取:用夹子夹住芯片,通过编程器直接读。好处是不用拆芯片,坏处是如果板子上有其他设备干扰,可能读出来的数据是错的。
- 离线读取:把芯片拆下来,放到编程器座子里读。数据最可靠,但需要拆焊。
我个人习惯,先在线读一次。如果读出来的数据校验不过,再拆下来离线读。你想想看,拆芯片是有风险的,尤其是多层板,焊盘很容易掉。
下面是一个用Python通过SPI读取Flash的示例:
import spidev
import time
spi = spidev.SpiDev()
spi.open(0, 0) # 打开SPI总线0,片选0
spi.max_speed_hz = 1000000 # 1MHz
# 读取芯片ID
spi.xfer2([0x9F]) # 发送读ID命令
id_bytes = spi.xfer2([0x00, 0x00, 0x00]) # 读取3个字节
print(f"Flash ID: {id_bytes[0]:02X} {id_bytes[1]:02X} {id_bytes[2]:02X}")
# 读取固件(从地址0开始,读256字节)
addr = 0x000000
spi.xfer2([0x03, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF])
firmware = spi.xfer2([0x00] * 256) # 读取256字节
print(f"Firmware first 16 bytes: {firmware[:16].hex()}")
spi.close()
我曾经遇到过一个问题:在线读取时,读出来的数据全是0xFF。折腾了半天,发现是板子上的电源没断干净,Flash处于复位状态。嗯,这里要注意:在线读取时,一定要确保板子完全断电,或者Flash的VCC引脚有稳定的电压。
2.3 BGA芯片拆焊与读取
BGA封装,是嵌入式安全分析里最头疼的东西。它没有引脚,只有底部的焊球。想读里面的固件?要么拆下来,要么用飞线。
2.3.1 什么时候需要拆BGA
一般来说,只有以下情况才需要拆BGA:
- 芯片内部集成了Flash(比如ESP32、MT7688等)
- SPI Flash被封装在芯片内部,外部无法直接访问
- 芯片有读保护,且无法通过调试接口绕过
我拆过最多次的是ESP32。它的Flash是内置的,想读固件只能把芯片拆下来,放到编程器上读。但ESP32的Flash是加密的,拆下来读出来的也是密文。嗯,那是另一章的内容了。
2.3.2 拆焊工具与步骤
拆BGA,工具很重要。我推荐以下配置:
- 热风枪:温度控制在300~350°C,风速中等
- 助焊剂:BGA专用助焊剂,不要用松香
- 镊子:防静电镊子,尖头的最好
- 预热台:如果板子比较大,建议用预热台先加热到100°C
步骤其实不复杂:
- 在芯片四周涂上助焊剂
- 用热风枪均匀加热,先预热整个芯片区域
- 等助焊剂开始冒烟,用镊子轻轻推一下芯片
- 如果芯片能滑动,说明焊球已经融化,用镊子夹起来
- 清理焊盘,用吸锡带把残留的锡吸干净
2.3.3 读取BGA芯片中的固件
拆下来之后,怎么读?有两种方法:
- 直接编程器读取:如果芯片是标准的SPI Flash封装(比如WSON-8),直接放到编程器座子里读。
- 飞线读取:如果芯片是BGA封装,没有现成的座子,就得飞线。找到芯片的SPI引脚(CS、CLK、DI、DO),焊上细漆包线,再连到编程器上。
飞线是个技术活。BGA芯片的焊球间距只有0.5mm甚至0.4mm,你得用显微镜才能看清。我一般用0.1mm的漆包线,先在焊盘上涂一点助焊剂,然后用烙铁轻轻一碰,线就焊上去了。
我记得有一次,客户拿了个工业路由器过来,主控是MT7621。它的Flash是内置的,BGA封装。我拆下来之后,发现芯片底部有176个焊球,但只有4个是SPI接口。我花了两个小时,用万用表一个一个量,才找到CS、CLK、DI、DO这四个引脚。嗯,那感觉就像在海底捞针。
2.4 总结
固件提取,说白了就是一场攻防博弈。厂家会想尽办法保护固件,比如割断JTAG、锁死SWD、加密Flash。但作为安全分析人员,我们也有自己的手段。JTAG和SWD是最优雅的方式,SPI Flash直读是最可靠的方式,BGA拆焊是最后的手段。
我个人建议,初学者先从SPI Flash读取开始练手。找一块旧的路由器或者开发板,用夹子夹住Flash,用编程器读出来。等你能熟练读出固件了,再尝试JTAG和SWD。至于BGA拆焊,嗯,那需要一定的焊接功底,建议你先在废板上练练手。
下一章,我们会讲固件分析的第一步:文件系统识别与提取。到时候你会看到,读出来的固件其实是一个大杂烩,里面有文件系统、内核、引导程序等等。怎么把它们分开?我们到时候细说。