2、嵌入式系统基础:常见嵌入式架构(ARM、MIPS、RISC-V)、固件组成与启动流程
嵌入式固件漏洞挖掘的第一步,是深刻理解目标系统“长什么样”。不同架构的指令集、寄存器模型、内存布局和启动流程,直接决定了固件逆向分析的切入点、Fuzzing 工具的选型以及漏洞利用的可行性。本章将系统梳理 ARM、MIPS、RISC-V 三大主流嵌入式架构的核心差异,剖析固件在 Flash 中的组成结构,并完整拆解从复位向量到操作系统加载的启动流程,为后续章节的固件提取、模拟执行和 Fuzzing 策略打下坚实的认知基础。
2.1 常见嵌入式架构核心原理
嵌入式设备的 CPU 架构决定了指令编码方式、寄存器组织、内存寻址模式以及异常处理机制。对于固件 Fuzzing 而言,架构差异直接影响反汇编工具的选择、内存映射的配置以及 Fuzzing 输入样本的构造方式。目前 IoT 设备中,ARM 占据主导地位,MIPS 在老旧路由器中仍有大量存量,RISC-V 则随着开源硬件浪潮快速渗透。
2.1.1 ARM 架构要点
ARM 是精简指令集(RISC)处理器,采用统一寻址空间(冯·诺依曼架构),支持 ARM(32位)和 Thumb/Thumb-2(16/32位混合)指令集。Cortex-M 系列(如 STM32)常用于 MCU,Cortex-A 系列(如高通、博通 SoC)用于应用处理器。ARM 有 16 个通用寄存器(R0-R15),其中 R13 为栈指针 SP,R14 为链接寄存器 LR,R15 为程序计数器 PC。异常向量表通常位于地址 0x00000000 或 0xFFFF0000。在 Fuzzing 中,ARM 的指令长度不固定(ARM 模式 4 字节,Thumb 模式 2 字节),反汇编时需正确识别模式切换(BX/BLX 指令)。
2.1.2 MIPS 架构要点
MIPS 是另一种经典 RISC 架构,采用固定 32 位指令长度,具有 32 个通用寄存器(R0-R31)。MIPS 最显著的特征是延迟槽(Delay Slot):分支指令后的下一条指令无论分支是否跳转都会被执行。这一特性在 Fuzzing 崩溃分析时容易造成指令流理解的偏差。MIPS 支持小端和大端两种字节序,常见路由器(如 MTK、Ralink)多为小端,而部分 Broadcom 芯片使用大端。MIPS 没有条件码寄存器,比较操作通过寄存器结果判断。由于 MIPS 在路由器中存量巨大,针对 MIPS 的 Fuzzing 工具(如 AFL-QEMU 模式)需要特殊处理延迟槽。
2.1.3 RISC-V 架构要点
RISC-V 是新兴的开源指令集架构,采用模块化设计,基础指令集 RV32I/RV64I 仅包含 40 多条指令,扩展指令集(M 乘法、A 原子、F/D 浮点、C 压缩)按需组合。RISC-V 有 32 个通用寄存器(x0-x31),其中 x0 硬编码为 0。RISC-V 没有延迟槽,指令长度可变(标准 32 位,压缩指令 16 位)。其异常处理机制通过 MTVEC(机器陷阱向量基址)寄存器指定入口。RISC-V 在安全芯片、边缘 AI 设备中增长迅速,Fuzzing 时需注意其可扩展指令可能带来的反汇编挑战。
| 特性 | ARM (Cortex-A/M) | MIPS (r4k/24k) | RISC-V (RV32/64) |
|---|---|---|---|
| 指令长度 | 4字节(ARM)/2字节(Thumb) | 固定4字节 | 4字节/2字节(压缩) |
| 通用寄存器 | 16个(R0-R15) | 32个(R0-R31) | 32个(x0-x31) |
| 延迟槽 | 无 | 有(分支后一条执行) | 无 |
| 字节序 | 小端为主 | 可配置(常见小端) | 小端为主 |
| 典型设备 | 手机/路由器/IP摄像头 | 老旧路由器/工控 | AI加速卡/安全MCU |
| Fuzzing难点 | Thumb/ARM切换 | 延迟槽导致指令流误判 | 扩展指令集不统一 |
file 命令或 readelf -h 确认固件中二进制文件的架构和字节序。例如,readelf -h busybox 输出中的 Machine 字段会明确显示 ARM/MIPS/RISC-V,Data 字段显示 little/big endian。错误的架构配置会导致 Fuzzing 工具完全无法执行目标代码。
2.2 固件组成与文件系统结构
嵌入式固件通常是一个完整的系统镜像,烧录在 SPI Flash 或 NAND Flash 中。理解固件的组成结构是提取文件系统、定位目标程序、配置模拟环境的前提。一个典型的固件镜像由引导加载程序、内核、文件系统、配置数据和元数据组成。
2.2.1 固件镜像的典型分区
固件在 Flash 中的布局通常遵循 SoC 厂商的参考设计。以常见的 16MB SPI Flash 为例,其分区可能如下:Bootloader(U-Boot)占用前 256KB-1MB,内核镜像(zImage/Image)占用 2-4MB,根文件系统(SquashFS/JFFS2)占用剩余大部分空间,末尾可能还有 U-Boot 环境变量区和厂商私有配置区。使用 binwalk 工具可以快速扫描固件中的文件签名,识别出各个分区的偏移和大小。
# 使用 binwalk 扫描固件结构
$ binwalk firmware.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 U-Boot version string, "U-Boot 2013.07"
262144 0x40000 Linux kernel ARM boot executable zImage (little-endian)
5242880 0x500000 Squashfs filesystem, little endian, version 4.0
15728640 0xF00000 JFFS2 filesystem, little endian
2.2.2 文件系统类型与提取
常见的嵌入式文件系统包括:SquashFS(只读压缩,广泛用于路由器)、JFFS2(日志闪存文件系统,支持磨损均衡)、UBIFS(用于 NAND Flash)、ext4(部分高端设备)、CramFS(老旧设备)。提取文件系统时,可以使用 binwalk -e 自动提取,或使用 unsquashfs、jefferson(JFFS2)、ubireader(UBI)等专用工具。提取后,需要重点关注 /bin、/sbin、/usr/bin 下的可执行文件,以及 /etc 下的配置文件(如 passwd、shadow、启动脚本)。
2.2.3 固件头部与加密签名
许多厂商会在固件头部添加自定义的魔数、版本号、校验和或加密签名。例如,TP-Link 固件头部包含 0x01000000 魔数,D-Link 固件可能包含加密段。在 Fuzzing 前,需要识别并剥离这些头部,否则模拟器无法正确解析。使用 binwalk -A 可以查看固件中的 ASCII 字符串,帮助识别厂商信息。对于加密固件,需要先进行解密(如通过硬件调试接口获取密钥或利用已知漏洞),才能进行后续分析。
2.3 启动流程深度拆解
固件的启动流程是从上电复位到操作系统运行起来的完整链路。对于 Fuzzing 而言,理解启动流程有助于确定哪些代码在启动早期执行、哪些输入数据可控,从而找到最早的攻击入口。例如,如果 Bootloader 解析网络数据包(如 TFTP 下载),那么网络接口就是一个潜在的 Fuzzing 目标。
2.3.1 复位向量与 Bootloader 第一阶段
CPU 上电后,程序计数器(PC)被设置为复位向量地址。对于 ARM,通常是 0x00000000 或 0xFFFF0000;对于 MIPS,通常是 0xBFC00000(映射到 Flash 起始地址)。Bootloader 的第一阶段(通常是汇编代码)负责初始化 CPU 模式、设置栈指针、关闭看门狗、初始化 DDR 控制器。这一阶段代码量小,但一旦存在漏洞(如栈溢出),由于此时内存尚未完全初始化,利用难度较高,但崩溃分析相对简单。
2.3.2 Bootloader 第二阶段与内核加载
第二阶段(通常是 C 代码,如 U-Boot 的 board_init)会初始化串口、Flash、网络接口,并进入交互命令行(如果配置了)。U-Boot 支持通过 TFTP、USB、SD 卡等介质加载内核。加载内核时,U-Boot 会将内核镜像从 Flash 复制到 RAM 中的指定地址(如 0x80008000),然后设置启动参数(ATAG 或设备树 DTB),最后跳转到内核入口。对于 Fuzzing,U-Boot 的网络协议栈(如 TFTP 客户端)和文件系统解析(如 ext4 加载)是常见的攻击面。
2.3.3 内核启动与根文件系统挂载
内核启动后,会进行架构初始化、内存管理初始化、驱动加载,然后根据启动参数(root=)挂载根文件系统。挂载成功后,内核执行 /sbin/init 进程(PID 1),该进程根据 /etc/inittab 或 systemd 配置启动系统服务。在 Fuzzing 中,我们通常关注内核启动后自动运行的服务,如 Web 服务器(httpd)、UPnP 服务、智能家居协议栈(Zigbee/Z-Wave 网关)等。这些服务直接暴露于网络,是漏洞挖掘的重点。
bootargs 添加 init=/bin/sh,从而绕过正常启动流程获得 root shell。从 Fuzzing 视角看,如果 U-Boot 的 TFTP 客户端存在缓冲区溢出,攻击者无需物理接触设备,通过网络即可实现任意代码执行。我们使用 AFL 对 U-Boot 的 TFTP 下载功能进行 Fuzzing,构造了包含超长文件名字段的 RRQ 请求包,在模拟环境中触发了 strcpy 溢出,证明了该攻击面的可利用性。
2.4 常见误区与总结复盘
在嵌入式 Fuzzing 的初始阶段,从业者常犯以下错误,导致分析效率低下或结果不可靠。
- 误区一:忽略架构差异。直接使用 x86 的 Fuzzing 工具(如 AFL 原生模式)处理 ARM 固件,导致无法执行。应使用 QEMU 的用户态模拟(
qemu-arm)或系统态模拟(qemu-system-arm)。 - 误区二:不剥离固件头部。将整个固件镜像直接加载到模拟器,导致内核无法识别文件系统。应先使用
binwalk提取出内核和文件系统,再分别处理。 - 误区三:忽视字节序。MIPS 大端固件使用小端工具链分析,导致反汇编结果完全错误。务必使用
readelf -h确认字节序。 - 误区四:只关注应用层。认为 Fuzzing 只针对 Web 服务,忽略了 Bootloader 和内核驱动的攻击面。实际上,许多 IoT 设备的漏洞位于硬件驱动层(如 ioctl 命令处理)。