3、固件文件系统与格式:常见固件格式(BIN、HEX、ELF)、文件系统(Cramfs、Squashfs、JFFS2)、固件打包与解包工具
物联网设备的固件本质上是一个“可执行的二进制集合体”,它既包含CPU执行的机器码,也包含操作系统内核、设备驱动、根文件系统以及应用程序。在安全分析、OTA升级和漏洞挖掘场景中,我们首先面对的问题不是“如何逆向”,而是“如何正确拆开这个固件包”。不同格式(BIN、HEX、ELF)决定了固件的加载方式与地址映射;不同文件系统(Cramfs、Squashfs、JFFS2)决定了内部数据的组织与压缩策略;而打包/解包工具则是连接“物理固件”与“逻辑文件”的桥梁。本章将系统梳理这三层知识,并给出实战中的判断路径与操作流程。
上图展示了固件分析的核心知识框架。从格式识别到文件系统提取,再到内容分析,每一步都依赖对底层结构的准确理解。下面我们将逐一深入。
3.1 常见固件格式:BIN、HEX、ELF
核心原理
固件格式决定了二进制数据如何被解释和加载。三种格式各有其适用场景:BIN(Binary)是最原始的格式,它只是内存映像的连续字节流,没有任何地址信息,通常直接烧录到Flash的固定偏移处;HEX(Intel HEX)是一种文本格式,每行记录包含地址、数据长度和校验和,主要用于早期EPROM编程器或MCU烧录,它允许非连续地址的数据块;ELF(Executable and Linkable Format)是Linux/Unix系统下的标准可执行文件格式,包含ELF头、程序头表(段)和节头表(节),支持重定位、动态链接和调试信息,是分析路由器、智能家居网关等Linux设备固件的主要对象。
判断方法
在实际操作中,我们通常使用以下命令快速判断固件格式:
# 查看文件类型
file firmware.bin
# 查看二进制头部(前64字节)
hexdump -C firmware.bin | head -n 4
# 如果是ELF文件,查看段信息
readelf -h firmware.elf
readelf -l firmware.elf
判断要点:BIN文件通常以随机数据或特定芯片厂商的头部开始(如Broadcom的“BCM”头);HEX文件以“:”开头,每行格式为“:LLAAAARR...”;ELF文件以“\x7fELF”魔数开头。对于BIN文件,我们还需要进一步识别其内部是否包含文件系统或内核镜像,这通常需要借助binwalk。
案例拆解:一个典型路由器固件的格式识别
假设我们拿到一个名为“router_fw.bin”的文件,大小16MB。初始现象是:直接使用file命令显示“data”,无法识别。我们执行hexdump -C router_fw.bin | head -n 8,看到前4字节为“\x27\x05\x19\x56”,这并非标准魔数。接着使用binwalk router_fw.bin,输出显示在偏移0x100处存在“TRX”头部(一种常见的路由器固件封装格式),在0x1F0000处发现“Squashfs文件系统”的魔数。由此判断:该固件是TRX封装,内部包含一个Linux内核和一个Squashfs根文件系统。我们使用binwalk -Me router_fw.bin自动提取,得到内核镜像和文件系统目录树。复盘经验:对于未知BIN文件,不要盲目尝试反汇编,先做结构扫描是最高效的路径。
常见误区与总结
objcopy -I ihex -O binary)。另外,ELF文件通常包含调试符号和未初始化的数据段(.bss),在提取固件时,应使用objcopy -O binary仅提取可加载段,否则生成的BIN文件可能包含大量无用的填充数据。
总结:格式识别是固件分析的第一步,也是最容易出错的一步。务必使用file、hexdump、readelf和binwalk进行交叉验证,确认固件的封装类型和内部结构。
3.2 文件系统:Cramfs、Squashfs、JFFS2
核心原理
文件系统是固件中存放用户数据、配置、Web页面和应用程序的部分。在物联网设备中,由于Flash存储空间有限,通常使用压缩式只读文件系统。三种主流文件系统各有特点:
| 特性 | Cramfs | Squashfs | JFFS2 |
|---|---|---|---|
| 读写属性 | 只读 | 只读 | 可读写 |
| 压缩算法 | zlib | gzip/lzma/lzo等 | zlib/rtime |
| 最大文件大小 | 16MB | 16EB(理论) | 取决于Flash |
| 适用场景 | 小容量NOR Flash | 大容量NAND Flash | 需要保存运行数据的设备 |
| 磨损均衡 | 不支持 | 不支持 | 支持 |
| 典型魔数 | 0x28cd3d45 | 0x68737173 | 0x1985 |
判断与提取方法
识别文件系统类型后,使用对应工具提取:
# 使用binwalk自动识别并提取
binwalk -Me firmware.bin
# 手动提取Squashfs(假设偏移为0x1F0000)
dd if=firmware.bin of=squashfs.img bs=1 skip=$((0x1F0000))
unsquashfs squashfs.img
# 手动提取Cramfs
dd if=firmware.bin of=cramfs.img bs=1 skip=$((0x10000))
# 使用cramfsck或firmware-mod-kit中的工具
# 手动提取JFFS2(需要指定页大小)
dd if=firmware.bin of=jffs2.img bs=1 skip=$((0x20000))
jefferson jffs2.img
关键参数:对于JFFS2,必须知道NAND Flash的页大小(通常为2KB或4KB)和擦除块大小(通常为128KB),否则无法正确解析节点。对于Squashfs,需要关注压缩算法和块大小,不同版本(v4.0 vs v3.0)的布局略有差异。
实战场景:从Squashfs中提取Web配置文件
在一次智能摄像头固件分析中,我们通过binwalk发现固件包含Squashfs文件系统。提取后,在/etc/目录下发现shadow文件,其中包含root用户的哈希密码。进一步分析/www/目录,发现存在一个config.php文件,其中硬编码了云服务器的。这个密钥被用于设备与云端的通信认证。通过提取该密钥,我们成功模拟了设备与云端的通信协议。复盘:Squashfs文件系统虽然只读,但其中包含的敏感信息(密码、密钥、内部IP)是安全分析的重要突破口。
常见误区与总结
binwalk -A查看ASCII码,结合strings搜索关键路径(如“/etc/passwd”、“/bin/sh”)来辅助定位文件系统的真实偏移。
总结:文件系统是固件内容的“容器”。理解Cramfs、Squashfs、JFFS2的差异,能帮助我们选择合适的提取工具和参数。对于只读文件系统,提取后应保持其原始属性,避免在重打包时破坏权限和所有权信息。
3.3 固件打包与解包工具链
核心原理
固件打包(重打包)是漏洞利用和固件修改的关键步骤。解包工具将固件拆解为可编辑的文件;打包工具则将修改后的文件重新封装为可烧录的固件。工具链的核心是处理三个问题:偏移对齐(文件系统起始地址必须与Flash分区对齐)、校验和修复(很多固件头包含CRC32或MD5校验)、大小调整(修改文件系统后,其大小变化可能影响后续分区的偏移)。
常用工具清单
| 工具 | 功能 | 典型命令 |
|---|---|---|
| binwalk | 固件扫描、提取、签名识别 | binwalk -Me fw.bin |
| firmware-mod-kit | 自动化解包/重打包脚本集 | ./extract-firmware.sh fw.bin |
| unsquashfs / mksquashfs | Squashfs解包/打包 | unsquashfs -d rootfs rootfs.img |
| mkcramfs / cramfsck | Cramfs打包/检查 | mkcramfs rootfs cramfs.img |
| jefferson / jffs2dump | JFFS2解包/打包 | jefferson -d rootfs jffs2.img |
| dd | 按偏移截取/拼接数据 | dd if=fw.bin of=part.img bs=1 skip=1024 |
| objcopy | ELF与BIN格式互转 | objcopy -O binary kernel.elf kernel.bin |
操作流程:手动重打包一个Squashfs固件
假设原始固件结构为:头部(512字节)+ 内核(2MB)+ Squashfs(剩余空间)。我们修改了Squashfs中的某个脚本,需要重新打包。操作步骤如下:
- 提取原始分区:使用
dd分别提取头部、内核和Squashfs镜像。 - 解包Squashfs:使用
unsquashfs解包到rootfs/目录。 - 修改文件:编辑
rootfs/etc/init.d/rcS,添加我们的启动脚本。 - 重新打包:使用
mksquashfs rootfs new_squashfs.img -comp xz -b 128K。注意:压缩算法和块大小必须与原始固件一致,否则设备可能无法挂载。 - 拼接固件:使用
cat head.bin kernel.bin new_squashfs.img > new_fw.bin。 - 修复校验和:如果固件头有CRC32校验,需要计算新固件的CRC并回填。可使用
crc32命令或Python脚本。
案例拆解:因块大小不匹配导致的启动失败
在一次对某品牌路由器的重打包过程中,我们修改了Squashfs中的Web管理页面,使用mksquashfs默认参数(块大小128KB,gzip压缩)重新打包。烧录后设备无法启动,串口日志显示“VFS: Cannot open root device”。排查发现,原始固件的Squashfs块大小为256KB,且使用了LZMA压缩。我们重新使用-b 256K -comp lzma打包后,设备正常启动。复盘:Squashfs的块大小和压缩算法必须与内核配置的CONFIG_SQUASHFS_*选项匹配,否则内核无法识别。在打包前,应使用unsquashfs -s查看原始镜像的超级块信息。
常见误区与总结
sudo操作并保持root:root所有权。
总结:固件打包与解包是一项精细的工程。工具链的选择取决于文件系统类型和固件封装格式。核心原则是:保持原始参数不变(压缩算法、块大小、偏移对齐),修复所有校验,验证打包结果(使用binwalk重新扫描)。