2、固件基础与结构:固件在嵌入式系统中的角色、常见固件格式(BIN/HEX/ELF)、固件启动流程(Bootloader->Kernel->Rootfs)
大家好,我是老周。今天咱们聊聊固件的基础结构。说实话,这章是整个课程的基石。你想想看,连固件长什么样都不清楚,后面怎么逆向分析?怎么挖漏洞?
我在做安全评估的时候,经常遇到一些朋友,上来就问「怎么破解这个设备」。我一般会反问一句:你先告诉我,它的固件是什么格式?启动流程是怎样的?嗯,大部分人就卡住了。
所以,咱们先把地基打牢。
2.1 固件在嵌入式系统中的角色
固件是什么?说白了,就是嵌入在硬件设备里的软件。它不像Windows、Linux那样装在硬盘上,而是直接烧录在Flash、ROM这类非易失性存储器里。
它的角色,我总结为三点:
- 硬件初始化:上电后,CPU、内存、外设这些硬件,都得靠固件来「唤醒」和配置。
- 系统引导:把操作系统内核加载到内存,然后跳转执行。
- 应用功能:很多小型设备(比如智能插座、传感器),固件本身就包含了全部业务逻辑,没有独立的操作系统。
重要提醒:在安全分析中,固件就是攻击者的「第一战场」。你拿到了固件,就等于拿到了设备的「灵魂」。我曾经在一个路由器的固件里,直接找到了硬编码的后门账号——嗯,那家厂商后来被通报了。
2.2 常见固件格式:BIN / HEX / ELF
做逆向分析,你得先认识这三种最常见的格式。我刚开始接触时也搞混过,后来踩了几个坑才彻底搞明白。
2.2.1 BIN 格式(原始二进制)
BIN 文件,就是最纯粹的二进制数据。没有头,没有尾,没有元信息。它是什么?就是内存或Flash的「裸拷贝」。
- 特点:简单、直接、体积小。
- 缺点:没有地址信息,你不知道这段数据该加载到哪个地址。
- 分析工具:binwalk、hexdump、010 Editor。
我记得有一次,客户给了一个BIN文件,说「帮我看看里面有什么」。我直接用binwalk跑了一下,发现里面嵌了一个完整的Linux内核和文件系统。嗯,这种「裸奔」的固件,在IoT设备里非常常见。
2.2.2 HEX 格式(Intel HEX)
HEX 格式,是文本形式的二进制数据。每一行都包含地址、数据类型和校验和。它比BIN多了地址信息。
举个例子,一个典型的HEX行是这样的:
:10000000FF00400000000000000000000000000000E0
拆开来看:
:起始标记10本行数据长度(16字节)0000起始地址00数据类型(00=数据,01=文件结束)FF004000...实际数据E0校验和
个人技巧:我习惯用 objcopy 把HEX转成BIN,然后再用binwalk分析。因为binwalk对BIN的支持最好,对HEX反而容易出问题。
2.2.3 ELF 格式(Executable and Linkable Format)
ELF 是Linux世界里最常用的可执行文件格式。它比BIN和HEX都复杂,包含了:
- ELF头:魔数、架构、入口点等。
- 程序头:描述如何加载到内存。
- 节头:描述代码段、数据段、符号表等。
在嵌入式领域,很多Bootloader和内核镜像都是ELF格式。比如U-Boot,编译出来就是ELF文件。
我建议你用 readelf -h 和 readelf -l 来查看ELF的结构。举个例子:
$ readelf -h u-boot.elf
ELF Header:
Magic: 7f 45 4c 46
Class: ELF32
Machine: ARM
Entry point address: 0x00000000
看到那个 7f 45 4c 46 了吗?这就是ELF的魔数,也就是 .ELF 的ASCII码。嗯,这个细节在逆向分析时很有用——你可以在二进制文件里搜索这个魔数,快速定位ELF镜像。
2.3 固件启动流程:Bootloader → Kernel → Rootfs
这是嵌入式系统启动的「三步曲」。我画了一张图,帮你直观理解:
这张图,我建议你保存下来。每次分析固件时,对照着看,思路会清晰很多。
2.3.1 第一阶段:Bootloader
Bootloader 是系统上电后第一个执行的程序。它的任务很简单:初始化硬件,然后加载内核。
常见的Bootloader有:
| 名称 | 适用架构 | 特点 |
|---|---|---|
| U-Boot | ARM、MIPS、x86等 | 功能强大,支持网络启动、Flash烧写 |
| Barebox | ARM、MIPS | 类似Linux内核,模块化设计 |
| RedBoot | ARM、MIPS | 轻量级,常用于网络设备 |
| Coreboot | x86、ARM | 极速启动,常用于PC/服务器 |
避坑指南:我曾经遇到一个设备,Bootloader没有锁定。攻击者通过串口中断了启动过程,直接进入了U-Boot命令行。然后他修改了启动参数,加载了自己的恶意内核。嗯,这就是典型的「Bootloader攻击」。所以,生产环境中一定要锁定Bootloader,禁用不必要的调试接口。
2.3.2 第二阶段:Kernel
Bootloader 把内核加载到内存后,就跳转到内核入口点。内核开始执行后,会做以下几件事:
- 解压自身:很多嵌入式内核是压缩的(比如zImage、uImage),需要先解压。
- 初始化硬件抽象层:设置MMU、中断控制器、定时器等。
- 加载驱动:根据设备树(Device Tree)或ACPI,加载对应的硬件驱动。
- 挂载根文件系统:这是最后一步,也是最关键的一步。
在安全分析中,内核镜像往往是重点。我习惯用 binwalk -Me 先解包,然后用 strings 搜索敏感信息。比如:
$ strings vmlinux.bin | grep -i "password\|key\|secret"
你猜怎么着?我经常能在内核里找到硬编码的密钥或者调试密码。嗯,厂商们总是「忘记」清理这些调试信息。
2.3.3 第三阶段:Rootfs
根文件系统(Rootfs)是系统启动后的「家」。它包含了所有用户空间的程序、库、配置文件。
常见的根文件系统格式:
- SquashFS:只读、高压缩比。常用于路由器、摄像头。
- JFFS2 / UBIFS:可读写、支持闪存。常用于高端设备。
- initramfs:内存中的文件系统,常用于临时启动。
- YAFFS2:专为NAND Flash设计。
我个人习惯,拿到固件后第一件事就是解包Rootfs。因为这里藏着最多的「宝藏」:
- 硬编码的密码
- 私钥和证书
- 调试后门
- 过时的漏洞库
实用技巧:解包SquashFS时,我常用 unsquashfs 命令。如果遇到未知格式,试试 binwalk -Me,它能自动识别并解包大部分文件系统。
2.4 小结
好了,这一章的内容就到这里。我们讲了固件的角色、三种常见格式(BIN/HEX/ELF),以及启动流程的三部曲。这些都是基本功,但也是最容易被忽视的。
我记得刚入行时,总觉得这些「太简单」,直接跳过去学高级技巧。结果呢?分析一个固件时,连它是什么格式都没搞清楚,折腾了半天。嗯,从那以后,我每分析一个固件,都会先做这三步:
- 识别固件格式
- 提取文件系统
- 分析启动脚本
你试试看,按照这个流程走一遍,很多问题会迎刃而解。