FuzzIL:Coverage Guided Fuzzing for JavaScript Engines

FuzzIL:Coverage Guided Fuzzing for JavaScript Engines

Abstract

工作目标是引导测试工具来测试脚本解释器,这需要在代码上定义在语义上有效的突变。然而,与现有的突变语法方式不同(如代码的文本形式或语法树),突变是在新设计的中间语言(IL)上进行的。基于其形式,其可以轻松执行控制流和数据流突变。这反映了,程序的句法属性无关紧要。此外,该系统可以以高概率生成语义上有效的样本。这避免了将生成的代码包装到try-catch结构中。

Analysis

总的来说是为了开发一款工具,该工具还是基于覆盖率的。

首先,语法上无效的程序一般无法通过解析阶段,所以生成的代码需要在语法上是合法的。

其次,鉴于基于覆盖率进行指导模糊测试,我们需要定义合理的突变,以在突变时能够保留样本的大多数interesting features,以突变出更多的interesting cases。因此有必要定义能够改变程序控制流和数据流的突变,因为这是组件最终处理的内容。

程序是从上到下顺序执行的,同时可以通过一些控制语句来改变执行的路线,受控制语句影响下,程序最终的执行路线就是控制流。

js 里面的控制语句有 if、for、while、try-catch 等,它们都会改变程序的走向。

程序是操作数据的,随着程序的运行,也就是控制流的前进而改变的数据叫做数据流。

很明显,数据流是依赖控制流的,程序分析里面的数据流分析也是要先做控制流分析。

比如这样一段代码:

1
2
3
4
5
6
7
8
const a = 1;
let b;

if (a === 1) {
b = '1111';
} else {
b = '2222';
}

因为 a 为 1,所以会执行到 b = '1111';,这就是控制流,也就是程序最终执行的代码,可以用来分析程序的走向,做一些死代码删除之类的优化。

而随着控制流的执行,b 会被赋值为 2222,这就是数据流,也就是值的变化的过程,可以用来分析某个语句的变量的值。

最后,还有处理语义上无效的程序的临时解决方案,我们知道使用try-catch语句会导致漏报一些漏洞,我们的系统需要产生较高比例的语义有效程序,以避免try-catch结构的使用。但是,由于一些漏洞是通过内部异常而发现的,所以完全的语义正确性也是不可取的。

try-catch语句

try/catch/finally语句是JS中的异常处理机制。try子句用于定义要处理其中异常的代码块,catch子句是在try块中的语句发生异常后会被调用的语句。(finally块中是清理代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
try{
//正常情况下,这里的代码会从头到尾执行,
//不会出现问题。但有时候也可能抛出异常:
//直接通过throw语句抛出,或者由于调用
//了一个抛出异常的方法而抛出
}
catch(e){
//当且仅当try块抛出异常时,才会执行这个
//块中的语句。这里的语句可以使局部变量e
//引用被抛出的Error对象。这个块可以以
//某种方式来处理异常,也可以什么也不做
//以忽略异常,还可以通过throw重新抛出异常
}

一个具体的例子

1
2
3
4
5
6
7
8
9
10
11
try{
//请用户输入一个数值
let n = Number(prompt("Please enter a positive integer",""));
//假设输入有效,继续计算该数值的阶乘
let f = factorial(n);
//显示结果
alart(n + "!=" + f);
}
catch(ex){ //如果用户的输入无效,则会跳转到这里
alart(ex); //告诉用户发生了什么错误
}

总到来说,我们有三条需求:

  1. 产生语法上有效的样本
  2. 定义影响程序控制流和数据流的突变类型
  3. 产生高比例的语义有效样本

Coverage Guide JavaScript Fuzzing

Concept

在更接近引擎内部表示的“字节码”级别上进行突变,而不是突变AST或者文本代码段等表示的句法结构。本质上,字节码级别类似于控制和数据流图,这是大多数攻击最终所需要的到达的级别,而语法信息(如AST)则在解析过程中很大程度上会被丢弃。

1

因此我们定义了一种名为FuzzIL的中间语言,这样我们可以定义一些在AST层面上不容易实现的突变策略。

为了满足第三条需求,我们首先注意到,由于每个突变执行的变化相对较小,所以改变语义有效性的概率也就较小。此外,所有突变都必须遵守IL的一套语义正确性的规则,在变量使用前要先进行定义等操作。最后可以将变异出的语义上无效的程序百分比维持在一个可以接受的程度上。

FuzzIL

易于突变,同时允许直接转换为JS代码

FuzzIL程序由指令列表组成,每个指令列表又由一个操作以及输入和输出变量列表组成

1
2
3
4
5
6
7
8
9
10
11
12
v0 <- LoadInt '0'							//const v0 = 0;
v1 <- LoadInt '10' //const v1 = 10;
v2 <- LoadInt '1' //const v2 = 1;
v3 <- Phi v0 //let v3 = v0;
BeginFor v0, '<', v1, '+', v2 -> v4 //for(let v4 = v0; v4 < v1; v4 = v4 + v2){
v6 <- BinaryOperation v3, '+', v4 // const v6 = v3 + v4;
Copy v3, v6 // v3 = v6;
EndFor //}
v7 <- LoadString 'Result: ' //const v7 = "Result: ";
v8 <- BinaryOperation v7, '+', v3 //const v8 = v7 + v3;
v9 <- LoadGlobal 'console' //const v9 = console;
v10 <- CallMethod v9, 'log', [v8] //const v10 = v9.log(v8);

let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

image-20220128195045584

JavaScript 原生中默认是没有 Console 对象,这是宿主对象(也就是浏览器)提供的内置对象。用于访问调试控制台, 在不同的浏览器里效果可能不同。

显示了由12个指令组成的程序实例,计算数字从0到9的总和并输出,变量通过整数识别,并且需要在每个单独的程序中从0开始依次连续编号

Mutations

突变的目的是将已有的程序转化为不同的程序,但要保存其有趣的特征

  • 输入突变器:改变输入的参数变量
  • 操作突变器:改变操作参数
  • 插入突变器:将新生成的代码插入到程序
  • 组合突变器:将现有的两个程序组合到一起
  • 拼接突变器:将现有程序的一部分插入另一个程序

Input Mutator

用于突变数据流,在FuzzIL中,指令的所有输入都是变量,所有突变器需要在原始程序中选择一个或者多个指令,并将其输入替换为不同的随机变量来运行

1
2
3
4
5
6
7
# Before Mutation
...
v16 <- CallFunction v1, v6, v9, v3

# After Mutation
...
v16 <- CallFunction v1, v12, v9, v3

Operation Mutator

用于突变操作参数,例如LoadString操作使用的常量,以及比较中使用的比较器,属性操作和方法调用中使用的属性或方法名称,以及二进制或一元操作中的实际操作

Insertion Mutator

本质上是一个小型代码生成组件,使用一系列预定义的代码生成器(一个将FuzzIL代码发送到突变程序中的函数)来进行工作

Combine Mutator

比较简单,将一个程序插入到另一个当中

Splice Mutator

插入一组被称为切片的指令,切片的关键属性是其中任何指令使用的每个变量也由切片中的指令定义

Refinement

在将新发现的样本加入语料库或保存触发crash的样本前,先进行精细化操作

  1. 检查样本的deterministic behavior
  2. minimization
  3. normalization

Determinism

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2021-2024 Kery
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信