0 引 言
ARM TrustZone
[1]是嵌入式环境中主流的安全解决方案,它将所有硬件和软件资源分为两个类别:安全和非安全,非安全对象无法访问安全对象。在采用TrustZone的系统中,有两个执行环境,可信执行环境(Trusted Execution Environments,TEE)和富执行环境(Rich Execution Environment,REE)。TEE又称安全世界,包括所有安全对象,REE又称普通世界,包括所有非安全对象。TEE和REE之间有硬件保障的强隔离可以保护运行于TEE中应用程序的完整性和保密性。
TEE运行时组件主要分为三类:安全监视器、可信操作系统(Trusted Operating System,TOS)和可信应用(Trusted Application,TA)。安全监视器运行于最高权限,实现TEE和REE之间的切换、电源管理以及其他运行时服务。REE的用户可以通过安全监视器调用(Secure Monitor Call,SMC)发起请求,安全监视器根据SMC的ID对请求进行不同的处理。TOS是运行于TEE中的操作系统,可以向REE提供更为丰富的安全服务。TA运行于TOS之上,一般为拥有可验证签名的第三方程序。目前,有多个基于TrustZone的TEE商业产品方案,它们的安全监视器大多是相同的,均采用ARM提供的开源实现,TOS和TA则各有不同,由厂商自定义。
TrustZone要达到的目的是即使攻击者攻破了REE的操作系统,TEE仍然是安全的。但由于TEE实现存在漏洞,目前TrustZone机制并不能完全达到这种理想效果。这主要是由于缺乏鲁棒的TEE实现,现有的各种TEE实现方案均存在安全问题
[2,3],如高通的QSEE
[4]、Trustonic的Kinibi
[5]和三星的KNOX
[6]。利用这些漏洞,攻击者可以成功控制TA并执行敏感操作。此外,攻击者还可以利用SMC,攻陷TEE,实现任意代码执行
[7~9]。
为了更加方便进行漏洞挖掘,人们提出了一种自动化软件测试方法——模糊测试。一些研究试图将这种方法应用于TrustZone
[10~12],但这些工作存在两个缺陷:1) 仅针对TOS进行测试,不同厂商的TOS实现不同,测试结果难以通用;2) 在TEE中添加TA需要安全世界的权限,使得在现实世界中难以利用发现的漏洞。通过研究发现,即使来自不同厂商,安全监视器的实现都是一样的,都基于ARM提供的样本。此外,REE的攻击者可以通过SMC对安全监视器进行调用,而不需要添加TA。因此,针对安全监视器的模糊测试具有更广泛的适用性和实用价值。
目前,对TOS的模糊测试工作通常假设攻击者处于安全世界的用户态。与之不同,本文的威胁模型假设攻击者处于普通世界,并能通过某些漏洞获得普通世界操作系统的管理员权限,从而可以随意构造和发起SMC调用。换言之,攻击者可以获得普通世界的最高权限,构造提交给安全监视器的任意输入。基于此威胁模型,本文提出了一个针对ARM安全监视器的模糊测试方法,测试的对象是安全监视器,而不是特定厂商的TOS实现,因此具有更高的通用性。此外,该方法不需要添加TA,只需要REE操作系统的权限,更加实用。这一方法利用SMC指令对安全监视器进行模糊测试,支持覆盖率引导,并主要实现三个功能:种子生成、用例变异和用例执行。首先,通过静态分析设计种子文件结构和SMC调用约定。接着,从ARM可信固件功能测试套件(Trusted Firmware-A Test,TF-A Test)
[13]中收集SMC信息,并按照特定的文件结构将其组织为种子文件。结合SMC调用约定,对种子执行结构化变异策略,生成新的测试用例。最后,结合模拟器,执行变异后用例,并在执行过程中监测代码覆盖率的变化。
根据提出的方法,本文实现了一个测试框架:SMC-Fuzzer,并以ARM可信固件(Trusted Firmware-A,TF-A)为测试对象进行了实验评估。实验结果证明,SMC-Fuzzer可以有效地收集涵盖测试套件中所有SMC的信息,并且在经过一段时间的测试后,可以发现总共21个崩溃,包括5个唯一性崩溃。
1 相关工作
模糊测试是一种常见的软件测试方法,它通过随机生成输入数据,模拟异常场景检测程序的漏洞。在早期的模糊测试工作中,研究人员主要关注普通程序的安全问题,如SAGE
[14]、混合模糊测试
[15]、AFL(American Fuzzy Lop)
[16]、QuickFuzz
[17]等,后来,模糊测试逐渐扩展到一些特殊目标。例如,Syzkaller
[18]可以对系统内核进行覆盖率引导的白盒模糊测试。TriforceAFL
[19]使用AFL对内核进行模糊测试,通过模拟器收集代码覆盖率,而文献[
20]则基于硬件反馈收集代码覆盖率。文献[
21,
22]改进了变异策略,对内核进行深入探索。文献[
23,
24]则实现了对虚拟机监视器的模糊测试,并进行了性能优化。文献[
25~
27]则专注于物联网固件的模糊测试。Peach
[28]实现了基于语法感知的黑盒模糊测试,AFLSmart
[29]结合Peach和AFL,实现了语法感知的灰盒模糊测试。
文献[
30,
31]对TEE的安全问题做了梳理性的研究,证明了安全审计工具的必要性。而近年来,部分研究者也开始将模糊测试应用于TEE的安全测试工作中。相对于静态分析的测试方法
[32~35],模糊测试更有利于TEE的漏洞挖掘。开源工具
[10]通过修改OP-TEE
[36]系统内核,对其系统调用进行了模糊测试,文献[
11]则对其进行了改进,实现了无需修改内核进行模糊测试的效果,文献[
12]实现了一个系统,可以对OP-TEE执行模糊测试。此外,文献[
37]使用Rust语言解决了可信应用的内存破坏漏洞,文献[
38]则构建了一个模拟器,可以用于对可信应用进行动态分析。
安全监视器是可信固件中唯一的运行时固件,因此,本文针对安全监视器进行安全测试,能够更有效地评估TEE解决方案的安全基础,填补现有对TrustZone安全性评估工作的空白。
安全监视器也是一种特殊的嵌入式固件,可采用模拟器作为测试环境,与通用物联网设备固件测试方法类似。虽然SMC接口与系统内核的系统调用接口格式相同,但ARM TrustZone架构存在较大区别,缺乏测试种子,故此,本文测试方法与内核模糊测试有所不同。此外,本文变异策略在AFLSmart的基础上进行改进,使其可对SMC更好地进行结构化变异。
2 背景知识
可信平台模块(Trusted Platform Module,TPM)是可信计算平台的核心硬件,为整个计算系统提供可信计算基。以TPM作为基础,通过逐级度量的方式,可信计算在系统引导的过程中建立信任链,确保了启动的系统和应用的完整性。
TrustZone是由ARM提供的新一代可信计算机制,旨在通过硬件扩展和特定的软件架构提高系统级别的安全性。其核心在于CPU的虚拟化,支持TrustZone的ARM内核有两种运行状态:安全状态和非安全状态。借助硬件扩展,系统内的所有硬件和软件资源分为两类。只有当处理器处于安全状态时,才能访问安全类的代码和资源。这种机制使得TEE和REE之间有强大隔离,保证了安全性。ARMv8的TrustZone架构中有4个异常级别:EL0、EL1、EL2和EL3,其中EL0最低,EL3最高。0到2级又分为正常和安全两个状态。安全监视器是唯一运行在EL3的组件。
图1显示了ARMv8下TrustZone的软件架构
[39]。通过硬件扩展和特定的软件架构,TrustZone实现了安全世界和普通世界之间的严格隔离,从而为整个系统的安全性提供了保障。
在ARMv8中,系统调用的概念扩展至四个异常级别。低权限级别使用系统调用请求高权限级别提供的服务。SMC指令由运行于EL1或EL2级别的软件发起,用于向运行于EL3级别的安全监视器组件请求服务。SMC指令最多可以携带8个参数,这些参数通过w0 ~ w7寄存器传递。其中,w0包含SMC ID,用于指定被调用的SMC服务的ID,安全监视器会根据ID路由到相应的处理函数。
ARM基于TrustZone规范构建了一个完整架构,用于实现从引导程序到REE系统的可信引导。根据引导流程,ARM将涉及到的组件分为:BL1、BL2、BL31、BL32和BL33,完整引导流程如
图2所示。BL1是整个系统的可信根,集成在芯片中。在引导期间,BL1首先验证BL2的签名,并启动BL2;接着,BL2验证剩余三个组件的签名,将BL31组件的入口点发送给BL1,跳转到BL1执行;BL1跳转到BL31入口点,执行BL31;BL31是安全监视器,初始化运行时服务,执行BL32的初始化函数;BL32是可信操作系统组件,一旦初始化完成,通过SMC返回到BL31;BL31执行BL33的初始化函数,BL33引导启动REE操作系统,最终进入用户环境。引导结束后,BL1和BL2将从内存中清除,BL31组件则以最高权限级别运行,负责两个世界之间的状态切换,以及异常的路由分配。
3 方法设计
本文提出的SMC-Fuzzer可以用于测试ARM可信固件BL31组件。基于TF-A的代码和文档,SMC-Fuzzer设计种子文件格式,以生成初始种子库。同时,SMC-Fuzzer使用模拟器的动态插桩技术,无需修改源码,实现了对待测组件代码覆盖率的监测。
图3显示了SMC-Fuzzer的整体架构。
SMC-Fuzzer的输入包括程序和文档。首先,SMC-Fuzzer使用ARM安全监视器源码、TrustZone白皮书和SMC调用约定作为输入,经过静态分析,得到SMC调用规范和种子文件格式。接着,SMC-Fuzzer在ARM的仿真器FVP
[40]上运行TF-A Test,并以TF-A Test发起的SMC作为输入,结合静态分析的结果,实现种子生成,得到初始种子库。结合SMC调用规范,SMC-Fuzzer不断从种子库中读取用例,并进行结构化变异,以生成当前的变异后测试用例。然后,SMC-Fuzzer在开源模拟器QEMU
[41]中执行变异后的用例,并在执行结束之后收集代码覆盖率,以引导后续变异操作。在这个过程中,如果有任何测试用例导致运行于QEMU中的待测组件安全监视器崩溃,则SMC-Fuzzer会保留这个用例。最终,测试结束后,SMC-Fuzzer输出所有导致安全监视器崩溃的用例。
3.1 种子生成
为了能够对SMC调用进行变异,本文将SMC调用组织成原始二进制文件,通过翻转位或字节等方式改变数据。每个种子文件包含两个主要部分,命令部分和数据部分。命令部分由多个SMC组成,每个SMC都包括SMC ID、一个记录所有参数类型的字段以及最多7个后续参数的值。数据部分则记录了所有涉及的数据,即数据类型的参数缓冲区中存储的内容。安全监视器会根据SMC的ID将SMC调用路由到相应的处理函数。SMC调用的参数具有多种类型,不同类型之间需要进行区别处理,多个SMC的参数之间也可能存在相互调用的关系。例如,后一个SMC调用的参数可能包含一个数据缓冲区,而这个缓冲区是由上一个SMC调用生成的结果。这使得参数的编码是最具挑战性的部分,为此,设计以下参数类型:
• 值。值类型的参数即一个整数,包括8位、16位、32位和64位整数。该类型的参数出现的频率最高,处理也最为简单,只需将参数的值直接写入种子文件中对应位置即可,无需进行其他操作。
• 缓冲区。缓冲区类型的参数值包含两个字段:缓冲区编号和缓冲区长度。根据用途的不同,缓冲区可以进一步分为两类:分配和引用。这两类缓冲区都会在种子文件中存储其缓冲区编号和长度。如果某个缓冲区的地址首次出现,则其参数类型为分配。对于分配类型的缓冲区,需要记录其首地址和长度,并分配一个对应的编号。之后如果遇到缓冲区参数,首先使用该参数的首地址和长度查找是否之前已经记录了该缓冲区,若有则其参数类型为引用。
• 数据。数据类型的参数值由两个字段组成:偏移量和数据的长度。与缓冲区类型相似,该类型的参数值同样为一个缓冲区。但与缓冲区类型不同的是,该类型的数据缓冲区不被其他SMC引用。因此,不记录该类型缓冲区的首地址和长度,而是将其缓冲区中存储的数据复制到种子文件的数据部分中,并在种子文件参数对应位置存储相对于数据部分起始位置的偏移和长度。
ARM可信固件测试套件是一个旨在测试TF-A功能的套件,主要测试SMC的标准实现。该套件在普通世界中测试TF-A功能,可提供包含足够数量的SMC的合法参数信息。一些SMC依赖于上下文,需要按照特定的顺序调用。幸运的是,该套件的每个测试用例本身就包含这样的时序信息,因此可以使用这些测试用例中包含的SMC作为种子文件,以满足SMC的时序要求。
种子生成需要根据参数类型进行不同的处理,因此所有合法的SMC信息,包括SMC ID、名称、参数数量和每个参数的类型,都是手动收集的。SMC-Fuzzer根据SMC ID获取参数数量和类型,并结合上下文信息进一步确定参数类型,对SMC参数值进行处理并存储在种子文件中。例如,如果缓冲区类型的参数值在测试用例中首次出现,则该参数类型被进一步确定为分配。否则,它将是引用。测试用例执行后,所有记录的信息都被写入主机环境,生成种子文件,然后清除所有记录的信息。
TF-A Test中包括许多电源管理测试,例如关闭电源和重新启动操作。在一些测试中,关机或重启操作会导致最后一部分SMC信息的丢失。为了解决这个问题,主机环境中使用一个文件记录一个整数,该整数用于创建种子文件。相同的整数值会产生相同的临时文件名,从而打开相同的种子文件。在普通测试用例被执行后,整数值将被增加,而当关机或重启操作后,当前的整数值将被写回主机文件,当前记录的SMC调用信息将被写入种子文件。重启后,整数值将被读取,以追加的形式写入相同的种子文件中,直到当前的测试用例被执行,从而实现了对重启操作的支持。
最终,SMC-Fuzzer将在收集的SMC信息之间插入一个ID为0xFFFFFFFF的SMC,以此作为命令部分的结束标志。在解析测试用例时,该ID将被用作命令部分的结束标志,从而确定SMC和数据部分之间的边界。
TF-A Test在虚拟机中运行,需要将收集的SMC信息导出到主机环境。半托管接口可以通过特定的汇编指令和传入不同的ID进行调用。通过在SMC-Fuzzer中依次调用这些接口,SMC信息可以作为二进制比特流存储在主机文件中,并作为后续模糊测试工作的初始种子。
3.2 用例变异
本文首先使用了AFLSmart来增强文件块级变异的支持,并增加了定义SMC种子文件结构的描述文件。每个种子文件包含了数量不等的SMC,每个SMC的头部必须包含一个4字节的调用ID和一个4字节的参数类型,后面是最多7个参数。每个参数都有多种类型,使用 Choice 元素定义。在SMC部分之后,可能会有一个长度可变的数据部分,使用 Blob 元素定义。
在AFL的确定性突变阶段,使用比特翻转、有趣值替换和算术运算这三种突变策略进行突变。这些策略被提取到一个单一的突变方法中,称为fuzz_one_chunk。本文首先根据种子文件的结构分析输入,将其分割成多个SMC,并对每个SMC的每个参数分别调用fuzz_one_chunk进行变异。对于SMC中的函数ID,进行合法范围内的变异,而对于SMC的参数类型字段不进行变异,因为代理在该字段的值改变后无法分析文件。其余参数根据其类型分别进行处理:
对于值类型的参数,可直接变异,进行各种算术运算,如加减某个整数值,或替换为边界值等。
与缓冲区有关的参数被记录在种子中,有两个值:缓冲区ID和长度。对这两个值在一定范围内随机变异,范围包括已经记录的所有缓冲区ID和长度。
数据类型参数与缓冲区类型类似,在文件中记录了两个值:偏移量和长度。使用该偏移量和长度可以访问种子文件中存储的数据的内容。在进行突变时,有较大概率突变数据的内容,较小概率直接突变偏移量和长度值。
有些值类型的参数有合法值约束,如:必须是0,不超过特定值,不低于特定值等。为了考虑这些约束条件,启发式信息被添加到了这类参数中。在这些参数突变前,SMC-Fuzzer会检查它们是否存在约束条件,如果有,则在约束条件下进行突变的概率会增加。
havoc阶段是AFL变异的其中一个阶段,意为“大破坏”。在havoc阶段,AFL不断对输入进行随机的突变操作。每个突变操作可以是确定性阶段的任意操作,也可以是对输入的随机块进行删除、添加或拼接操作。本文的SMC-Fuzzer在havoc阶段加入了对结构化变异操作的支持。具体而言,SMC-Fuzzer在执行任意突变操作之前,首先选择一个随机的SMC,然后在该SMC的范围内随机选择一个参数。在执行突变操作时,SMC-Fuzzer会以一定的概率选择执行结构化突变操作或原始突变操作。其中,结构化突变操作指对所选参数执行与随机突变等效的变异操作。
由于原始的随机突变操作可能会造成输入的长度和结构发生变化,从而导致无法按照种子文件的结构进行解析。因此,在每次进行大规模破坏操作之前,会将输入内容复制到一个临时缓冲区,并在原始缓冲区上执行原始的突变操作,同时在临时缓冲区上执行结构化突变操作,两者相互独立,互不干扰。完成一次大规模破坏操作后,这两个缓冲区都将被送入被测程序中,作为测试用例进行执行。
算法1描述了在AFLSmart
[29]基础上修改后的种子变异策略,其中所有增加的结构化变异操作都是以概率方式执行的。这是为了确保变异后的用例尽可能符合结构要求,同时保留模糊测试探索边界值的能力,这些边界值是固有的唯一值,可能导致程序出现异常状态。
3.3 用例执行
SMC-Fuzzer在REE添加代理应用和SMC驱动实现对变异后用例的解析和执行。经过变异后,测试用例成为代理输入,代理将解析这些用例并执行相应的SMC。首先,代理会获得一个SMC的ID,然后确定参数的数量。对于每个参数,代理会根据测试用例中参数类型的值确定参数类型,进而处理参数,并恢复SMC调用的原始参数。当代理遇到一个ID为0xFFFFFFFF的SMC调用时,它将结束测试用例的执行。
值类型的参数值就存储在文件中,直接读出并作为参数的实际值使用。
对于分配类型的缓冲区,代理将分配一个缓冲区,并将获取到的缓冲区基址作为参数值传递,同时将其编号设置为种子文件中记录的编号。对于引用类型的缓冲区,代理将根据编号查找相应的缓冲区,并将基址作为参数实际值使用。
对于数据类型的参数,代理将分配一个缓冲区,并将种子数据部分存储的数据内容复制到分配的缓冲区中,然后将缓冲区基址作为参数实际值使用。
在AArch64架构中,只有EL1及以上的特权级别可以调用SMC。因此,在 REE 系统的内核层中增加了一个 SMC 调用的驱动程序。该驱动程序的写函数接收代理解析出的 SMC 参数,按顺序将参数存储到寄存器中,然后执行汇编指令 smc #0 调用SMC。
类似于TriforceAFL
[19],SMC-Fuzzer同样对模拟器QEMU进行修改,添加了4个超调用。通过这些调用,代理程序能够在模拟器中执行读取测试用例、设置代码段追踪范围等操作。这4个超调用分别是:StartForkServer、StartWork、GetWork和DoneWork。代理在启动后首先调用StartForkServer以执行添加的Forkserver代码并初始化Forkserver和共享内存。然后,代理通过调用StartWork设置要跟踪的地址范围,即BL31的代码段。接着,代理可以通过GetWork超调用获取测试用例。当代理解析并执行完测试用例后,发起DoneWork超调用通知模拟器完成测试用例的执行。
基于QEMU模拟器,SMC-Fuzzer实现了对安全监视器的动态插桩。QEMU将运行在虚拟环境中的程序指令翻译为主机指令,实现对不同架构环境的模拟。SMC-Fuzzer对QMEU进行扩展,在QEMU翻译指令时,判断指令是否在要追踪的代码段范围之内。如果在,SMC-Fuzzer按照AFL的方式计算其代码路径的哈希值,并更新当前记录的代码覆盖信息,从而实现对覆盖率信息的收集。
4 实验评估
本文在TF-A上对SMC-Fuzzer进行评估。首先,针对TF-A的BL31组件进行测试,以发现的崩溃和代码覆盖率两个关键指标评估SMC-Fuzzer的有效性。其次,为了进一步评估所提出的结构化变异策略的有效性,本文从测试速度、代码覆盖率和发现路径数三个方面分析了应用结构化变异策略前后的测试结果。
由于目前缺乏对ARM安全监视器组件模糊测试的工具,本文实验缺乏对比对象,更多从代码覆盖率等指标对框架自身有效性进行评估。此外,本文的测试框架针对ARM安全监视器实现,难以用于普通的应用层程序。
4.1 实验环境搭建
本文在虚拟机上进行实验。底层主机使用了Intel(R) Core(TM) i5-10400F CPU @ 2.90 GHz和16 GB RAM,虚拟机被分配了4个内核和4 GB的RAM,虚拟机上运行Ubuntu 20.04 LTS操作系统。实验是针对QEMU 6.1.0和TF-A 2.4.0进行的,这两个版本相比于最近的相关工作而言足够先进。由于三星、高通等厂商一般是实现自己的TEE系统,而安全监视器组件基本与TF-A的BL31完全一致,因此测试BL31就足够通用
[10~12]。
4.2 ARM的BL31组件测试分析
为验证SMC-Fuzzer的功能是否正常,本文对TF-A的BL31组件进行了模糊测试。然而,由于TF-A在QEMU平台下的电源管理没有完全实现,会导致电源重启、休眠操作直接触发断言,这可能会被SMC-Fuzzer误认为是崩溃。因此,在代理中,与这些操作相关的SMC被直接跳过。经过一段时间的测试后,SMC-Fuzzer在TF-A的BL31组件中发现了21个崩溃,其中5个是唯一性崩溃,均为断言异常。
崩溃复现。为了复现崩溃,对于每一个崩溃,SMC-Fuzzer都保存了其输入样例。本文实现一个测试程序,只需提供单个测试用例,而不进行变异操作。测试程序可与QEMU进行交互,以确保StartForkserver和GetWork等超调用的正常运行。以崩溃用例作为测试用例,运行测试程序,可以在QEMU虚拟环境中成功复现崩溃。在测试环境中,成功复现了所有崩溃,此外,在QEMU 5.2.0和TF-A 2.2.0的环境中,所有崩溃同样可以成功复现。
代码覆盖率。为了评估SMC-Fuzzer执行SMC处理函数的能力,本文收集了代码覆盖率。BL31提供多种运行时服务,主要包括:标准服务、架构服务、ARM芯片服务、电源管理接口(Power State Coordination Interface,PSCI)和软件委托异常接口(Software Delegated Exception Interface, SDEI)。
表1列出了这些服务的代码覆盖率结果,其中BL31项代表整个安全监视器的代码段的覆盖率。
表1中函数命中、块命中和指令命中均以分数的形式表示,分别代表SMC-Fuzzer执行的函数数量、基本块数量和指令数量与总数量的比值。可以看出,SMC-Fuzzer对整个BL31组件的代码覆盖率并不高。这主要是因为BL31不止包括SMC处理函数,还包括很多初始化函数。这些初始化函数在运行时不会被执行到,并非需要关注的对象。因此,为了让测试结果更加直观,在排除了初始化函数之后,
表2单独统计了BL31中最主要的两个服务PSCI和SDEI的覆盖率。此外,PSCI的电源重启、休眠等操作会直接跳过,SMC-Fuzzer并不会探索这部分代码。为此,
表2还统计了除去电源重启、休眠等函数之后,PSCI服务的代码覆盖率,记为PSCI′。根据
表1中数据可知,SMC-Fuzzer对整个安全监视器可以达到23.82%的代码覆盖;对于架构服务,最高可达83.78%的代码覆盖,三个函数全部命中,基本块仅有一个未命中,但从指令数可以看出该服务代码量较小;由
表2知,PSCI的运行时服务处理函数代码覆盖率接近40%,且覆盖的指令数为单项服务中最多的;PSCI′和SEDI的代码覆盖率都在60%以上。因此,SMC-Fuzzer可以较为深入地探索安全监视器运行时服务处理函数的代码空间。
崩溃分析。本文分析了由包含SMC接口sdei_event_signal的测试用例所触发的崩溃。当安全监视器处理调用时,会触发一个异常,由中断控制器GIC处理。在GIC对该异常的处理函数中,会使用断言函数来确定触发异常的目标处理器的掩码不应该为0。处理器的掩码数组应在中断控制器被初始化时被填充,这一部分操作是平台特定的代码。由于TF-A的SDEI服务本身不支持QEMU平台,本文对其进行了移植。虽然在平台的控制器初始化函数中调用了掩码填充函数,但仍然很难确定崩溃是由TF-A代码本身还是由移植操作所引起的。
4.3 结构化变异策略分析
图4展示了使用AFL的变异策略和改进后的变异策略(SMC-Fuzzer)测试速度的对比。SMC-Fuzzer的速度略低于AFL,这是因为对于每个测试用例,它需要将其解析为多个SMC,并在此基础上进行结构化变异。这种结构化信息的引入对模糊测试的效率造成了一定影响。
图5比较了AFL策略和SMC-Fuzzer代码覆盖率随时间变化的情况,结果显示SMC-Fuzzer取得的最大代码覆盖率高于AFL。因此,改进后的变异策略提高了代码覆盖率。虽然改进带来了一些性能开销,但影响不大,在可承受的范围之内。
本文注意到
图5中SMC-Fuzzer曲线有一个异常的升高。原因是相比AFL,SMC-Fuzzer在同一个种子上执行了更多的突变操作。而种子文件库中的种子涵盖多个服务,因此SMC-Fuzzer对同一服务的多个种子进行突变的时间要比AFL长。在某项服务的所有种子都被突变后,SMC-Fuzzer转到下一项服务,其覆盖率在很短的时间内就会有大幅度提高。而AFL在初始时间段内很快就执行到了这部分种子,但在数个小时之后,SMC-Fuzzer才开始转到另一类服务的种子中,覆盖率有了明显提高,因此,也就出现了
图5中的曲线的突然升高。
图6展示了24小时内AFL策略和SMC-Fuzzer发现的代码路径数量随时间的变化情况,在测试前期两者区别不大,相对于SMC-Fuzzer来说,AFL曲线更早达到峰值,但是峰值比SMC-Fuzzer曲线小。这表明,改进的结构化变异策略可以更有效地探索待测组件的代码路径。
5 结 语
本文提出了一种支持覆盖率指导的ARM安全监视器的模糊测试方法,该方法具有以下优点:可对具有最高权限的对象进行测试,可在不同供应商之间通用,并且没有难以满足的权限要求。基于该方法开发了一个测试框架,其中包括种子生成、输入变异和代码覆盖率收集等关键步骤。利用该框架,本文测试了ARM的安全监控组件。实验评估结果表明,本文方法可以探索安全监视器的运行时服务的代码,发现了5个可复现的唯一性崩溃。
后续可以从以下两个方面对算法进行优化:一方面,提高漏洞挖掘能力,例如改进变异策略,结合符号执行等措施,更深入地探索安全监视器组件的代码空间,以发现真实世界可利用的漏洞;另一方面,提高执行效率,例如采用预翻译策略,在QEMU初始化时将待测组件的代码段基本块全部翻译,以减少每次QEMU派生子进程的重复翻译。
湖北省重点研发计划项目(2022BAD079)
国家自然科学基金(42071431)
国家自然科学基金(61876134)
国家重点研发计划项目(2020YFB1805400)
国家重点研发计划项目(2021YFB3100700)