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类型 | 速度 | 价格区间 |
|---|---|---|---|
| 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
它会扫描整个固件文件,找到这些签名出现的位置,然后尝试提取。
3.2.2 手动分析固件结构
当Binwalk识别不出来的时候,就得靠手动分析了。我常用的方法:
- 看熵值:用
binwalk -E firmware.bin看熵值分布。高熵区域通常是压缩或加密数据,低熵区域可能是代码或未压缩数据。 - 看字符串:
strings firmware.bin | head -100。有时候固件里会留下路径信息、版本号、错误提示,这些都能帮你判断结构。 - 看十六进制:用
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 固件提取与解包流程总览
下面这张图概括了整个流程,我建议你把它记在脑子里:
这个流程我用了很多年了。说白了就是:先让工具干活,工具干不了的自己上。
3.5 实战中的避坑指南
最后分享几个我踩过的坑:
- 别信编程器读出来的数据是100%正确的:尤其是便宜的编程器,读大容量Flash时偶尔会有位错误。读完之后一定要校验一下,最好读两次对比。
- Binwalk提取出来的文件不一定完整:它只是根据签名切分,有时候切分点不对。提取完之后要检查文件大小和头部是否正常。
- 加密固件不一定无解:很多厂商的“加密”其实就是XOR了一下,或者密钥硬编码在Bootloader里。找找看,说不定密钥就在某个字符串里。
- 注意字节序:ARM和MIPS的字节序不同,读出来的数据解释方式也不同。我见过有人把大端数据当小端分析,折腾了一周才发现问题。
hexdump -C看前512字节,再用strings扫一遍,最后才上Binwalk。这样心里有个底,知道大概是什么结构。