第三章 固件提取基础:编程器与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就用编程器,不能拆就找JTAG。如果两者都不行,那就得考虑其他接口了——比如UART、I2C、甚至SPI。
3.4 实战中的常见问题
我总结了一些新手容易踩的坑,分享给大家:
- Flash写保护:有些Flash有状态寄存器,设置了写保护。读之前先读一下状态寄存器,如果有保护位,先解锁。
- JTAG扫描链:如果芯片有多个JTAG设备(比如CPU+FPGA),需要指定扫描链长度。用
jtag scan_chain命令查看。 - 电平不匹配:JTAG适配器输出3.3V,但芯片是1.8V的。这时候需要电平转换芯片,否则会烧坏芯片。
- 时钟干扰:长线缆会导致时钟信号变形。我习惯用屏蔽线,长度控制在15cm以内。
个人经验:有一次我读一个工业路由器的Flash,编程器怎么都读不出来。后来发现是Flash的WP#引脚被拉低了——写保护。我用镊子把WP#引脚挑起来,再读就成功了。这种小问题,往往最坑人。
3.5 总结
固件提取,说白了就是硬件与协议的博弈。编程器是“暴力破解”,JTAG是“巧取豪夺”。两种方法各有优劣,但核心思路是一样的——找到芯片的通信接口,用正确的协议把数据读出来。
我建议你从编程器开始练手,因为成本低、风险小。等熟练了,再挑战JTAG。记住:先断电,再接线;先检测,再操作。这两条铁律,能帮你省下不少芯片钱。
好了,今天就聊到这里。如果你在实战中遇到问题,欢迎来公众号找我。