APP下载

一种针对JavaScript 引擎JIT 编译器的模糊测试方法*

2021-01-26王轶骏

通信技术 2021年1期
关键词:测试工具编译器覆盖率

王 越,孙 亮,王轶骏,薛 质

(1.上海交通大学,上海 200240;2.江苏省南通市公安局,江苏 南通 226001)

0 引言

Web 浏览器安全是用户在网络环境中至关重要的一环,而JavaScript 引擎是浏览器中的重要组件,攻击者可以通过钓鱼网页轻易地让用户触发JavaScript 引擎漏洞。JavaScript 引擎又是内存危险的,触发漏洞后攻击者可以构造特殊的原语来实现内存的任意读写,从而控制被攻击者的设备。JavaScript 引擎漏洞在近几年频繁出现,伴随的利用脚本都仅仅需要远程浏览器访问,范围覆盖Windows、MacOS、iOS、Android 各种操作系统,故JavaScript引擎的安全问题已经是安全研究的重点对象。

近几年来,JavaScript 引擎漏洞中JIT 编译器的漏洞占据了越来越大的比重。JIT 编译器,全称(Just-In-Time)是一种程序语言提高运行效率的方法。如图1 所示,JavaScript 引擎解释执行JavaScript 的流程为:先通过词法和语法分析将JavaScript 脚本转化为AST 树,然后编译为中间语言字节码,接着进行逐步解释执行,当执行的过程出现多次循环或者多次函数调用时,JIT 编译器会将该部分字节码重新编译,通过编译优化提升运行效率,最终转化为符合操作系统的机器码并执行。

图1 解释执行JavaScript 流程

JIT 优化阶段,JIT 会将优化拆分成为多个步骤(如常数折叠、循环不变量提升等),这些步骤即为每一个具体的JIT 优化阶段。JIT 优化时会将需要优化的代码进行分析,当其满足优化阶段的条件时,进行优化。

JIT 编译器漏洞,主要包含的是JIT 优化相关的漏洞,优化漏洞通常在JIT 编译优化过程中利用错误预测或绕过检查而达到利用。其中包含绕过边界检查,如CVE-2015-0817、CVE-2017-2547 和CVE-2018-0769;绕过类型检查,如CVE-2017-11802、CVE-2018-17463 和CVE-2018-4233;以及各类其他类型漏洞。

模糊测试是一种自动化漏洞挖掘技术。尤其在针对浏览器引擎这类复杂的系统时,模糊测试相比代码审计等其他方式有着更高的挖掘漏洞效率。其核心思想是将随机生成的输入重复提供给应用程序,然后在处理输入直至程序退出期间,监视程序是否出现错误情况。模糊测试主要分为两类,基于生成的方法和基于变异的方法。分述如下:

基于生成的模糊测试方法,每个输入文件都是从头开始生成的,通常遵循一组预定义的规则。该规则将是上下文无关并会限定所有输入的集合。在生成的过程中,通过随机选择实现随机生成。

基于变异的模糊测试方法,是从一组已知良好的种子文件开始,然后以随机方式对它们进行变异。可能的变异包含比特和字节翻转、递增和递减整数值、插入预定义的特殊整数和字符串值等。

在模糊测试程序运行后便会不断生成测试样本,让目标程序执行,获取执行结果并统计。衡量模糊测试对目标程序漏洞挖掘的进度,通常使用的是覆盖率指标。覆盖率是通过桩来计算的,桩存在于目标程序的分支跳转和函数调用处,通常都是在编译目标程序时由编译器完成插桩工作[1]。每次执行目标程序后,模糊测试工具可以获得执行过程中抵达的桩信息,计算抵达过的桩数量相对于总数的占比,即为覆盖率指标。

为了能尽快提高覆盖率,提出了基于覆盖率为导向的模糊测试方法,当样本抵达了新的桩,则将其确定为“有趣”的样本保留,在未来继续对其进行变异,如此往复,逐渐遍历系统的所有空间。

1 研究现状

在现今针对JavaScript引擎的模糊测试工具中,有基于规则生成的[2,3],也有基于变异生成的[4,5,6,7],本节会介绍当今主流的几款针对JavaScript 引擎的模糊测试工具[2,6,8],并分析其使用的模糊测试技术。

1.1 CodeAlchemist

这是一款基于语料库的生成模糊测试工具,从JavaScript 种子文件中分解出代码单元片段,对其进行变量重命名、数据流分析、类型分析后并放入代码块池中,然后对代码块池中的片段进行随机选择,组合生成JavaScript 测试样本。CodeAlchemist 在组合代码片段时,会考虑上下文约束,从符合变量类型和数量的代码片段中随机选择。该方法成功提高了模糊测试样本的成功率。

1.2 Fuzzilli

这是一款基于中间语言的模糊测试工具,Fuzzilli 定义了一种新的中间语言FuzzIL 并在其上进行生成和变异,在组合了一系列中间语言后,统一将其提升为JavaScript 测试样本。提升过程时基于上下文和类型系统,测试样本有着较高的JavaScript语义正确性,并且变异过程是基于覆盖率导向的,可以达到较高的覆盖率。

1.3 DIE

这是一款基于类型语法树的变异模糊测试工具,将JavaScript 种子文件转为语法树,然后通过类型分析,产生包含类型标识的语法树(Typed AST)。DIE 基于类型语法树并保留种子文件结构和类型特征进行变异,优质的样本对于DIE 非常重要,故其选择了大量以往的JavaScript 崩溃样本。

1.4 分析

对于现今针对JavaScript 引擎的模糊测试工具而言,测试样本的成功率和覆盖率是重要的衡量指标。对于JavaScript 引擎中的JIT 编译器,测试样本成功率和覆盖率同样是衡量模糊测试性能的重要指标。其中测试样本成功率指成功进行JIT 优化并执行优化代码的样本的比例,而覆盖率指JIT 编译器部分的覆盖率。

当今针对JavaScript 引擎的模糊测试工具对JIT引擎的测试样本成功率不高。原因为:(1)JIT 引擎需要特殊的条件进行触发,并不是所有测试样本都会触发JIT 优化;(2)JIT 代码会出现优化退出的情况,而以一个会产生优化退出的样本作为种子文件时,会产生许多不成功的测试样本;(3)生成过长的样本会影响JIT 的运行时间而导致超时。

当今针对JavaScript 引擎的模糊测试工具对JIT引擎的覆盖率也不高。原因为:(1)需要拥有优质的JIT 种子文件作为变异样本;(2)在模糊测试JIT 优化阶段时较为盲目,无法针对某一JIT 优化阶段进行专一的探索。

2 设计思路

该节会介绍本模糊测试引擎的设计思路,包含生成JIT 种子文件、检测和变异三个部分。

2.1 生成JIT 种子文件

从触发JIT 优化条件而言,需要一个循环被多次执行,这样在循环内部的代码将会被JIT 优化。故设计了一个JIT 种子文件的模板,包含:(1)一个将要被优化的opt 函数内部;(2)触发JIT 优化的循环;(3)外部不被优化的代码。如图2 所示,在修改了JavaScript 引擎的运行参数后,可以将多次调用的数量减少为10~1000 次。

图2 种子文件生成结构

在生成JIT 种子文件时,采用基于中间语言的生成方式。相比基于语法或基本块的生成方式,中间语言可以构造更为泛型和复杂的句法结构。另外在从中间语言提升为JavaScript 的过程中会考虑上下文约束,每一个变量不仅保存类型信息,还有方法、属性、原型链信息,相比基于类型语法树的方法能够生成语义正确性更高的样本。如图3 显示了如果只通过类型语法树进行生成可能导致的语义错误,由于未考虑到原型链,会认为变量a 作为数组类型仍拥有splice 方法。

图3 单一的类型系统可能发生的语义错误

2.2 检测

在检测上,除了崩溃捕获、运行时长和覆盖率信息,还需要检测JIT 优化是否成功执行。JIT 编译后会在代码中插入一些检查来确保JIT 优化的正确执行,而当检查不被通过时,JIT 优化将会被退出,返回到原本的字节码进行逐步解释执行。在这种情况下,该测试样本并没有对JIT 编译器进行有效的模糊测试。

JIT 种子文件在生成后需要被执行一次,统计其运行时长、覆盖率和JIT 执行情况,并以此决定其是否被设置优先级放入变异队列中。如果生成的JIT 种子文件没有触发JIT 优化或退出JIT 优化,则认为对该种子文件的变异无法有效地对JIT 进行模糊测试,将会抛弃该种子文件。当一个JIT 种子文件抵达了新的桩,成功执行了JIT 优化,并且有较短的运行时长后,该种子文件将会被设置一个较高的优先级放入变异队列中。

2.3 变异

变异会选择一个JIT 种子文件,在不改变其中间语言结构的情况下,重新生成中间语言中可替换的变量,产生新的测试样本。使用该方法的原因是触发JIT 优化每个阶段的条件和每一条JIT 优化的JavaScript 语句都有较大的关系。如果如Fuzzilli 在每次变异进行插入、合并等细粒度较大的变异操作,将会大幅修改测试样本结构,从而改变已触发的JIT 优化阶段,导致盲目地对JIT 优化阶段进行模糊测试。而本方法保持了中间语言结构,并在此之上进行的变异能大概率确保触发的测试样本的JIT优化阶段和JIT 种子文件的JIT 优化阶段相同,从而能更深入地探索JIT优化阶段,以达到更高的覆盖率。

种子文件的能量分配规则类似主流的模糊测试工具[9,10]。但对于优化函数内部和外部的能量分配和变异方式却是不同的。每次对一个种子文件进行变异会执行数次基于中间语言的变异操作,每次操作在优化函数内部的概率会大于在外部的概率。在JIT 优化函数内部会采用保留中间语言结构的变异方式,而在优化函数外部则会细粒度大的变异方式。

3 实现

本节会分别叙述本模糊测试工具实现上较为核心的几个部分。

3.1 中间语言

中间语言是一种抽象的操作,本方法定义了如LoadProperty,StoreElement,CallMethod,BeginIf 等中间语言,中间语言需要尽可能涵盖JavaScript 的各种语言特征,如变量声明、赋值语句、一元操作符、二元操作符、函数调用、方法调用、创建对象、控制流等,如图4 所示。

图4 中间语言描述

在生成中间语言后,对其逐条分析其上下文环境,从已有的上下文容器中获取合适的变量,然后再分析该条中间语言生成的返回值变量,将其存入上下文容器中,最终提升为JavaScript 语句。

3.2 上下文容器

系统需要知道每一条中间语言执行后,上下文变量的情况,来确保语义的正确性。通过维持一个容器,在分析中间语言的过程中实时地将上下文变量存储在容器中。容器中存储着JavaScript 内置的构造器对象,也会将全局变量和局部变量存储其中。在逐条分析中间语言时,当遇到if-else、for 循环、函数声明等会产生新作用域的中间语言,会生成一个新的域环境,在新作用域中生成的变量会被存储在新域环境中。当退出该作用域时,会将该域环境以及其中的变量全部删除。容器同时为系统提供获取当前上下文环境下各种类型的变量接口。

3.3 变量

每一个变量都继承于Variable 类,该类包含了变量名、类型、方法、属性、原型链和函数签名。在JavaScript 中的内置类型有许多,本方法将其分类为基础的Undefined、Int、Float、String、Object、Boolean、Function、Constructor。另外针对Object 对象再根据不同的构造器划分,包含Symbol、Map、Set、ArrayBuffer 等。每一种类型都存储了属性名和方法名以及原型链的上一个对象指针。对于内置对象的方法或者自定义的函数,会存储定义方法所需参数和返回值的类型作为函数签名,以便在调用方法时准确地从上下文容器中取出可用的变量。值得注意的是,在调用方法的过程中,需要考虑原型链的规则,优先寻找当前对象的方法是否被定义,然后是构造器的方法是否被定义,再寻找内置的方法。如果没有则去寻找原型链上一个对象的构造器方法直至到达原型链头节点。如图5 所示,定义了JavaScript 中Array 的内置方法,在该对象中,键名对应着方法名,值对应着参数列表的类型,数组最后一项为返回值类型。

图5 系统对JavaScript 内置方法的描述

3.4 程序执行

生成测试样本算法如图6 所示。

图6 生成测试样本算法

模糊测试对于运行效率有着很高的要求,所以系统应尽可能提高运行效率。系统的限速瓶颈在于JavaScript 代码的执行时长,故参考了AFL[9]的思路,使用了Fork Server 来节约进程的创建开销。系统架构如图7 所示,模糊测试系统在选择了一个种子文件后会进行变异并生成JavaScript 测试样本,而在另一处,Fork Server 在解释执行JavaScript 代码前等待信号,接收到信号后fork 进程执行样本,获得结果并统计执行结果(崩溃、运行错误、超时、运行成功)。

图7 系统运行模糊测试流程

4 测试与分析

本模糊测试工具JITFuzz 使用Python3 开发,实现生成种子文件和变异功能,Fork Server 和执行结果统计使用C 语言编写。

4.1 实验指标

采用成功率、超时率、JIT 成功率、JIT 覆盖率作为评价本模糊测试系统的主要指标,描述如下。

成功率:执行成功的测试样本数量/测试样本总数。

超时率:执行超时的测试样本数量/测试样本总数。

JIT 成功率:执行成功并成功触发JIT 优化的测试样本数量/测试样本总数。

JIT 覆盖率:抵达的桩的数量/JIT 编译器的桩的总数。

4.2 实验结果

如图8 所示,JITFuzz 和Fuzzilli 的测试样本成功率和超时率较为接近,优于CodeAlchemist 和DIE的测试样本成功率和超时率。说明本方法采用了基于中间语言的生成和变异算法,并考虑了上下文和变量特性,有效地提升了测试样本的JavaScript 语义正确性,能够生成正确性更高的测试样本。

图8 测试样本成功率和超时率统计

考虑在所有测试样本中,成功触发JIT 优化的比例,统计结果如图9 所示,JITFuzz 相比Code Alchemist、DIE、Fuzzilli 都有更高的JIT 成功率。这说明本方法采用生成JIT 种子文件和基于中间语言结构的变异方法所产生的样本,有更高的成功执行JIT 优化的概率。

此外,JITFuzz 和Fuzzilli 分析比较了模糊测试的JIT 覆盖率情况。通过对JavaScript 引擎中JIT 编译器部分的插桩,统计覆盖率情况如图10 所示。

图9 测试样本JIT 成功率统计

图10 JITFuzz 和Fuzzilli 的JIT 覆盖率统计

相比于Fuzzilli,JITFuzz 抵达的JIT 覆盖率是Fuzzilli 的1.75 倍,并且在相同的样本数量下,JITFuzz 能达到更高的覆盖率,说明单位样本触发覆盖率的数量更高,体现了本方法所采用的变异方法在针对JIT 编译器进行模糊测试时有更好的效果。

5 结语

本文提出了一种针对JavaScript 引擎JIT 编译器进行模糊测试的方法。该方法借鉴了前人的思路,采用了基于覆盖率和中间语言的技术,为能够顺利对JavaScript引擎进行模糊测试打下了良好的基础。本文在此之上针对JIT 编译器提出了新颖的模糊测试方法,首先利用触发JIT 优化的模板生成JIT 种子文件,并针对合适的种子文件进行保持中间语言结构的变异。这样可以使得测试样本有更高的JIT优化概率和对JIT 优化阶段更为专注的探索,有利于覆盖率的提升。实验结果表明,相比于现今较为流行的模糊测试工具,基于本文所实现的模糊测试工具JITFuzz 有更高的JIT 成功率和JIT 覆盖率,表明本方法针对JIT 编译器的模糊测试具有更好的性能优越性,为进一步的漏洞挖掘提供了良好的基础。

猜你喜欢

测试工具编译器覆盖率
民政部等16部门:到2025年村级综合服务设施覆盖率超80%
我国全面实施种业振兴行动 农作物良种覆盖率超过96%
面向理想性能空间的跨架构编译分析方法
运行速度大突破华为《方舟编译器》详解
电信800M与移动联通4G网络测试对比分析
基于移动平台APP测试
手车式真空断路器回路电阻测试电流线接头研究
浅谈响应时间测试分析方法
基于ARM嵌入式平台的x86译码SOC架构设计