4、安全启动链:BootROM、Bootloader、内核镜像签名与验证流程
安全启动链,说白了就是给系统上电后的每一步都加把锁。从芯片复位的第一条指令开始,到操作系统跑起来,中间任何一步被篡改,系统就得立刻罢工。我做了这么多年嵌入式安全,见过太多因为启动链没锁死,被黑客轻松植入后门的案例。
嗯,咱们今天就把这条链上的三个关键环节拆开来看:BootROM、Bootloader、内核镜像。它们各自怎么签名,怎么验证,环环相扣。
4.1 信任根:BootROM 的不可篡改性
整个安全链的起点,是芯片内部固化的 BootROM。这段代码是出厂时写死的,物理上无法修改。为什么?因为它是信任的根。如果根烂了,上面长出来的东西全不可信。
我个人习惯把 BootROM 比作「保险箱的锁芯」。锁芯本身是焊死在保险箱门上的,你没法换。攻击者想撬保险箱,要么破坏锁芯(物理攻击),要么猜密码(逻辑攻击)。BootROM 也一样,它只做两件事:
- 初始化硬件:时钟、内存、基本外设
- 验证下一级镜像:检查 Bootloader 的签名
这里有个关键点:BootROM 里必须硬编码一个公钥哈希,或者直接存公钥。我见过一些低成本芯片,为了省空间只存了哈希,然后从外部 Flash 读公钥来比对。这种做法其实有风险——如果攻击者能同时篡改公钥和签名,就能绕过验证。
我曾经在一个 IoT 项目里,发现芯片厂商为了节省几 KB 的 ROM 空间,把公钥存在了外部 OTP 中。结果攻击者通过电压毛刺攻击,让 OTP 读出错误值,成功绕过了签名验证。从那以后,我坚持公钥必须放在 BootROM 内部,哪怕多花点成本。
4.2 Bootloader 签名与验证
BootROM 验证通过后,控制权交给 Bootloader。Bootloader 通常分两阶段:
- SPL (Secondary Program Loader):极小,负责初始化 DDR
- U-Boot 或类似:完整功能,负责加载内核
签名流程其实不复杂。以我常用的 RSA-2048 + SHA-256 为例:
# 生成签名
openssl dgst -sha256 -sign private_key.pem -out kernel.sig kernel.bin
# 验证签名(在 Bootloader 中)
hash = SHA256(kernel.bin)
decrypted_hash = RSA_public_decrypt(signature, public_key)
if hash == decrypted_hash:
boot_kernel()
else:
panic()
你想想看,这里有个容易被忽略的细节:签名验证的对象是什么?是整个镜像文件,还是镜像的某个部分?
我个人建议:只对关键部分签名。比如内核镜像头部包含加载地址、入口点、大小这些元数据,这些必须签名。而镜像中的一些非关键数据段(比如调试符号表),可以跳过。为什么?因为签名验证本身有性能开销,尤其是在低端 MCU 上,每多验证 1MB 数据,启动时间可能增加几百毫秒。
我在一个车载项目中,Bootloader 需要验证 8MB 的内核镜像。用 RSA-2048 逐块验证,启动时间从 2 秒飙到了 12 秒。后来我们改成只验证镜像的哈希树(Merkle Tree)根节点,启动时间降回 3 秒以内。安全性和性能的平衡,永远是设计重点。
4.3 内核镜像的完整性保护
Bootloader 验证通过后,内核开始加载。但这里有个问题:内核运行起来后,怎么保证它没有被篡改?
嗯,其实内核镜像的签名验证,在 Bootloader 阶段就已经完成了。内核本身不需要再重复验证自己。但有一个例外——内核模块(.ko 文件)。
Linux 内核支持模块签名机制。当内核加载一个模块时,会检查该模块的签名是否与内核内置的公钥匹配。这个机制我在项目中踩过坑:
在一个网关设备上,我们只验证了内核镜像本身,没管内核模块。结果攻击者通过替换一个合法的 WiFi 驱动模块,植入了后门。因为模块加载时内核默认不检查签名,我们完全没发现。后来我强制开启了 CONFIG_MODULE_SIG_FORCE,所有模块必须签名才能加载。
内核镜像的签名验证流程,可以用下面这张图来概括:
4.4 哈希链与防回滚
签名验证保证了镜像的完整性,但还有一个问题:版本回滚攻击。
攻击者可能拿一个旧版本的、有已知漏洞的内核镜像来替换新版本。旧镜像的签名是合法的(因为当时就是用同一个私钥签的),所以签名验证能通过。但旧版本有安全漏洞,攻击者就能利用这些漏洞入侵系统。
怎么防?加一个版本号计数器。Bootloader 里保存一个「最小可接受版本号」,每次升级时递增。如果检测到镜像版本号小于这个值,直接拒绝启动。
安全启动链不是「验证一次就完事」,而是「每一级都验证下一级」。BootROM 验证 Bootloader,Bootloader 验证内核,内核验证模块。任何一环断了,整个信任链就崩塌了。
我记得有一次帮客户做安全审计,发现他们的 Bootloader 只验证了内核镜像的前 4KB。问为什么?工程师说「内核头部在开头,签名也在开头,所以只验证开头就够了」。这其实是个误区——攻击者完全可以修改内核的代码段,只要不动头部和签名区域,就能绕过验证。正确的做法是验证整个镜像,或者至少验证所有关键段。
最后说一句:安全启动链不是银弹。它只能防止启动时被篡改,但防不了运行时攻击。不过,作为第一道防线,它已经挡住了 90% 的常见攻击手段。剩下的,交给运行时保护和监控机制去处理。
• 公钥必须存储在 BootROM 或一次性可编程(OTP)存储器中
• 签名算法推荐 RSA-2048 或 ECDSA-P256
• 哈希算法推荐 SHA-256 或更高
• 务必实现防回滚机制,版本号存储在安全存储区
• 内核模块签名强制开启,不要留后门