3. Bootloader 安全:U-Boot 配置、签名验证与漏洞挖掘
Bootloader 是硬件上电后第一个运行的软件。它的安全直接决定了整个启动链的可信度。我见过太多团队把精力花在应用层加密上,却忽略了 Bootloader 这个“守门员”。门没锁,墙修再高也没用。
这一章,我们聚焦三个核心问题:U-Boot 怎么配才安全?签名验证到底怎么落地?攻击者通常从哪里下手?
3.1 U-Boot 安全配置:别让默认设置害了你
U-Boot 是嵌入式 Linux 世界的事实标准。但它的默认配置,坦白说,是为了开发便利,不是为了安全。我接手过一个项目,产品出厂后被人通过串口中断启动,直接进了 U-Boot shell,然后 dump 了整个文件系统。原因?开发者忘了关掉 bootdelay 和 stop_boot 功能。
3.1.1 必须关闭的“开发后门”
| 配置项 | 风险说明 | 建议操作 |
|---|---|---|
CONFIG_BOOTDELAY |
启动时等待按键中断,进入命令行 | 设为 -1,彻底禁用中断 |
CONFIG_SYS_PROMPT |
暴露 U-Boot 版本和编译信息 | 改为自定义字符串,隐藏版本号 |
CONFIG_CMD_USB |
允许从 USB 设备加载镜像 | 量产固件中移除该命令 |
CONFIG_CMD_NET |
允许通过网络加载镜像 | 量产固件中移除该命令 |
CONFIG_ENV_IS_IN_FLASH |
环境变量可被物理篡改 | 配合签名校验,或使用只读存储 |
3.1.2 环境变量保护
U-Boot 的环境变量存储在 Flash 或 EEPROM 中。攻击者如果能修改 bootcmd 变量,就能加载任意代码。我建议的做法是:
- 锁定关键变量:使用
setenv -l锁定bootcmd、bootargs等变量,防止运行时修改。 - 环境变量签名:对环境变量区域计算 HMAC,启动时验证完整性。
- 冗余备份:在 Flash 中保留一份只读的出厂默认环境,用于恢复。
3.2 Bootloader 签名验证机制:信任链的第一环
签名验证的核心逻辑很简单:Bootloader 在加载下一阶段镜像(如 Linux 内核)之前,先验证镜像的数字签名。只有签名通过,才允许执行。但“简单”不等于“容易做对”。
3.2.1 典型的验证流程
我画了一张图,帮你理解整个流程:
3.2.2 签名验证的常见实现方式
U-Boot 支持两种主流的签名验证方式:
- FIT Image(Flattened Image Tree):U-Boot 原生支持的镜像格式。镜像本身包含一个描述头,里面存放了哈希值和签名。U-Boot 在加载时会自动校验。
- 外部签名工具:使用
mkimage工具对镜像进行签名,然后将签名附加到镜像尾部或单独存储。
我个人习惯用 FIT Image,因为它把镜像、设备树、ramdisk 打包在一起,签名和配置都在一个文件里,管理起来方便。而且 U-Boot 对 FIT 的支持很成熟,几乎不需要额外写代码。
3.2.3 一个典型的 FIT Image 签名配置
// 在 U-Boot 配置中启用 FIT 签名
CONFIG_FIT=y
CONFIG_FIT_SIGNATURE=y
CONFIG_RSA=y
CONFIG_RSA_SOFTWARE_EXP=y
CONFIG_SPL_FIT=y
CONFIG_SPL_FIT_SIGNATURE=y
// 使用 mkimage 对 FIT 镜像签名
mkimage -f fit.its fit.itb
mkimage -F -k /path/to/keys -r fit.itb
这里要注意:-r 参数会在签名后标记镜像为“只读”,防止后续被篡改。我见过有人忘了加这个参数,结果签名后的镜像还能被重新签名,等于没保护。
3.3 Bootloader 漏洞挖掘方法:攻击者视角
要防守,先得知道攻击者怎么打。我这些年做安全审计,发现 Bootloader 的漏洞主要集中在几个地方。
3.3.1 常见攻击面
| 攻击面 | 典型漏洞 | 危害等级 |
|---|---|---|
| 串口/调试接口 | 未禁用中断、无密码保护 | 严重 |
| 镜像加载路径 | 未校验镜像来源(USB/网络) | 严重 |
| 内存缓冲区 | 镜像解析时的缓冲区溢出 | 高危 |
| 环境变量 | 未锁定关键变量,可被篡改 | 高危 |
| 签名验证逻辑 | 验证失败后仍允许回退执行 | 严重 |
| 硬件调试接口 | JTAG/SWD 未锁定 | 严重 |
3.3.2 漏洞挖掘方法
我总结了一套实用的挖掘流程,你可以照着做:
- 固件提取:从 Flash 或固件包中提取 U-Boot 镜像。用
binwalk或dd命令。 - 静态分析:用 Ghidra 或 IDA Pro 反编译 U-Boot。重点关注字符串引用(如
bootcmd、load、verify)和命令处理函数。 - 动态调试:如果 JTAG 可用,直接硬件调试。否则通过串口交互,尝试输入异常命令或超长参数。
- 模糊测试:对镜像解析函数(如 FIT 解析、ext4 文件系统解析)进行 fuzz。我常用
afl-fuzz配合 QEMU 模拟。 - 逻辑漏洞挖掘:检查签名验证失败后的处理逻辑。很多实现会在验证失败后打印错误,但仍然继续执行。这是致命的。
💡 一个小技巧: 在反编译 U-Boot 时,搜索 bootm 命令的处理函数。这个函数负责加载和启动内核,通常包含了签名验证的核心逻辑。找到它,你就找到了整个信任链的“咽喉”。
3.3.3 一个真实案例:验证失败后的回退
我曾经审计过一个 IoT 网关设备。它的 U-Boot 实现了签名验证,但代码逻辑是这样的:
if (verify_signature(image) == 0) {
printf("Signature verification failed!\n");
// 注意:这里没有 return 或 goto
}
// 继续执行加载
bootm_image(image);
看到了吗?验证失败后只打印了日志,然后继续执行 bootm_image。攻击者只需要提供一个未签名的镜像,设备就会乖乖加载。这就是典型的“日志型安全”——看起来有验证,实际上形同虚设。
hang() 函数,让系统卡死。
3.4 总结一下
Bootloader 安全没有银弹。你需要做好三件事:
- 配置上:关掉所有开发后门,锁定环境变量,隐藏版本信息。
- 验证上:建立完整的信任链,每一级都要签名,验证失败必须死锁。
- 测试上:从攻击者视角去挖掘漏洞,别只做功能测试。
嗯,这一章的内容就到这里。记住:Bootloader 是信任链的根,根烂了,上面的一切都是空中楼阁。