先理解一下题目:VoltJockey: Breaching TrustZone by Software-Controlled Voltage Manipulation over Multi-core Frequencies,这个漏洞打破了ARM下的TrustZone(可信执行环境),通过使用软件控制的电压来影响多核上的频率来达到目的,这个软件实际上就是DVFS(dynamic voltage and frequency scaling),后面会讲到。作者利用“骑士”漏洞从TrustZone中获取到AES的key,并且绕过了基于RSA的TrustZone验证机制。
1.背景知识
1.1 TrustZone
TEE(Trusted Execution Environment)可信执行环境,是CPU上的一块区域(SoC 芯片级系统),该区域能为代码和数据提供一个隔离的、安全的执行环境。TrustZone是ARM设计的可信执行环境,相应地,Intel也推出了对应功能,叫SGX(Software Guard eXtensions)
目前,针对TrustZone的攻击主要是利用软件接口存在的漏洞,比如安全监控调用 (SMC),中断请求 (IRQ),快速终端请求 (FIQ),共享内存访问,和可信的特定程序调用。
修正不安全的函数调用能有效阻止这些攻击。
针对硬件,使用边信道、硬件错误注入(VoltJockey属于硬件错误注入攻击)等攻击方法也有成功的案例,如CLKscrew(VoltJockey受此启发),该攻击能被硬件频率锁阻止。
在TrustZone中,一个物理处理器会被虚拟化为两个虚拟处理器(一个用于一般环境,一个用于安全执行环境),并通过时间片切换的方式,分别执行两种环境中的程序。受信任的程序只要能够通过TrustZone的验证机制(基于RSA),就能从一般环境加载进安全执行环境中被执行。
1.2 DVFS(Dynamic Voltage and Frequency Scaling)
能耗是瞬时动态功率随时间变化的积分,可以表示如下:P = αCV2F,其中,P表示能耗,C表示电容,V表示电压,F表示频率,α为常数。
DVFS就是一个用来提高能源效率的东西,它能根据实时的计算负载,动态调整处理器核心的电压和频率。
厂商规定的电压、频率操作性能点将电压和频率关联起来,根据运行需要的频率,产生特定的电压,从而保证处理器正确运行并且能源效率最高。系统管理员可以使用驱动命令手动设置cpu频率,但是不能直接改变cpu的电压。
Krait架构中的电源管理(Krait是用于本文实验的处理器)
通常,芯片级系统(SoC)会整合多种外设,这些设备可能需要不同的电压。SoC如何为这么多外设提供电压呢,如果为所有外设提供一个固定的电压,很明显是不现实的,所以就提出了电源管理集成电路(PMIC)的概念,这个电路集成了多个硬件管理器,从而能够为不同外设提供不同电压。
图:Krait下的电压管理机制
在安卓设备中,有两个重要的驱动能够改变电压:
- 高通支持一个供应商特定的管理驱动,该去东能够控制PMIC的输出并封装电压相关操作;
- 通用DVFS会根据节能和性能两个标准更新处理器核心的频率,这会间接导致电压变化。问题就出现在这里,所有处理器核心共享一个硬件管理器,该管理器提供的某一电压,能在同一时刻为不同核心提供不同的频率。本论文将会利用到这一点。
1.3 基于电压的硬件错误注入
电压是确保电路实现正确功能的关键因素,不恰当的电压会违反时序约束并且使数字电路产生错误输出。本文就是通过控制电压产生硬件错误,从而实现攻击。为了进一步理解攻击的原理,我们需要先了解什么是时序约束以及如何通过电压产生错误输出。
1.3.1 时序约束
数字电路包含多个电子元器件,对于某个电子元器件,给定一个输入数据,需要一个特定的时间来产生稳定的、明确的输出结果。这个时候,就需要适当地满足时间约束,从而保证数字电路中的信息被有效处理。
下面用一个例子解释什么是时序约束:
在这个例子中,电路的开始和结束分别是一个序列电子元件(本例中是flip-flop触发器),中间的逻辑单元将第一个flip-flop触发器的输出结果传输给最后一个flip-flop触发器。
flip-flop(触发器): 能够存储一位二进制数字信号的基本单元电路叫做触发器。
假设两个flip-flop触发器是由时钟脉冲上升沿触发的,表示如上图,对其解释如下:
- Tclk表示同步时钟脉冲的时钟周期,反映了电路的时钟频率(1秒内多少个周期就是频率);
- 最后一个flip-flop触发器的输入信号(Idst)需要在下一次时钟脉冲的上升沿到来之前的Tsetup时间内维持不变,这个Tsetup时间相当于一个缓冲间隔,用来确保时钟脉冲上升沿到来之时,得到的数据是正确的。
- Tsrc表示在接收到时钟脉冲上升沿信号后,第一个序列单元(Esrc)给出稳定输出所需要的时间。
- Ttransfer表示从第一个序列单元(Esrc)输出的数据传输到最后一个序列单元(Edst)所需要的时间,也就是中间逻辑单元的执行时间。
如果要形成稳定的频率,周期(Tclk)必须是一个常数,同时,对于给定的Edst,其Tsetup实固定不变的。为了使得Edst的输出结果(Odst)符合预期,Tsrc、Ttransfer需要满足以下关系:
Tsrc + Ttransfer ⩽ Tclk - Tsetup - Tϵ
其中,Tϵ是非常小的一个常数。等式表达的意思是,在开始Tsetup时间前再预留一点时间,确保所有数据都已经正确传输过来。
1.3.2 通过不恰当的电压产生硬件错误
如果提供的电压比期望值低,Tsrc和Ttransfer将会增加,这将会使得等式 Tsrc + Ttransfer ⩽ Tclk - Tsetup - Tϵ 不成立,从而违反时间约束。
Tsrc和Ttransfer增加后,因为在下一个时钟上升沿到来之时,Idst还没有准备好(还没有从1变为0),所以Odst将不会发生变化,这种情况将会导致硬件错误,从而造成比特位翻转(例如:本来应该输出0,结果输出1),同样的道理,如果施加一个低于期望值的电压,也会导致硬件错误。
2.攻击方法
通过上面内容,已经知道了如何通过电压产生硬件错误,但是,如何在多核CPU上面通过电压使其产生硬件错误呢?
如果一个处理器核心的电压和频率与其它内核相互独立,那么攻击者可以找到受害者的处理器核心,固定其频率并选择一个低于预期的得电压,从而产生硬件错误。但是,现在的大部分包含DVFS的CPU中,多个处理器核心是共享同一个硬件电压管理器,换话说,所有处理器核心是使用同样的电压,如果改变某处理器核心的电压,则其它核心的电压也会同步改变。
一个处理器核心的频率是由底层电路的延迟决定的,高电压导致低的电路延迟,从而产生高频率。DVFS需要管理多个不同的频率,不同频率需要的电压之间存在的差值是产生问题的关键。
2.1 VoltJockey思路
攻击者的进程运行在一个低频率的处理器核心,受害者的进程运行在一个高频率的处理器核心上,攻击者进程提供一个短时间的故障电压,控制好电压的大小,使得这个电压对攻击者进程所在处理器核心没有影响,但是能使受害者进程所在处理器核心产生硬件错误,从而影响受害者进程。
举两个例子:
- 为了攻击被TrustZone保护的AES函数,攻击者可以调用AES并且将错误引入中间状态矩阵,从而窃取AES的加密密钥。
- 为了将不受信任的应用程序加载进TrustZone,攻击者可以改变RSA解密函数的公共模数,从而改变该函数的最终输出,欺骗(绕过)RSA身份验证机制。
下图对"VoltJockey"的思路进行了展示:
- 为能够通过电压干扰程序运行的环境作准备;
- 攻击者程序等待受害者程序被调用;
- 攻击者程序等待目标代码被执行;
- 攻击者程序改变处理器核心的电压,使得受害者程序所在处理器核心产生硬件错误;
- 恢复到之前的正常电压。
从上图可以看到,实际上,就是在受害者程序执行到攻击者想要攻击的代码时,改变处理器核心的电压,使受害者程序所在的处理器核心产生硬件错误,从而使被攻击代码不能正确执行,最后恢复正常电压。(受害者程序错误执行后,需要正确执行后面的代码)
下面是每个步骤的具体细节:
- 准备一个适当的能够发生电压故障的环境,做三件事:(1)将受害者程序运行的处理器核心配置成高频率,其它处理器核心配置成低频率;(2)攻击者程序用一个固定、安全的电压初始化处理器;(3)清楚目标设备的剩余状态,包括Cache布局、分支预测表、中断向量表和状态寄存器等。
- 通常情况下,能够被VoltJockey注入错误的函数在受害者程序中只占很小的一部分,我们并不能确定其具体的执行时间,因此,攻击者程序需要在受害者程序产生错误之前对其中间执行过程进行监控,等待能够用来注入错误的函数被执行。
- 硬件注入攻击的目标是改目标函数的一小部分指令和数据,而且,这部分被影响的代码应该尽可能小。因此,错误注入点应该能被精确控制。到能够产生错误注入之前需要的时间,称为“预延迟”。
- 故障电压的大小和持续时间,是使产生的硬件错误能够被控制的两个因素。找到恰当的电压和持续时间,使得数据按照预期被改变,从而影响原有的程序流程,是非常重要的。
- 攻击的最终目的是获取受害者程序的敏感数据,或者篡改受害者进程的函数,而不是使受害者程序所在内核崩溃,因此,需要错误注入完成后,尽快恢复处理器核心电压为修改之前的正常值,确保受害者程序继续执行。
2.2 面临的挑战
-
并行执行:在VoltJockey中,攻击者程序和受害者程序使并行执行的。
应对:系统库函数提供来了将某个线程和处理器核心绑定的函数,此外,操作系统也允许用户设置某个任务在特定的处理器核心上运行。
-
剩余状态:受害者程序被执行之前的系统剩余状态会影响受害者程序的指令执行时间,反映再三个方面:1)当访问数据时,缓存命中比缓存未命中需要的时间更少;2)分支预测会导致指令执行的时间改变;3)未完成的任务和中断可能强制处理器核心运行受害者程序。
应对:首先,清除缓存中的所有旧数据,然后,通过多次运行受害者程序的方法填充缓存数据,并且设置和受害者程序数据有关的处理器状态寄存器。此外,还关闭了在操作电压过程中针对受害者程序所在处理器核心的IRO和FIQ中断。
-
有效的执行电压:在android系统中,OPPs定义在一个驱动相关的系统文件dtsi中,里面规定了可用的频率相应的安全电压。
应对:VoltJockey需要突破OPPs规定的限制,从而控制电压。作者分析了Nexus 6的DVFS中软件栈的内核代码,发现顶层驱动负责频率表的建立和频率的选择。此外,供应商特定的频率-电压调节驱动会使用顶层软件修改处理器核心的频率和电压,无论想要修改的值是否在频率表中。这使得电压管理机制规定的有效频率和电压轻易被打破。
-
电压阈值:高通公司为krait架构植入了低压差线性稳压器(LDO)模式,该模式可为内核提供稳定且可控的电压(电压阈值),从而避免处理器出现故障。
也就是说如果请求的电压小于阈值,则厂商特定的电压调节器就把处理器电压模式转换为LDO,从而为处理器提供稳定的阈值电压,而不是这个较小的电压(原本请求的电压)。
同理,如果使用高电压阈值,理论上能达到和上面一样的效果,但在实验中发现,如果设置为高电压阈值,系统就会立即重启,因此不可用。
应对:这个阈值定义在“管理器属性描述文件”中,使用电压管理器中的功率指针读取。本实验中,通过修改功率指针函数修改这个电压阈值,这比直接修改“管理器属性描述文件”的影响更小。
-
电压下调限制:为了保证处于高频的内核在调整电压的时候能够正确运行,电压管理器驱动器仅选择 请求电压 和 处于高频内核对应的电压 两者中的最大值作为调整后的值。换句话说,除非降低频率,否则调节器不接受任何向下调调节电压的操作。
应对:在本研究中,通过修改调节器驱动程序来取消此类限制。
-
VoltJockey 内核:我们可以通过更改管理器驱动的方式绕过上面提到的限制,此外,我们在驱动中加入了一个限制机制,确保电压被更改后不会被其他程序再次更改。用户态程序没有权限执行电压管理器暴露出来的与电压有关的操作。但是,本文作者希望攻击程序能够运行在用户态,因为这样会更稳定,并且易执行。
应对:为了达到这个目标,开发了一个用于电压操作的内核模块(VoltJockey kernel)来调用管理器驱动并且向用户程序提供接口。为了保证操作电压时的时序稳定以增加注入精度,内核模块实现了前面提到的5个攻击步骤。
-
监测受害者进程:错误注入进程要在受害者函数被执行之后马上执行,攻击者还需要检测数据是否按被更改为预期值。这要求在实验中,受害者进程的数据能够被攻击者看到。但是,在操作系统中攻击者进程所在不能直接读取受害者进程的数据,因为他们属于两个不同的进程。
应对:在本文中,使用侧信道攻击的方式来预测受害者程序中的数据。常见的侧信道攻击有:prime+probe, flush+reload, evict+reload, and flush+flush,本文使用prime+probe方法。
-
之前提到的延迟和故障电压持续时间都是时间相关的因素,但是,在实际操作中,为了达成恰当的错误注入,通常需要几个时钟周期。操作系统提供的计时功能无法满足对实验对时间精度的要求。
应对:在VoltJockey内核中,使用只有NOP操作的特定循环来计算执行周期,NOP操作只花费一个时钟周期而不会执行任何操作,这能保证计时精度。将时间计算指令放在需要的位置来估计函数从调用到返回消耗的时间。
-
电压操作:发生硬件故障时,异常的电压可能会使系统重启。 此外,某些操作也可能失败。
应对:为了避免重启和提高攻击的效率和可靠性,使用以下方法:
1)使和攻击无关的内核处于“忙”状态,下图展示了在Nexus 6处理器中,无关内核(除攻击者程序和受害者程序所在内核)分别处于不同的状态(关闭、正常、忙),施加不同电压时,为了实现硬件错误注入攻击,该电压需要持续的最短时间。可以看出,无关内核(黑线)关闭时,最小需要持续的时间最长,这是因为此时的处理器电压充足,受害者程序所在内核运行很稳定,要使其发生错误,故障电压则需要持续更长时间。为了不使错误影响到无关内核,实验中,它们使用和攻击者相同的低频率。
2)从上图可以看出,电压越低,攻击越容易(故障电压持续时间更短),但是,突然的大幅度电压下降会导致系统重启和中断。实验发现,从基线电压到故障电压之间的差值越小,效果越好。因此,将处理器基线电压设置成适合所有处理器核心的最低电压。
3)适当的温度对VoltJockey攻击的实现也相当重要,我们发现,较高的温度对注入攻击的成功有帮助。实验中,通过一个计算任务,使CPU得温度在攻击实施之前达到35◦C到40◦C之间。
从频率表中选出频率,并找到对应频率下,使得系统稳定的边界电压。此外,还要获得受害者进程所在处理器核心处于“忙”状态时的安全电压边界。结果如下图:
从图中可以看到,不通频率对应的电压区别是很明显的,处理器核心空闲时候的边界电压比忙的时候要高,特别是在高频率的时候更明显,可能因为忙的时候需要消耗更多能量。
-
攻击程序的安全性:将两个程序放在不同的处理器核心,可以在受害者程序因为硬件错误而崩溃时不至于影响到攻击者程序,但是,故障电压也可能导致攻击者程序所在核心出现故障,特别是攻击者程序核心的频率不低于受害者程序核心时。从上图可以看到,一个处理器核心的最小可接受电压和频率成正相关,可以找到一个频率,使得其对低频率是安全的,但是对高频率时有害的。
应对:幸运的是,不同处理器核心的频率是独立的,攻击之前,我们设置攻击者程序所在核心为低频率,受害者程序所在处理器核心在高频率,然后选择一个电压,使得受害者程序需哦在和信出现硬件故障,但是攻击者程序所在核心是安全的。
-
预备延迟:预备延迟控制着错误注入现场,主要由下面四个因素影响:1)受害者函数代码,2)受害者核心频率,3)攻击者核心频率,4)电压。
应对:对于一个固定函数,特别是加密函数,它的实现通常是公开的,这很方便分析受害者函数的执行过程并提前找到合适的注入点。本研究中,实现了基于S-box的AES加密函数和基于安卓加密库的RSA函数,我们使用不同的受害者核心频率,相同的电压,获得了从AES开始到第七轮的MixColumn操作的NOP循环周期。此外,还计算了从RSA开始到将大尾数公共模转换为小尾数模的指令所需的时间。 下图显示了预备延迟与AES和RSA受害者核心频率之间的关系:
-
故障电压和持续时间组合:虽然硬件错误的根源是电压,但是恶意的操作是由电压和持续时间决定的。我们的目标是将可控的硬件故障注入到第八轮AES的输入状态矩阵和RSA的公共模数中。观察由不同的电压和持续时间组合引起的数据修改可以帮助我们选择适当的攻击参数。
应对:我们固定处理器核心频率,然后施加不同的核心电压,并保持这个电压持续不同时间。接下来,在第八轮AES和RSA的公共模数之前获取状态矩阵中引起的字节错误数。 对于每个电压和持续时间组合,进行五次测试,并将平均修改后的位数绘制为如下所示的变色点/正方形。
3.后记
论文后面介绍了两种利用“骑士”漏洞的具体攻击实施过程,一种是攻击TrustZone的AES加密算法,破解私钥,另一种是攻击基于RSA的TrustZone验证机制,绕过验证并在其中运行任意程序。