3. 安全启动链与回滚:安全启动流程、回滚对安全启动的威胁、信任根的保护
安全启动链,说白了就是一套「谁验证谁」的链条。从芯片上电的第一行代码开始,每一级都要验下一级的签名,验不过就罢工。我做了这么多年固件,见过太多人只盯着应用层加密,却忽略了启动链上最底层的那个信任根——那才是真正要命的地方。
3.1 安全启动的标准流程
一个典型的安全启动链长这样:
- ROM Bootloader(不可变):芯片出厂固化,只验下一级 Bootloader 的公钥哈希。
- First Stage Bootloader(FSBL):验 Second Stage 的签名,通常包含硬件初始化。
- Second Stage Bootloader(SSBL):验 OS Kernel 或 Bootloader 的签名。
- OS Kernel:验文件系统或关键服务的签名。
- 应用层:验升级包、配置文件的签名。
每一级只信任上一级给它的公钥。这个公钥要么烧死在 eFuse 里,要么嵌在 ROM 代码中。我习惯把这种结构叫做「洋葱模型」——剥一层,验一层。
核心原则:信任链的长度决定了攻击面。链越长,中间被篡改的风险就越大。所以很多芯片厂商把信任根直接放在 ROM 里,连 eFuse 都省了。
3.2 回滚对安全启动的威胁
回滚攻击,本质上就是「降级」。攻击者把设备上的固件换成一个有已知漏洞的老版本,然后利用那个漏洞提权。你想想看,安全启动链验签名验得再严,如果老版本也是官方签过名的,那它照样能通过验证。
我在项目中遇到过一起真实案例:某 IoT 网关设备,安全启动链做得滴水不漏,但升级时没做版本号检查。攻击者把固件回滚到两年前的版本,那个版本有个缓冲区溢出漏洞,结果整个信任链被从应用层捅穿。嗯,这就是典型的「签名合法,版本有毒」。
回滚攻击的典型路径:
- 攻击者获取旧版本固件(官方签名有效)。
- 通过升级接口或物理手段刷入旧版本。
- 旧版本启动,安全链验证通过(签名合法)。
- 利用旧版本已知漏洞提权,篡改信任链。
为什么安全启动防不住回滚?因为安全启动只关心「签名对不对」,不关心「版本新不新」。说白了,它是个静态验证机制,没有状态感知能力。要防回滚,必须在信任链里加入版本号比较逻辑。
3.3 信任根的保护策略
信任根是整个安全启动链的基石。如果信任根被破了,那后面的一切都是白搭。我习惯把信任根的保护分成三个层次:
| 层次 | 存储位置 | 保护方式 | 可更新性 |
|---|---|---|---|
| 硬件信任根 | ROM / eFuse | 物理不可更改,只读 | 不可更新 |
| 软件信任根 | Bootloader 头部 | 签名验证 + 版本号检查 | 可更新(需签名) |
| 运行时信任根 | 安全内存 / TEE | 隔离执行 + 内存保护 | 运行时保护 |
硬件信任根是最安全的,但也是最难改的。一旦芯片流片,ROM 里的代码就焊死了。所以很多厂商把公钥哈希放在 eFuse 里,这样至少还能在产线阶段烧录不同的密钥。
我个人的建议:如果芯片支持 eFuse,尽量把信任根的公钥哈希烧进去,而不是嵌在 ROM 里。这样即使密钥泄露,还能通过更换芯片批次来补救。当然,eFuse 是一次性编程的,烧之前一定要反复确认。
3.4 信任根与回滚保护的结合
把信任根和回滚保护结合起来,才是完整的安全启动方案。具体怎么做?我总结了一套「三明治」策略:
- 底层(信任根):ROM 或 eFuse 中存储根公钥哈希,不可更改。
- 中间层(版本锚点):在 Bootloader 中增加版本号寄存器,每次启动时比较当前版本与存储版本。
- 上层(升级策略):升级包必须携带版本号,且只能递增。降级请求直接拒绝。
我曾经在一个车机项目里用过这个方案。当时有个坑:版本号寄存器用的是 Flash 存储,结果攻击者直接物理擦除 Flash,版本号归零,然后刷入旧版本。后来我们改成了 eFuse 递增计数器——每次升级烧一根 eFuse,物理上无法回退。嗯,代价是 eFuse 数量有限,升级次数不能太多。
关键设计原则:
- 版本号必须存储在不可回滚的介质中(eFuse / OTP / TEE 安全存储)。
- 版本号比较必须在信任根验证之后、加载下一级之前执行。
- 升级包必须包含版本号字段,且签名覆盖版本号。
3.5 常见陷阱与避坑指南
做安全启动链和回滚保护,有几个坑我踩过,分享给你:
- 陷阱一:版本号不参与签名。攻击者可以修改版本号字段,绕过回滚检查。我曾经见过一个方案,版本号放在固件头部但没被签名覆盖,结果攻击者把版本号改成 99.99.99,所有旧版本都能刷进去。
- 陷阱二:信任根存储在可写介质。有些低成本芯片把根公钥放在 SPI Flash 里,攻击者直接替换 Flash 芯片就能绕过。嗯,这已经不是安全启动,是「信任幻觉」了。
- 陷阱三:回滚检查只在升级时做。攻击者可以在升级完成后、重启之前篡改版本号。所以回滚检查必须在每次启动时都执行,不能只在升级流程里做。
避坑总结:信任根要硬(不可写),版本号要硬(不可回滚),签名要全(覆盖版本号),检查要勤(每次启动都验)。少一条,你的安全启动链就是纸糊的。