简述:模糊测试是一种重要的软件测试技术,得到了学术界和工业界的广泛关注。近年来,关于模糊测试的研究不断涌现。实现模糊测试的程序称为模糊器(Fuzzer)。面对如此众多的 Fuzzer,如何准确、有效地评估 Fuzzer 的性能成了一项值得关注的难题。本文从变异杀死的角度对 Fuzzer 进行评估。
实验流程:编写脚本,利用变异测试工具重新运行模糊测试的产生的测试输入,从变异测试的角度(变异得分)评估 Fuzzer 的性能,工具:AFL(Fuzzer) + Mull(变异测试工具)
测试对象:所有 Real-world Projects
步骤:(1)利用 AFL 对实验对象进行模糊测试,产生测试输入;(2)利用Mull 复现 AFL 产生的测试输入,记录变异杀死情况;(3)编写脚本对运行结果进行分析,绘制统计图。
实验过程报告:
AFL过程解析
AFL对原程序进行模糊测试产生的用例分为两类:
1.一类会让原程序报错,这类测试用例存于crashes文件夹中。
2.另一类探明了原程序新的执行路径,这类测试用例存于queue文件夹中。
mull过程解析
按照定义好的变异算子,mull 先对源程序变异,得到变异体,利用变异体去衡量AFL产生的测试用例质量,分为三类情况:
1.AFL产生的测试用例会使变异体崩溃(变异体被杀死)
2.AFL产生的测试用例会使变异体输出和原程序不一样的结果(差分测试思路,同样导致变异体被杀死)
3.AFL产生的测试用例会使变异体输出和原程序一样的结果(变异体存活)
衡量指标
变异得分,即为所有被杀死的变异体除以变异体总数。变异得分越高则表明 AFL 效果越好。
注意:由于mull对每个变异体的执行过程高度封装,难以进入到子进程内部去修改运行逻辑,因此我们将变异被杀死的情况1和情况2分别进行实验。
主要工作:
硬件配置
服务器:华为云服务器 2核 CPU 4G 内存 50M 带宽 40G 云存储空间 Ubuntu 20.04 操作系统。
软件环境
操作系统:Ubuntu 20.04
C++项目构建:clang 12
脚本版本:python 3.8
测试源程序
Fuzzing 配置
fuzzer
afl-2.52b
补充库
bison 3.5.1
texinfo 6.7.0
实验过程解析
AFL
AFL的运行流程如下图:
AFL工作流程
①从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage);
②选择一些输入文件,作为初始测试集加入输入队列(queue);
③将队列中的文件按一定的策略进行“突变”;
④如果经过变异文件更新了覆盖范围,则将其保留添加到队列中;
⑤上述过程会一直循环进行,期间触发了crash的文件会被记录下来。
我们在实验过程中大致遵循上述流程,步骤如下:
1.下载
从AFL官网下载AFL。
2. 安装
将压缩包解压完成之后,进入到其目录下打开终端。
make
sudo make install
如果在该目录下,出现了afl-fuzz的可执行文件,说明安装成功。
3. 模糊测试实验–以readelf为例
下载binutils,完成后解压进入其目录。
必须修改编译器环境为afl-gcc和afl-g++,在大部分的源码编译插桩中基本都需要这一步。
CC=~/.../afl-gcc CXX=~/.../afl-g++ ./configure //替换为自己afl的目录
make
>mkdir fuzz_in
>mkdir fuzz_out
>~/../afl-fuzz -i fuzz_in -o fuzz_out binutils/readelf -a @@
AFL结果
输出文件夹fuzz_out内部结构如下:
- crashes:引起原程序崩溃的测试用例;
- queue:能探明原程序新的执行路径的测试用例;
- plot_data:按时间顺序存储了afl执行过程,用于绘图;
- hangs:导致目标超时的独特测试用例;
- fuzzer_stats:afl-fuzz的运行状态。
选择说明:
本实验后续基本使用 queue 中的用例作为变异测试的用例输入。这是有缺陷的,因为queue文件夹中存放的是所有具有独特执行路径的测试用例,一般并不会导致变异杀死,因此变异得分总体较低。但是crashes文件夹中的测试用例过少。权衡之后,我们更多衡量queue文件夹中的测试用例质量。
对AFL用例筛选:
由AFL得到对readelf分支全覆盖的用例,使用脚本筛选出可正确执行的部分用例并记录。
python3 select.py ./readelf
运行select.py脚本,尝试运行所有模糊输入,找出使其不崩溃的模糊输入。
import sys
import subprocess
import os
# select.py
test_executable = sys.argv[1]
path = "./fuzz_out/queue" #文件夹目录
files= os.listdir(path) #得到文件夹下的所有文件名称
s = []
for file in files:
if not file.endswith(".elf"):
os.rename(path+"/"+file,path+"/"+file+".elf")
record = open("./fuzz_out/correct","w+")
recordE = open("./fuzz_out/error","w+")
for file