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设备上特别常见。
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命令,应该只允许刷入签过名的镜像。
16.3.3 第三层:系统级防护
系统跑起来之后,也要防止恶意软件篡改Bootloader。
- dm-verity:设备映射器验证,确保系统分区在运行时不被篡改。如果检测到篡改,直接panic重启。
- AVB(Android Verified Boot):Android的验证启动框架,从Bootloader到系统分区,整个链条都做验证。
- SELinux策略:限制普通应用对块设备的访问权限。只有system和root用户才能写Boot分区。
16.4 知识体系结构图
下面这张图是我自己整理的Bootloader安全知识体系,你可以对照着看:
16.5 实战建议
最后,给你几条我踩过坑之后总结出来的建议:
- 别信任何外部输入。不管是USB发过来的数据,还是SD卡读出来的镜像,都要当恶意数据来处理。我在一个项目里就吃过这个亏——Fastboot模式下没校验镜像大小,结果被人塞了个超大的镜像搞崩了内存。
- 防回滚一定要做。哪怕你觉得「用户不会主动降级」,也要做。因为攻击者会。我建议用硬件计数器,每次升级加1,降级时检查计数器值。
- 日志要谨慎。Bootloader阶段的日志不要打印敏感信息,比如密钥、地址、版本号。我曾经见过某个厂商的Bootloader在串口日志里直接打印了RSA私钥的哈希——虽然只是哈希,但也给了攻击者线索。
- 测试要全面。边界值测试、模糊测试、压力测试,一个都不能少。Bootloader出问题就是变砖,没有后悔药。
公众号:蓝海资料掘金营,微信deep3321