3、固件获取与提取:从Flash/芯片读取固件、固件解包与文件系统提取(Binwalk)、固件加密与压缩识别

好,咱们进入第三章。这一章讲的是逆向分析的第一步——怎么把固件弄到手,以及拿到手之后怎么把它“拆开”。

说实话,很多新手觉得逆向分析最难的是看懂汇编代码。但我个人经验是,固件提取和解包才是真正的第一道坎。你连固件都读不出来,或者解不开,后面再牛的技术也用不上。

3.1 从Flash/芯片读取固件

固件存储在哪?绝大多数嵌入式设备里,固件都放在Flash芯片里。可能是SPI Flash、NAND Flash,也可能是芯片内部集成的Flash。

读取方式分几种情况:

  • 直接拆芯片读:把Flash芯片吹下来,用编程器读。这是最暴力也最可靠的方法。
  • 通过调试接口读:JTAG/SWD接口连上,用调试器把固件dump出来。
  • 通过Bootloader读:如果设备有U-Boot或者别的Bootloader,可能直接提供读取命令。
  • 通过系统运行时读:设备已经跑起来了,通过漏洞拿到shell,然后dd或者cat把/dev/mem读出来。
⚠️ 注意: 直接拆芯片读的时候,一定要确认Flash的电压和引脚定义。我曾经有一次把1.8V的Flash插到3.3V的编程器上,芯片直接冒烟了。嗯,从那以后我每次都会先查datasheet。

常见的编程器有这些:

编程器型号 支持的Flash类型 速度 价格区间
CH341A SPI Flash 慢 ¥20-50
TL866II Plus SPI/NAND/并行 中等 ¥300-500
Xgecu T48 几乎所有 快 ¥800-1500
Dataman 48Pro2 工业级全支持 极快 ¥5000+

我个人习惯是,手边常备一个CH341A做快速验证,真要干活的时候用TL866。便宜货有时候读出来的数据会有位翻转,坑过我好几次。

3.2 固件解包与文件系统提取(Binwalk)

固件读出来了,拿到一个.bin文件。接下来怎么办?

你想想看,这个二进制文件里可能包含了Bootloader、内核、文件系统、配置数据、各种资源。它们像三明治一样一层层叠在一起。我们需要把它们分开。

Binwalk 就是干这个的。它是固件逆向分析里最常用的工具,没有之一。

3.2.1 Binwalk的基本用法

# 扫描固件,识别文件头和签名
binwalk firmware.bin

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

# 指定提取目录
binwalk -e -C ./output firmware.bin

# 递归提取(解包后再解包)
binwalk -Me firmware.bin

Binwalk的工作原理其实不复杂。它维护了一个签名数据库,里面存了各种文件格式的魔数(Magic Number)。比如:

  • U-Boot的头部签名:27051956
  • Zlib压缩数据:789C 或 78DA
  • JFFS2文件系统:19851985
  • Squashfs文件系统:hsqs 或 sqsh
  • LZMA压缩:5D0000

它会扫描整个固件文件,找到这些签名出现的位置,然后尝试提取。

💡 关键点: Binwalk不是万能的。它只能识别它签名库里有的格式。遇到自定义加密或者混淆的固件,Binwalk就抓瞎了。这时候需要手动分析。

3.2.2 手动分析固件结构

当Binwalk识别不出来的时候,就得靠手动分析了。我常用的方法:

  1. 看熵值:用binwalk -E firmware.bin看熵值分布。高熵区域通常是压缩或加密数据,低熵区域可能是代码或未压缩数据。
  2. 看字符串:strings firmware.bin | head -100。有时候固件里会留下路径信息、版本号、错误提示,这些都能帮你判断结构。
  3. 看十六进制:用hexdump -C firmware.bin | head -200。观察文件头部的模式。

举个例子,我曾经分析过一个路由器固件,Binwalk什么都没扫出来。但我用strings一看,发现了/etc/config和/usr/sbin/httpd这些路径。再结合熵值分析,发现固件前半部分熵值低,后半部分熵值高。我判断前半部分是未加密的内核,后半部分是压缩的文件系统。手动切分后果然提取成功。

3.2.3 常见文件系统提取

嵌入式设备里最常见的文件系统是Squashfs和JFFS2。提取方法:

# 提取Squashfs文件系统
unsquashfs -d ./squashfs-root squashfs.bin

# 提取JFFS2文件系统(需要先模拟MTD设备)
modprobe mtdblock
modprobe jffs2
dd if=jffs2.bin of=/dev/mtdblock0
mount -t jffs2 /dev/mtdblock0 /mnt/jffs2

# 提取Cramfs文件系统
cramfsck -x ./cramfs-root cramfs.bin
🔧 小技巧: 如果unsquashfs报错说文件系统损坏,别急着放弃。有时候只是文件系统头部被修改了,把正确的头部补回去就能解。我遇到过好几次这种情况,都是厂商故意改了几个字节来防提取。

3.3 固件加密与压缩识别

现在越来越多的厂商开始给固件加密。说白了,就是不想让你轻易看到里面的内容。

3.3.1 如何判断固件是否加密

判断方法其实不难:

  • 熵值检测:整个固件熵值接近1.0(最大值),且没有明显的低熵区域,大概率是加密了。
  • 字符串检测:strings跑不出任何有意义的ASCII字符串。
  • 头部特征:没有已知的文件签名,头部是一堆看起来随机的字节。
  • 大小特征:固件大小是某个值的整数倍(比如16字节对齐),可能是AES加密的特征。
特征 压缩 加密
熵值 高(0.8-0.95) 极高(0.99-1.0)
头部签名 有(如789C) 无,随机
字符串 压缩块之间可能有 几乎没有
重复模式 无 可能有(ECB模式)

3.3.2 常见加密算法识别

如果确定固件加密了,下一步是识别加密算法。这通常需要结合Bootloader分析:

  • AES:最常见。128位或256位密钥。如果看到16字节对齐的块,可能是AES。
  • XOR:简单但常见。单字节XOR或多字节XOR。可以用binwalk -X尝试暴力破解。
  • RSA:通常用于签名验证,不是用于加密整个固件。
  • 自定义算法:一些厂商会自己写加密算法,说白了就是“安全通过模糊”。这种反而好破解,因为算法通常有漏洞。

3.3.3 压缩算法识别

压缩比加密好处理多了。常见压缩算法:

  • gzip/zlib:头部1F8B或789C
  • LZMA:头部5D0000
  • LZO:没有固定头部,需要启发式检测
  • XZ:头部FD377A585A00

Binwalk对压缩数据的识别率很高。如果它没识别出来,可以试试手动解压:

# 从偏移量0x10000开始,提取128KB数据,尝试解压
dd if=firmware.bin bs=1 skip=65536 count=131072 | zlib-flate -uncompress > output.bin
⚠️ 注意: 解压之前一定要确认偏移量和长度。我曾经因为偏移量算错了一个字节,解出来的数据全是乱码,折腾了半天才发现是少算了一个字节的偏移。

3.4 固件提取与解包流程总览

下面这张图概括了整个流程,我建议你把它记在脑子里:

固件获取与提取流程 拆芯片读 调试接口读 Bootloader读 运行时读 原始固件二进制文件 (.bin) Binwalk自动识别 手动分析(熵/字符串/Hex) 解包提取文件系统 识别压缩算法并解压 识别加密算法并解密 获取可分析的文件系统/代码 核心原则:先自动后手动,先识别后处理,遇到加密找密钥

这个流程我用了很多年了。说白了就是:先让工具干活,工具干不了的自己上。

3.5 实战中的避坑指南

最后分享几个我踩过的坑:

  • 别信编程器读出来的数据是100%正确的:尤其是便宜的编程器,读大容量Flash时偶尔会有位错误。读完之后一定要校验一下,最好读两次对比。
  • Binwalk提取出来的文件不一定完整:它只是根据签名切分,有时候切分点不对。提取完之后要检查文件大小和头部是否正常。
  • 加密固件不一定无解:很多厂商的“加密”其实就是XOR了一下,或者密钥硬编码在Bootloader里。找找看,说不定密钥就在某个字符串里。
  • 注意字节序:ARM和MIPS的字节序不同,读出来的数据解释方式也不同。我见过有人把大端数据当小端分析,折腾了一周才发现问题。
🔧 我的习惯: 拿到一个新固件,我第一件事不是跑Binwalk,而是先hexdump -C看前512字节,再用strings扫一遍,最后才上Binwalk。这样心里有个底,知道大概是什么结构。