第三章 固件提取基础:编程器与JTAG实战

大家好,我是老周。今天咱们聊聊固件提取最基础、也最核心的两个手段——用编程器读SPI Flash,以及用OpenOCD走JTAG接口。这两个方法,我做了十几年嵌入式安全,几乎天天都在用。

你可能会问:为什么需要两种方法?其实很简单——SPI Flash是芯片的“硬盘”,JTAG是芯片的“调试后门”。一个管存储,一个管运行,缺一不可。

核心观点:固件提取不是玄学,是硬件与协议的博弈。你手里有编程器,就能直接读Flash;你有JTAG接口,就能绕过一切软件保护。

3.1 编程器读取SPI Flash:最直接的暴力手段

编程器,说白了就是个硬件读写工具。我个人最常用的是CH341A,便宜、稳定、社区支持好。淘宝上几十块钱一个,配上夹子或者座子就能干活。

为什么选CH341A?

  • 支持3.3V和5V电平,兼容大多数SPI Flash
  • 软件开源,驱动好找
  • 读写速度够用(实测25MHz时钟下,读8MB Flash约30秒)

嗯,这里要注意:CH341A默认是5V电平,很多现代Flash是3.3V甚至1.8V的。我曾经有一次没注意,直接把一个1.8V的Flash烧了——芯片冒烟,板子报废。从那以后,我每次上电前都会用万用表量一下VCC引脚。

3.1.1 硬件连接

连接其实很简单,但细节决定成败。我习惯用夹子而不是座子——因为很多设备上的Flash是焊死的,拆下来太麻烦。

CH341A引脚 SPI Flash引脚 说明
CS (片选) CS# (1脚) 注意有些Flash是CE#
CLK (时钟) SCLK (6脚) 时钟线,别接反
MOSI (主出从入) SI (5脚) 数据输入
MISO (主入从出) SO (2脚) 数据输出
VCC (电源) VCC (8脚) 3.3V或5V,看Flash规格
GND (地) GND (4脚) 共地,必须接

警告:千万不要在设备通电时连接或断开编程器!我见过有人带电操作,结果Flash内部电荷泵损坏,整个芯片报废。先断电,再接线,这是铁律。

3.1.2 软件操作

CH341A的配套软件很多,我推荐FlashROM(开源)或者AsProgrammer(界面友好)。下面以FlashROM为例:

# 安装flashrom(Ubuntu/Debian)
sudo apt-get install flashrom

# 检测Flash芯片
sudo flashrom -p ch341a_spi

# 读取整个Flash到文件
sudo flashrom -p ch341a_spi -r firmware.bin

# 如果芯片检测失败,可以强制指定型号
sudo flashrom -p ch341a_spi -c "W25Q64JV" -r firmware.bin

你想想看,为什么有时候检测不到芯片?多半是接触不良。夹子夹在Flash引脚上,稍微动一下就虚焊了。我习惯在夹子后面垫一块海绵,压紧一点。

小技巧:读出来的.bin文件,先用hexdump -C firmware.bin | head -20看一眼。如果全是0xFF或者0x00,说明没读对——要么是引脚接错了,要么是Flash被写保护了。

3.2 JTAG接口:芯片的“后门”

JTAG,全称Joint Test Action Group,本来是做芯片测试的。但后来大家发现,这玩意儿能直接控制CPU、读写内存、甚至绕过BootROM。说白了,JTAG就是芯片的调试后门

我遇到过很多设备,Flash被加密了,编程器读出来全是乱码。但JTAG一接上去,直接dump出整个固件——因为JTAG走的是CPU内部总线,不经过Flash的加密逻辑。

3.2.1 JTAG引脚定义

标准的JTAG有4个信号线(加上VCC和GND共6根):

信号 全称 作用
TMS Test Mode Select 控制状态机切换
TCK Test Clock 时钟信号
TDI Test Data In 数据输入
TDO Test Data Out 数据输出
VCC 电源 参考电平(1.8V/3.3V)
GND 共地

嗯,这里有个坑:很多设备不会把JTAG引脚标出来。你得自己找。我常用的方法是——用万用表量一下,看看哪些引脚是连到GND的,哪些是连到VCC的。JTAG的TMS和TCK通常有上拉电阻,所以量出来是VCC电平。

3.2.2 OpenOCD实战

OpenOCD(Open On-Chip Debugger)是JTAG调试的瑞士军刀。我习惯用FT2232H作为JTAG适配器,配合OpenOCD使用。

先写一个配置文件:

# jtag.cfg
source [find interface/ftdi/ft2232h_compact.cfg]
transport select jtag
adapter speed 1000

# 假设目标芯片是STM32F4
set CHIPNAME stm32f4x
source [find target/stm32f4x.cfg]

# 初始化
init
targets
halt

# 读取整个Flash
flash read_bank 0 firmware.bin 0x08000000 0x100000

# 或者直接dump内存
dump_image firmware.bin 0x08000000 0x100000

然后运行:

openocd -f jtag.cfg

你可能会问:为什么有时候JTAG连不上?我告诉你,最常见的原因是时钟频率太高。有些芯片的JTAG接口只能跑几MHz,你设成10MHz肯定不行。我一般从1MHz开始试,慢慢往上调。

避坑指南:我曾经遇到一个设备,JTAG死活连不上。折腾了两天,最后发现是JTAG被熔丝位禁用了。有些芯片出厂时就把JTAG关掉了,这时候只能走编程器或者找其他后门。

3.3 两种方法的对比与选择

说了这么多,到底该用哪种?我画了个流程图,帮你快速决策:

固件提取方法选择流程图 开始提取固件 Flash是否可拆卸? 使用编程器 (CH341A) 是否有JTAG接口? 使用JTAG (OpenOCD) 寻找其他后门 (UART/I2C/SPI)

从流程图可以看出:能拆Flash就用编程器,不能拆就找JTAG。如果两者都不行,那就得考虑其他接口了——比如UART、I2C、甚至SPI。

3.4 实战中的常见问题

我总结了一些新手容易踩的坑,分享给大家:

  1. Flash写保护:有些Flash有状态寄存器,设置了写保护。读之前先读一下状态寄存器,如果有保护位,先解锁。
  2. JTAG扫描链:如果芯片有多个JTAG设备(比如CPU+FPGA),需要指定扫描链长度。用jtag scan_chain命令查看。
  3. 电平不匹配:JTAG适配器输出3.3V,但芯片是1.8V的。这时候需要电平转换芯片,否则会烧坏芯片。
  4. 时钟干扰:长线缆会导致时钟信号变形。我习惯用屏蔽线,长度控制在15cm以内。

个人经验:有一次我读一个工业路由器的Flash,编程器怎么都读不出来。后来发现是Flash的WP#引脚被拉低了——写保护。我用镊子把WP#引脚挑起来,再读就成功了。这种小问题,往往最坑人。

3.5 总结

固件提取,说白了就是硬件与协议的博弈。编程器是“暴力破解”,JTAG是“巧取豪夺”。两种方法各有优劣,但核心思路是一样的——找到芯片的通信接口,用正确的协议把数据读出来。

我建议你从编程器开始练手,因为成本低、风险小。等熟练了,再挑战JTAG。记住:先断电,再接线;先检测,再操作。这两条铁律,能帮你省下不少芯片钱。

好了,今天就聊到这里。如果你在实战中遇到问题,欢迎来公众号找我。

专注资料整理