第二章:固件提取与解包——硬件提取方法(SPI Flash、JTAG、UART)

做固件安全分析,第一步就是拿到固件。这听起来简单,但实际操作中,我见过太多人卡在这一步。你手头有个IoT设备,外壳拆了,芯片露出来了,然后呢?怎么把里面的二进制数据弄出来?

这一章,我带你走一遍硬件提取的三种主流方法:SPI Flash、JTAG和UART。每种方法都有它的脾气,咱们一个个说。

2.1 SPI Flash:最直接的固件仓库

SPI Flash是嵌入式设备里最常见的固件存储介质。说白了,它就是一块NOR Flash,通过SPI接口和主控芯片通信。我拆过的设备里,十有八九都是它。

提取流程其实不复杂:

  1. 定位芯片:找板子上标着“25Q”、“W25Q”、“MX25”这类字样的8脚芯片。嗯,就是那个。
  2. 连接编程器:用夹子或者焊线,把SPI Flash的CS、MISO、MOSI、CLK、VCC、GND引出来,接到编程器上。
  3. 读取数据:用软件(比如FlashROM、SPI Flash Programmer)读出来,保存成.bin文件。

关键点:读取前一定要确认芯片型号和电压。1.8V的芯片接到3.3V上,瞬间冒烟。我刚开始干这行时就烧过一块,教训深刻。

这里有个小技巧:很多设备会开启写保护或者状态寄存器保护。你读出来的数据可能全是0xFF或者0x00。遇到这种情况,别慌。先读一下状态寄存器,看看WP(写保护)位是不是置1了。如果是,用编程器发个命令清掉它。

避坑指南:我曾经遇到一个路由器,SPI Flash读出来全是0xFF。折腾了半天,发现是板子上的电源没完全断开,芯片处于复位状态。把设备彻底断电,再读就正常了。

2.2 JTAG:调试接口的“后门”

JTAG本来是给开发者调试用的,但对我们做安全分析的人来说,它就是个“后门”。通过JTAG,你可以直接访问CPU的内存空间,把固件dump出来,甚至单步调试。

JTAG接口长什么样?通常是4线或5线:

  • TMS:模式选择
  • TCK:时钟
  • TDI:数据输入
  • TDO:数据输出
  • TRST(可选):复位

找JTAG接口有个笨办法:顺着CPU的引脚找。很多芯片的JTAG引脚是复用的,比如GPIO。你得看datasheet确认。

提取固件的典型步骤:

  1. 用OpenOCD或者J-Link连接目标板。
  2. 识别CPU内核(比如Cortex-M3、MIPS、ARM9)。
  3. 暂停CPU执行,防止它干扰内存访问。
  4. dump_image命令把Flash区域读出来。

注意:JTAG提取有个坑——很多量产设备会禁用JTAG接口,或者设置密码。你连上去可能什么都读不到。这时候别灰心,试试暴力破解JTAG密码?嗯,那是另一个话题了。

我个人习惯是,先试SPI Flash,不行再上JTAG。因为JTAG对硬件知识要求更高,而且容易把设备搞死。有一次我JTAG线序接反了,直接把CPU烧了,板子彻底报废。

2.3 UART:串口里的“意外收获”

UART不是用来提取完整固件的,但它能帮你拿到运行时信息。很多设备在启动时会通过UART输出内核日志、文件系统挂载信息,甚至直接给你一个shell。

怎么找UART接口?看板子上有没有4个排针,或者焊盘。通常标着TX、RX、GND、VCC。如果没有标注,可以用万用表量:

  • GND:对地电阻为0
  • VCC:3.3V或1.8V
  • TX:空闲时是高电平(3.3V),有数据时会跳动
  • RX:通常是低电平

连接方法:用USB转串口模块,TX接RX,RX接TX,GND接GND。波特率嘛,我一般从115200开始试,不行就换57600、38400、9600。

实战经验:我曾经分析一个智能摄像头,SPI Flash焊盘太小没法夹,JTAG又被禁用了。最后通过UART拿到了启动日志,里面直接打印了文件系统的挂载路径和内核版本。虽然没拿到完整固件,但这些信息帮我找到了后续的攻击面。

UART提取固件有个限制:它只能看到CPU能访问的内存区域。如果固件被加密或者压缩,你拿到的是解密后的数据,不是原始固件。所以,UART更适合做辅助分析,而不是主要提取手段。

2.4 三种方法对比

方法 难度 成功率 获取内容 典型场景
SPI Flash 完整固件 大多数IoT设备
JTAG 内存/固件 调试接口未锁的设备
UART 运行时信息 启动日志、shell获取

你想想看,这三种方法其实对应了不同的硬件抽象层次。SPI Flash是物理层,直接读存储介质;JTAG是调试层,通过CPU访问内存;UART是通信层,获取系统输出。做固件分析时,我建议你从最简单的SPI Flash开始,不行再升级到JTAG,UART作为补充。

软件提取工具:binwalk、dd、unblob

硬件提取拿到的是原始二进制文件,但里面可能包含多个文件系统、内核镜像、引导加载程序。这时候就需要软件工具来“解包”了。

2.5 binwalk:固件分析的瑞士军刀

binwalk是我用得最多的工具,没有之一。它能自动识别固件里的文件签名,把各个部分拆开。

基本用法

# 扫描固件中的文件签名
binwalk firmware.bin

# 提取所有识别到的文件
binwalk -e firmware.bin

# 递归提取(处理嵌套的文件系统)
binwalk -Me firmware.bin

binwalk的识别能力很强,能认出SquashFS、CramFS、JFFS2、LZMA、gzip等几十种格式。但它也有弱点:如果固件被加密或者自定义了魔数,binwalk就认不出来了。

小技巧:我遇到过一些固件,binwalk扫描结果全是“Unknown”。后来发现是厂商把文件系统的魔数改了。这时候可以用binwalk -R手动指定特征字符串,或者用hexdump先看看文件头长什么样。

2.6 dd:简单粗暴的数据切割

dd是Linux下的数据复制工具,但用来切割固件也很顺手。当你知道了固件中各个部分的偏移量和大小,就可以用dd把它们精确地切出来。

典型用法

# 从偏移量0x100000开始,读取0x500000字节
dd if=firmware.bin of=part1.bin bs=1 skip=$((0x100000)) count=$((0x500000))

dd的好处是简单、可靠,不依赖任何文件签名识别。坏处是你得事先知道偏移量。怎么知道?看binwalk的扫描结果,或者分析固件的头部结构。

我个人习惯是,先用binwalk做自动提取,如果结果不对,再用dd手动切。有一次binwalk把SquashFS的尾部多切了一段,导致解压失败。我用dd重新切了一次,问题就解决了。

2.7 unblob:新一代固件解包工具

unblob是近几年才火起来的工具,专门针对固件解包做了优化。它比binwalk更智能,能处理嵌套的压缩和文件系统。

安装和使用

# 安装
pip install unblob

# 解包
unblob firmware.bin

unblob会自动创建一个目录,把解包后的文件放进去。它支持递归解包,遇到嵌套的gzip+SquashFS也能一次搞定。

对比:binwalk和unblob各有千秋。binwalk的签名库更全,unblob的递归解包更稳定。我一般两个都用,互相验证结果。如果两个工具解出来的文件不一样,那就要小心了——可能是固件里有隐藏的校验或者反分析机制。

固件文件系统分析:SquashFS、CramFS、JFFS2

固件解包后,你通常会得到一个或多个文件系统。嵌入式设备最常用的就是SquashFS、CramFS和JFFS2。这三种文件系统各有特点,咱们逐个分析。

2.8 SquashFS:只读压缩文件系统

SquashFS是嵌入式Linux的标配。它是只读的,数据经过压缩,体积小,适合存储固件。

解压方法

# 使用unsquashfs
unsquashfs rootfs.squashfs

# 如果unsquashfs版本不兼容,试试squashfs-tools的旧版本

我遇到过一个问题:unsquashfs解压时报错“Filesystem uses unsupported compression type”。这是因为SquashFS支持多种压缩算法(gzip、lzma、lzo、xz),而你的工具可能不支持某种算法。解决办法是安装对应的压缩库,或者用squashfs-tools-ng这个新工具。

注意:SquashFS是只读的,你不能直接修改里面的文件再打包回去。如果想修改固件,需要把SquashFS解压,修改文件,然后用mksquashfs重新打包。但要注意,重新打包后的文件系统大小可能和原来不一样,需要调整固件分区。

2.9 CramFS:老牌只读文件系统

CramFS比SquashFS更老,现在用得少了,但在一些老旧设备上还能见到。它的特点是简单、占用内存少,但压缩率不如SquashFS。

解压方法

# 使用cramfsck
cramfsck -x output_dir rootfs.cramfs

CramFS有个限制:单个文件不能超过16MB,文件系统总大小不能超过256MB。如果你看到固件里有个CramFS,但大小异常,可能是被分成了多个片段。

2.10 JFFS2:可写文件系统

JFFS2是专门为NOR Flash设计的日志结构文件系统,支持读写。它通常用于存放运行时数据,比如配置文件、日志文件。

解压方法

# 使用jefferson(Python工具)
jefferson rootfs.jffs2

# 或者用flash_otable_info + mount
modprobe mtdblock
modprobe jffs2
dd if=rootfs.jffs2 of=/tmp/jffs2.img
mount -t jffs2 /tmp/jffs2.img /mnt/jffs2

JFFS2解压有个坑:它依赖MTD驱动,在普通Linux上可能挂载失败。我建议用jefferson这个Python工具,它不需要内核支持,直接解析JFFS2的节点结构。

实战案例:有一次我分析一个工业路由器,固件里有个JFFS2分区,里面存了设备的配置文件和密码哈希。用jefferson解压后,直接拿到了管理员密码。嗯,那个密码是明文存储的,厂商的安全意识有待提高。

2.11 文件系统对比总结

文件系统 读写属性 压缩 典型用途 解包工具
SquashFS 只读 根文件系统 unsquashfs
CramFS 只读 老旧设备根文件系统 cramfsck
JFFS2 可写 配置分区、日志分区 jefferson

做固件分析时,我建议你先把文件系统解压出来,然后用tree命令看看目录结构。重点关注/bin/sbin/etc/usr/bin这些目录。里面藏着可执行文件、配置文件、脚本,这些都是后续漏洞挖掘的目标。

好了,这一章的内容就到这里。固件提取和解包是后续所有分析的基础,花时间把这一步做扎实了,后面会省很多事。


专注资料整理