第三章 固件获取与提取:从Flash芯片读取、从OTA更新包提取、从开发板/调试接口获取
做固件逆向这么多年,我经常被问到同一个问题:「固件到底从哪来?」
说实话,这个问题比想象中复杂。你想想看,一个嵌入式设备摆在面前,固件可能藏在Flash芯片里,可能躲在OTA更新包里,也可能通过调试接口偷偷溜出来。每种方式都有自己的门道,也有各自的坑。
今天我就把这三种主流获取方式掰开揉碎了讲清楚。嗯,都是实战中摸爬滚打出来的经验。
3.1 从Flash芯片读取固件
这是最直接的方式——把芯片拆下来,用编程器读。但直接归直接,操作起来有不少讲究。
3.1.1 识别Flash芯片型号
拿到一块PCB板,先找Flash芯片。常见的封装有SOIC-8、SOIC-16、WSON-8、BGA等。芯片表面通常会印着型号,比如Winbond的W25Q128、Macronix的MX25L25635F。
我个人习惯先用手机微距镜头拍张照,放大看型号。有些芯片表面被磨掉了标记——嗯,遇到过不少次,厂家故意防逆向。这时候就得靠经验判断了:
- 引脚数量:8脚通常是SPI Flash,48脚以上可能是NAND Flash
- 供电电压:3.3V是主流,1.8V多见于低功耗设备
- 容量标识:128、256这些数字往往代表Mbit
3.1.2 使用编程器读取
常用的编程器有CH341A、FT2232H、树莓派(没错,它也能当编程器用)。连接方式很简单:
# 使用flashrom工具读取SPI Flash
flashrom -p ch341a_spi -r firmware.bin
# 指定芯片型号(如果自动检测失败)
flashrom -p ch341a_spi -c "W25Q128.V" -r firmware.bin
# 验证读取结果
flashrom -p ch341a_spi -v firmware.bin
这里有个关键点:读取速度。默认速度可能太快导致数据出错。我一般把SPI频率降到1MHz以下,虽然慢点,但数据可靠。
3.1.3 在线读取 vs 离线读取
| 方式 | 优点 | 缺点 |
|---|---|---|
| 在线读取(夹子夹住) | 无需拆焊,操作简单 | 受电路干扰,可能读错 |
| 离线读取(拆下芯片) | 数据准确,不受干扰 | 需要焊接,可能损坏芯片 |
我的建议是:优先用在线方式试读,如果数据异常再拆下来。毕竟拆焊一次,板子就少一分完整性。
3.2 从OTA更新包提取固件
很多设备不让你拆,但会通过OTA推送更新。这反而是个突破口——更新包就在你手里,就看你能不能解开。
3.2.1 抓取OTA更新包
方法主要有三种:
- 抓包:用Wireshark或Burp Suite拦截设备与服务器的通信
- 日志分析:查看设备日志中的下载链接
- 逆向App:从手机App中提取更新包的URL
我记得有一次分析某智能摄像头,更新包藏在App的so文件里,硬编码了一个加密的URL。折腾了两天才找到解密密钥——其实就是设备序列号的前8位。
3.2.2 解包OTA文件
OTA更新包常见的格式有:
- ZIP压缩包:直接unzip解压
- 加密ZIP:需要找到密码或解密算法
- 差分更新包:需要结合旧版本还原
- 自定义格式:头部有魔数,需要逆向解析
# 解压标准ZIP
unzip update.zip -d firmware_dir/
# 查看文件头(判断格式)
hexdump -C update.bin | head -20
# 如果是差分更新,使用bsdiff还原
bspatch old_firmware.bin new_firmware.bin update.patch
3.2.3 处理加密和签名
现在大部分厂商都会对OTA包加密签名。常见的手法:
- AES加密:密钥可能藏在Bootloader或App中
- RSA签名:验证固件完整性,但通常不加密内容
- 自定义算法:异或、位移等简单变换
我曾经遇到过一个奇葩案例:厂商用「固件大小」作为AES密钥的一部分。我试了各种方法都解不开,最后发现密钥是文件大小+固定字符串的MD5值。你说这设计思路...嗯,确实够「巧妙」的。
3.3 从开发板/调试接口获取
如果你手头有开发板,或者设备上留有调试接口,那获取固件就轻松多了。
3.3.1 利用JTAG/SWD接口
JTAG和SWD是嵌入式设备的「后门」。只要找到接口,就能直接读取内存和Flash。
# 使用OpenOCD通过JTAG读取Flash
openocd -f interface/jlink.cfg -f target/stm32f4x.cfg \
-c "init" \
-c "flash read_bank 0 firmware.bin 0 0x100000" \
-c "shutdown"
找JTAG接口有个诀窍:看PCB上有没有排针或测试点,通常会有TCK、TMS、TDI、TDO、GND这几个信号。用万用表量一下,TCK和TMS一般会连接到CPU的对应引脚。
3.3.2 通过UART Bootloader读取
很多MCU出厂自带Bootloader,通过UART就能读取固件。比如STM32的系统Bootloader:
# 使用stm32flash工具读取
stm32flash -r firmware.bin /dev/ttyUSB0
# 指定起始地址和长度
stm32flash -S 0x08000000:0x100000 -r firmware.bin /dev/ttyUSB0
进入Bootloader的方式各不相同:有的需要拉高BOOT0引脚,有的需要在启动时发送特定字符。我建议先查芯片手册,别盲目尝试。
3.3.3 利用调试漏洞
有些设备虽然锁了调试接口,但存在漏洞可以绕过。比如:
- 电压毛刺攻击:在CPU启动时注入电压干扰,跳过安全检查
- 时钟毛刺攻击:改变时钟频率,导致指令执行错误
- 激光攻击:用激光照射芯片特定区域,改变存储单元状态
这些方法比较高级,需要专门的设备。我个人只在实验室里玩过几次电压毛刺,成功率大概30%左右。说白了,还是找软件漏洞更靠谱。
3.4 本章知识体系
下面这张图总结了固件获取的三种途径和关键步骤:
3.5 避坑指南
最后,分享几个我踩过的坑:
- 别信芯片标签:标称128Mbit的芯片,实际可能只有64Mbit。读之前先确认容量。
- OTA包可能不完整:有些更新包只包含差异部分,需要结合旧固件才能还原。
- 调试接口可能被锁:别一上来就焊线,先测测接口有没有反应。
- 注意固件校验:读出来的固件先算个MD5,和官方版本对比一下。
我曾经有一次,花了一整天读了一个Flash芯片,结果发现读出来的数据全是0xFF——原来是夹子没夹好,虚焊了。从那以后,我每次读取完都会先检查文件头,确认不是空数据。
好了,固件获取的方法就讲到这里。记住,没有万能的方案,只有最适合当前设备的方案。多试几种方法,总有一条路能走通。