2、固件架构基础:常见嵌入式架构(ARM、MIPS、RISC-V)、固件启动流程(Bootloader、Kernel、Rootfs)、固件文件系统(Squashfs、JFFS2、YAFFS2、Cramfs)、固件镜像结构分析。
大家好,我是你们的老朋友。今天咱们聊聊固件架构的基础。说实话,很多新手一上来就急着拿工具去挖漏洞,结果连固件跑在什么芯片上、怎么启动的都没搞清楚。这就像修车不知道发动机型号,对吧?
我个人习惯,拿到一个固件,第一件事不是解包,而是先看架构。为什么?因为后续所有的分析工具、调试手段,都跟架构绑定。你选错了反汇编器,那分析出来的东西就是一堆乱码。
2.1 常见嵌入式架构:ARM、MIPS、RISC-V
嵌入式世界,目前三足鼎立:ARM、MIPS、RISC-V。当然还有x86,但在IoT设备里相对少见。
ARM
ARM 是绝对的霸主。从你的手机到路由器,再到智能灯泡,到处都是。ARM 是精简指令集(RISC),特点是指令长度固定(Thumb模式除外),寄存器多。
我遇到过最坑的事:分析一个路由器固件,用IDA默认的ARM模式反汇编,结果全是乱码。后来才发现,这固件是Thumb模式编译的。切换成Thumb模式后,代码瞬间清晰了。所以,拿到固件先看入口地址是奇数还是偶数——奇数通常是Thumb。
MIPS
MIPS 曾经是网络设备(路由器、交换机)的王者。现在虽然被ARM挤压,但存量巨大。MIPS 也是RISC,但它有个特点:延迟槽。
什么叫延迟槽?就是分支指令后面的那条指令,无论分支是否跳转,都会被执行。这导致反汇编时,如果你不识别延迟槽,控制流图就是错的。我曾经在分析一个MIPS固件的漏洞时,因为没处理延迟槽,把漏洞路径给看丢了,折腾了两天。
RISC-V
RISC-V 是后起之秀,开源、模块化。这几年在AIoT、MCU领域发展很快。它的指令集非常简洁,基础指令只有40多条。分析RISC-V固件时,要注意它的压缩指令集(RVC),指令长度可以是16位或32位,混合在一起,反汇编器如果支持不好,很容易出错。
| 架构 | 指令长度 | 字节序 | 常见设备 | 逆向难点 |
|---|---|---|---|---|
| ARM | 32位/16位(Thumb) | 小端为主 | 手机、路由器、摄像头 | ARM/Thumb模式切换 |
| MIPS | 32位 | 大小端都有 | 老路由器、交换机 | 延迟槽、大小端识别 |
| RISC-V | 32位/16位(RVC) | 小端 | AIoT、MCU | 压缩指令、新架构工具链不成熟 |
2.2 固件启动流程:Bootloader、Kernel、Rootfs
一个典型的嵌入式固件,启动过程就像接力赛:Bootloader → Kernel → Rootfs。每一棒都至关重要。
Bootloader
Bootloader 是硬件上电后运行的第一段代码。它的任务很简单:初始化硬件(时钟、内存、串口),然后加载Kernel到内存,跳转过去。
常见的Bootloader有:U-Boot(最流行)、RedBoot、Barebox。U-Boot 支持交互式命令行,你可以通过串口中断启动过程,修改启动参数。这在漏洞挖掘中很有用——比如你可以通过修改启动参数,让系统以单用户模式启动,绕过一些认证。
Kernel
Kernel 是操作系统的核心。它管理硬件资源、提供系统调用。在固件分析中,我们最关心的是Kernel的版本和配置。
为什么?因为老版本Kernel有大量已知漏洞。比如,我曾经分析过一个摄像头固件,它的Kernel版本是2.6.39,这个版本存在一个著名的内存破坏漏洞(CVE-2016-5195,脏牛)。通过这个漏洞,我轻松实现了权限提升。
怎么看Kernel版本?解包固件后,找到vmlinuz或zImage文件,用file命令查看,或者用strings搜索"Linux version"。
Rootfs
Rootfs 是根文件系统。它包含了所有用户空间的程序、库、配置文件。启动时,Kernel会挂载Rootfs,然后执行/sbin/init程序,启动所有服务。
Rootfs 通常是一个压缩的镜像文件。常见的格式有Squashfs、JFFS2、YAFFS2、Cramfs。我们接下来详细说。
2.3 固件文件系统:Squashfs、JFFS2、YAFFS2、Cramfs
文件系统是固件内容的载体。不同的文件系统有不同的特点,解包工具也不同。
Squashfs
Squashfs 是目前最流行的只读压缩文件系统。它压缩率高,解压速度快。几乎所有新的路由器、摄像头固件都在用。
解包Squashfs,我推荐用unsquashfs工具(来自squashfs-tools包)。但要注意,Squashfs有多个版本(v3.0, v4.0, v4.1等),不同版本可能不兼容。我曾经遇到一个固件,用最新的unsquashfs解包失败,后来发现它是v3.0的老格式,需要指定-s参数。
# 查看Squashfs版本和压缩方式
unsquashfs -s firmware.squashfs
# 解包
unsquashfs -d output_dir firmware.squashfs
JFFS2
JFFS2 是日志结构的文件系统,专为Flash设计。它支持磨损均衡和掉电保护。但它是可读写的,所以固件中通常用于存放配置数据(如/etc/config)。
解包JFFS2比较麻烦,因为它不是块设备。我通常用jefferson工具(Python写的),或者用flash_erase + nanddump从硬件上读取。
YAFFS2
YAFFS2 是另一种专为NAND Flash设计的文件系统。它比JFFS2更简单、更快。在早期的Android手机和一些工业设备中常见。
解包YAFFS2,可以用unyaffs工具。但要注意,YAFFS2的镜像通常包含OOB(Out-of-Band)数据,解包时需要处理。
Cramfs
Cramfs 是古老的只读压缩文件系统。它非常小,但功能有限(最大文件不能超过16MB)。现在基本被Squashfs取代,但在一些老设备中还能见到。
解包Cramfs,用cramfsck或cramfsswap。如果遇到字节序问题(比如大端Cramfs在小端系统上解包),需要用cramfsswap转换。
| 文件系统 | 类型 | 压缩 | 常见场景 | 解包工具 |
|---|---|---|---|---|
| Squashfs | 只读 | gzip/lzma/lzo | 固件主文件系统 | unsquashfs |
| JFFS2 | 读写 | zlib | 配置分区 | jefferson |
| YAFFS2 | 读写 | 无 | NAND Flash | unyaffs |
| Cramfs | 只读 | zlib | 老设备 | cramfsck |
2.4 固件镜像结构分析
一个完整的固件镜像,通常不是单一的文件系统,而是由多个部分拼接而成。我把它比作一个三明治:头部 + Bootloader + Kernel + Rootfs + 尾部。
头部包含了固件的元信息:魔数(Magic Number)、版本号、硬件ID、各部分的偏移量和大小、校验和等。分析头部,是固件逆向的第一步。
我常用的方法是:
- 用
binwalk扫描:binwalk firmware.bin可以自动识别固件中的各个部分。 - 手动分析头部:用
hexdump或xxd查看头部字节,寻找魔数。比如,U-Boot的魔数是27051956(小端),Squashfs的魔数是hsqs或sqsh。 - 提取各部分:根据binwalk的输出,用
dd命令提取出Bootloader、Kernel、Rootfs。
# 用binwalk扫描固件
binwalk firmware.bin
# 手动提取Kernel(假设偏移是0x10000,大小是0x500000)
dd if=firmware.bin of=kernel.bin bs=1 skip=$((0x10000)) count=$((0x500000))
嗯,说到这里,我想起一个案例。有一次分析一个智能门锁的固件,binwalk只识别出了一个Squashfs文件系统。但我总觉得不对劲,因为固件大小有8MB,而Squashfs只有4MB。后来我用hexdump仔细看头部,发现头部里有一个字段标记了“双固件”模式——原来这个固件包含两个完整的系统镜像,一个运行,一个备份。而binwalk只识别出了第一个。所以,永远不要完全信任自动化工具,手动验证是必须的。
好了,这一章的内容就到这里。固件架构是后续所有分析的基础,花时间把这块吃透,后面会顺利很多。