16、Bootloader漏洞与安全:已知Bootloader漏洞案例、如何防止设备被恶意刷机

说到Bootloader安全,我脑子里第一个蹦出来的画面,是几年前一个同事抱着砖头一样的手机来找我。那台设备怎么按电源键都没反应,连充电指示灯都不亮。拆开一看,Bootloader被刷坏了。嗯,说白了就是被人用恶意固件把引导区给覆盖了。

今天我们就来聊聊Bootloader的那些安全漏洞,以及怎么防住恶意刷机。我个人习惯把Bootloader安全分成两个维度来看:一是攻击者怎么攻破它,二是我们怎么加固它。

16.1 已知的Bootloader漏洞案例

先看几个真实案例。这些漏洞可不是实验室里的理论模型,都是真实发生过、被CVE收录过的。

漏洞编号 影响设备 漏洞类型 后果
CVE-2016-10277 联发科MTK平台 Motorola Bootloader未锁定 可刷入任意系统
CVE-2017-15847 高通骁龙平台 ABOOT整数溢出 提权执行任意代码
CVE-2019-10538 三星Exynos平台 Bootloader绕过验证 绕过安全启动链
CVE-2020-26558 高通平台 PBL签名验证缺陷 持久化恶意固件

我挑两个典型的展开说说。

16.1.1 高通ABOOT整数溢出(CVE-2017-15847)

这个漏洞我记得特别清楚。高通平台的ABOOT在解析某些特定格式的镜像时,没有做边界检查。攻击者可以构造一个超大尺寸的镜像头,触发整数溢出,然后绕过签名验证。

为什么会这样?说白了就是C语言的老毛病——整数运算不自动检查溢出。ABOOT里有一段代码大概长这样:

// 漏洞代码简化版
uint32_t image_size = header.size;
uint32_t total_size = image_size + sizeof(header);
// 如果image_size是0xFFFFFFFF,total_size会变成0x100000003
// 截断后变成3,绕过大小检查
if (total_size > MAX_SIZE) {
    return ERROR;
}
memcpy(buffer, data, image_size); // 这里就崩了

我在项目中遇到过类似的问题。当时做安全启动验证,发现某个OEM厂商的Bootloader里也有类似的整数运算。嗯,这里要注意:所有涉及镜像大小的计算,一定要用安全函数,比如saturated_add或者先判断image_size < MAX_SIZE - sizeof(header)

16.1.2 三星Exynos Bootloader绕过验证(CVE-2019-10538)

这个漏洞更有意思。三星的Bootloader在验证签名时,只检查了第一个分区的签名,后续分区直接信任了。攻击者可以刷入一个合法的第一分区,然后替换后面的分区内容。

你想想看,这就像你只检查了门口的保安是不是本人,然后就让后面所有人都直接进了大楼。我当年看到这个漏洞报告时,第一反应是:这设计也太天真了。

核心教训:安全启动链必须环环相扣,每个环节都要独立验证。不能因为上一级通过了,就信任下一级。

16.2 恶意刷机的常见手法

攻击者想搞你的设备,通常走这几条路:

  • 物理攻击:直接拆机,用JTAG/SWD接口读取或写入Bootloader。我见过有人用树莓派加几个杜邦线,就能从eMMC里把Bootloader读出来。
  • 软件攻击:通过系统漏洞提权后,直接写Boot分区。比如Root后的设备,用dd if=/sdcard/malicious.img of=/dev/block/bootdevice/by-name/aboot这种命令。
  • OTA劫持:伪造系统更新包,在更新过程中替换Bootloader。有些设备在OTA时没有校验完整签名链。
  • 降级攻击:刷回有漏洞的旧版本Bootloader,然后用已知漏洞提权。这个在Android设备上特别常见。
警告:降级攻击是最容易被忽视的。很多厂商只防新版本被篡改,却忘了防旧版本回滚。我建议所有Bootloader都要实现anti-rollback机制,也就是防回滚计数器。

16.3 如何防止设备被恶意刷机

好了,前面说了这么多漏洞,现在聊聊怎么防。我总结了一套「三层防护」的思路,你可以参考一下。

16.3.1 第一层:硬件级防护

这是最底层、最可靠的防护。说白了就是让攻击者连碰都碰不到Bootloader。

  • eFuse/Otp:一次性可编程存储,用来存储根公钥哈希和防回滚版本号。一旦烧写,无法修改。
  • 安全启动ROM:芯片内部固化的PBL(Primary Boot Loader),只认签过名的下一级镜像。这个ROM是只读的,物理上无法修改。
  • JTAG熔断:量产时熔断JTAG/SWD调试接口,防止物理调试。我见过有些设备出厂时忘了熔断,结果被攻击者用OpenOCD直接读出了整个Flash。

16.3.2 第二层:Bootloader自身加固

Bootloader代码本身也要做好安全防护。这部分是我最常跟团队强调的。

  • 签名验证全覆盖:每个分区、每个镜像都要独立验证。不能只验证第一个。
  • 防回滚计数器:每次升级Bootloader时,检查版本号是否大于等于当前版本。小于就拒绝。
  • 输入验证:所有从外部传入的数据(USB、UART、SD卡)都要做边界检查、类型检查。整数溢出、缓冲区溢出这些老毛病一个都不能有。
  • 最小权限原则:Bootloader在早期阶段只做必要的事,不要开放太多功能。比如Fastboot模式下的flash命令,应该只允许刷入签过名的镜像。
小技巧:我曾经在项目里加了一个「熔断开关」——如果检测到连续3次签名验证失败,就永久锁定Bootloader,只能返厂维修。这招虽然狠了点,但能有效防止暴力破解。

16.3.3 第三层:系统级防护

系统跑起来之后,也要防止恶意软件篡改Bootloader。

  • dm-verity:设备映射器验证,确保系统分区在运行时不被篡改。如果检测到篡改,直接panic重启。
  • AVB(Android Verified Boot):Android的验证启动框架,从Bootloader到系统分区,整个链条都做验证。
  • SELinux策略:限制普通应用对块设备的访问权限。只有system和root用户才能写Boot分区。

16.4 知识体系结构图

下面这张图是我自己整理的Bootloader安全知识体系,你可以对照着看:

Bootloader安全知识体系 攻击面 物理攻击(JTAG/SWD) 软件攻击(提权写分区) OTA劫持 / 降级攻击 三层防护体系 硬件级防护 eFuse / 安全ROM / JTAG熔断 Bootloader自身加固 签名验证 / 防回滚 / 输入检查 系统级防护 dm-verity / AVB / SELinux 目标:构建可信启动链,防止恶意固件持久化

16.5 实战建议

最后,给你几条我踩过坑之后总结出来的建议:

  1. 别信任何外部输入。不管是USB发过来的数据,还是SD卡读出来的镜像,都要当恶意数据来处理。我在一个项目里就吃过这个亏——Fastboot模式下没校验镜像大小,结果被人塞了个超大的镜像搞崩了内存。
  2. 防回滚一定要做。哪怕你觉得「用户不会主动降级」,也要做。因为攻击者会。我建议用硬件计数器,每次升级加1,降级时检查计数器值。
  3. 日志要谨慎。Bootloader阶段的日志不要打印敏感信息,比如密钥、地址、版本号。我曾经见过某个厂商的Bootloader在串口日志里直接打印了RSA私钥的哈希——虽然只是哈希,但也给了攻击者线索。
  4. 测试要全面。边界值测试、模糊测试、压力测试,一个都不能少。Bootloader出问题就是变砖,没有后悔药。
一句话总结:Bootloader安全没有银弹。硬件、软件、系统三层都要做,缺一层都可能被攻破。我做了这么多年底层,最大的体会就是——安全不是加一个功能,而是一种设计思维。

公众号:蓝海资料掘金营,微信deep3321