用故障注入和二进制分析对BootLoader实施攻击
双电压毛刺的故障攻击,侧信道史上首次!
背景介绍
Bootloader是一段在嵌入式操作系统内核运行之前,用于初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境的程序段,可以理解成BIOS,因此Bootloader对于嵌入式系统的安全运行起到至关重要的作用。
2020年12月,来自伯明翰大学的Jan、David、Flavio等人在顶级学术期刊TCHES上发表了一篇论文,该论文所做的事情就是通过硬件及软件手段绕过Bootloader的保护机制,从而读取微控制器内敏感的固件信息,避免与复杂的密码算法打交道,其中硬件手段部分完成了侧信道史上首次双电压毛刺的故障攻击。
这篇论文的结构如下:论文首先介绍了实验配置,然后依次对三家厂商的四种型号的微控制器进行了分析与实验,最后总结了在实验过程中发现的不安全的bootloader设计。
实验配置
论文的实验配置如图1所示,μC是目标微控制器,GIAnT用于生成时钟毛刺,Raspberry Pi用于给微控制器的bootloader发送指令,以及给GIAnT配置电压毛刺参数。右图为毛刺信号示意图,其中VF为毛刺电压,T为毛刺偏移,W为毛刺宽度。
图1 实验配置图,左图为实验装置,右图为毛刺信号,其中VF为毛刺电压,T为毛刺偏移,W为毛刺宽度。
实验过程
接下来论文依次对三家厂商的四种型号的微控制器的bootloader进行了分析与实验。
NXP LPC1xxx bootloader
恩智浦的LPC1系列bootloader根据CRP防护等级的不同存在不同级别的防护。其中论文作者通过反汇编bootloader的二进制代码并分析后发现,LPC1系列芯片的bootloader在CRP1防护等级下存在软件漏洞,在向内存写入数据时不检查写入地址是否在栈内,导致攻击者可以通过栈溢出的方式调用在CRP1防护下本应被禁用的”Read Memory”指令,从而绕过CRP1防护读取RAM中的数据。
图2是栈溢出的ROP链。其中从0x10001f54地址开始,栈内11个字的数据因为栈溢出攻击而被覆写,0x10001f54为”Write memory” 指令的返回地址,被修改成了0x1fff0cfb,刚好就是”Read memory” 指令中检查CRP防护级别后的第一条语句。
图2 ROP链,0x10001f54为“Write memory”返回地址,0x1fff0cfa为“Read memory”指令里检查CRP防护级别后的第一条语句,蓝色的FC020000为“Read memory”指令读取的地址,绿色的0x1fff0e80为执行完一个pop语句后程序回归正常运行状态。
STM8 bootloader
本节论文分析了STM8L和STM8A两个型号芯片的bootloader,图3是将bootloader二进制代码反汇编后得到的程序流图。以STM8L芯片为例,为了使程序进入_SERIAL_BL状态,即串行调试状态,需要注入两个电压毛刺,第一个毛刺跳过chk_empty或者chk_bl函数,即检查芯片是否为空或者检查“Bootloader Enable”的值;第二个毛刺跳过chk_crp,即检查芯片是否有crp防护。
图3 STM8L和STM8A bootloader程序流图,第一个电压毛刺使程序从chk_empty或者chk_bl状态进入chk_crp状态,第二个电压毛刺使程序从chk_crp状态进入_SERIAL_BL状态。
直接进行电压毛刺的故障攻击是困难的,因为在两个电压毛刺都成功注入前,芯片不会有任何反应,这导致我们无法根据芯片的反应来调整电压毛刺的参数,使得毛刺参数的搜索空间变得非常庞大。为了减小毛刺参数的搜索空间,提高毛刺的注入效率,论文将影响程序执行的关键代码段(在本小节就是指chk_empty、chk_bl和chk_crp)读入到用户程序中,然后对这些代码段单独进行毛刺注入的测试,极大地减少了电压毛刺注入的参数选择范围,提高了故障攻击调参的速度,最终实现了双电压毛刺的故障攻击。
图4 双电压毛刺故障攻击,第一个毛刺位置为检查芯片是否为空或者BL值,第二个毛刺位置为检查CRP值。
Renesas 78K0 bootloader
本节对瑞萨的78K0芯片的bootloader进行了分析,不同于STM系列芯片,芯片启动后会立刻检查防护情况,除非防护被无效否则不会继续运行。78K0芯片在启用CRP防护机制后,只允许特定安全的指令可以运行,即verify和checksum。本节通过电压毛刺修改两条指令执行的最小字节数,从256字节减少至4字节,从而破解出了内存信息。
同样为了减少电压毛刺的参数组合,本节使用了symbolic execution(符号执行)的方法,将特定指令的输入参数分成了若干等价类,从而提高了注入电压毛刺的效率。
如图5所示,将执行路径相同的输入归类为一个等价类,可以极大地提高电压毛刺偏移选择的速度。
图5 get_block_no模块输入参数等价类示意,图中两条路线对应等价类0x1004c和0x5ff。
如图6所示,论文将checksum函数的输入分成了9个等价类,由于执行路径不同,9个等价类需要设置不同的毛刺偏移。由此,大大减小了电压毛刺的参数的搜索空间。
图6 checksum函数第一个输入参数的9个等价类。
论文结论
攻击的目的是为了防御,本文攻击的设备都是商用产品,因此论文在结尾根据实验过程中的发现,提出了10条在设计微控制器时应该加以防护的漏洞,如下所示:
①在受保护状态下的部分RAM写权限:有多级保护机制的芯片往往允许对芯片内存的有限debug。没有MMU的芯片应该保证bootloader的内存空间与用户可以存取的内存空间是分开的。
②内存或寄存器的部分泄露:在CRP防护被启用时,有些芯片仍然提供对内存或者寄存器的读取权限。利用这一漏洞,Obermaier等人引入了“cold boot stepping”,基于SRAM快照重建了程序的控制流。此外,通过单步加载指令和修改CPU寄存器,Brosch恢复出了蓝牙芯片的固件信息。
③部分flash覆写:有一个sector的写权限本质上给予了攻击者对整个芯片的读权限。许多系统可以通过覆写一个flash sector,使其输出整个芯片内存而被攻破。
④不完全的或非原子的芯片擦除:许多芯片上CRP可以通过整张芯片的擦除而被无效。但是,一些情况下芯片擦除不会完全清除芯片的内部状态,导致攻击者可以恢复存储在未擦除空间的密码算法密钥。为了避免这一情况,芯片擦除过程应该是不可中断的和原子的,也就是说无效CRP的代码应该在擦除程序的最后执行。
⑤非固定时间代码:在受密码保护的bootloader上的时间泄露,例如Renesas M16C或TI MSP430,允许攻击者逐字节的恢复密码并且获得整片flash内存的读写权限。
⑥默认无防护:如果只有特定的值才启用读保护,那么相比只有特定的值才无效读保护而言,要更容易被攻击,例如LPC1343。
⑦对读保护没有冗余检查:在没有针对故障注入的硬件防护的芯片上,跳过一个检查的成功率往往是很高的,但是如果有冗余检查,就会显著地降低攻击的成功率。
⑧大量的防护等级:这可能会使得开发人员搞不清每一种防护等级对应哪种防护。开发者们使用的厂商提供的IDE往往会隐藏CRP细节,使得用户不清楚他们使用的CRP防护等级。
⑨分离的On-Chip Debug(OCD)和CRP机制:在许多芯片如Renesas V850,78K0R和78K0,或者TI MSP430上,读保护机制和OCD权限是不相关的,使得用户需要通过软件或者保险丝来保护。由于这一模糊性,开发人员可能会注意不到某一种对芯片内存的读取方式,最终使得其它所有防护都无效化。
⑩复杂的bootloader逻辑:每一种bootloader的通讯协议都会增加攻击者可以攻击的面的宽度,导致引入更多的软件风险。例如,特定LPC芯片的USB存储仿真,其中包含一个FAT文件系统,可能会引出更多的问题,反观STM8芯片,一旦CRP被启用,所有bootloader指令都会被禁用。此外,一些Renesas芯片在一个bootloader里支持三种通讯接口,UART,single-wire UART和SPI,这极大地增加了攻击面宽度。
参考文献
[1]Jan Van den Herrewegen, David F. Oswald, Flavio D. Garcia, Qais Temeiza:Fill your Boots: Enhanced Embedded Bootloader Exploits via Fault Injection and Binary Analysis. IACR Trans. Cryptogr. Hardw. Embed. Syst. 2021(1): 56-81 (2021).
