前言
AFL(American Fuzzy Lop)是由安全研究员Michał Zalewski 开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具,AFL主要用于C/C++程序的测试。
AFL 通过记录输入样本的代码覆盖率,不断对输入进行变异,从而达到更高的代码覆盖率,以增加发现漏洞的概率。AFL 采用新型的编译时插桩和遗传算法自动发现新的测试用例,这些用例会触发目标二进制文件中的新内部状态。这大大改善了模糊测试的代码覆盖范围。
一、遗传算法(genetic algorithm)
遗传算法是进化算法的一种。 进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择以及杂交等等。该算法通过编码将要求解的问题表示成遗传空间的染色体或者个体,将问题的求解过程转换成类似生物进化中的染色体基因的交叉、变异等过程。
遗传算法借鉴适者生存理论,拥有一个适应度函数也叫评价函数,是用来判断群体中的个体的优劣程度的指标,它是根据所求问题的目标函数来进行评估的。
遗传算法中初始群体中的个体是随机产生的。
每次迭代,从群体中选择优胜的个体,淘汰劣质个体,把优化的个体(或解)直接遗传到下一代或通过配对交叉产生新的个体再遗传到下一代。遗传时会出现交叉和变异。交叉是指把两个父代个体的部分结构加以替换重组而生成新个体的操作。变异是对群体中的个体串的某些基因座上的基因值作变动。
当最优个体的适应度达到给定的阈值,或者最优个体的适应度和群体适应度不再上升时,或者迭代次数达到预设的代数时,算法终止。预设的代数一般设置为100-500代。
二、AFL 的使用步骤
1. 获取 AFL
Linux下,终端键入:
1 | sudo apt-get install afl |
从 前 Google 安全研究员 lcamtuf 的个人网站上下载亦可:
进入 https://lcamtuf.coredump.cx/afl/releases/,下载并解压,输入make; make install 命令安装。
或终端输入命令:
1 | wget https://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz |
GitHub 上下载亦可:
1 | git clone https://github.com/google/AFL.git --depth=1 |
安装之后,输入 afl-fuzz 进行验证。
注:如果在 docker 中使用 AFL,在开启 container 时要加上 “—privileged” 选项,否则在执行某些命令时会报错。
2. 测试前置工作
指示系统将 coredumps 输出为文件
如果系统配置为将核心转储文件(core)通知发送到外部程序,将导致把崩溃信息发送到 Fuzzer 的延迟增大,进而可能将崩溃误报为超时。
故而,需要执行以下命令指示系统临时将 coredumps 输出为文件(重启后失效):
1 | echo core > /proc/sys/kernel/core_pattern |
3. 白盒测试(有源码测试)
对测试内容进行插桩
插桩技术指在保证原有程序逻辑完整性的基础上,在程序特定的位置插入代码段,从而收集程序运行时的上下文信息(方法本身、方法参数值、返回值等)。
这里我们使用 afl-gcc 进行源码插桩编译。afl-gcc/afl-g++ 是 gcc/g++ 的包装,它们的用法基本一样,前者会将接收到的参数传递给后者,我们编译程序时只需要将 gcc 替换为 afl-gcc/afl-g++ 就行。
如,对于单个测试用例 test.c
1 | -g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。 |
对于一个项目进行插桩编译,参考:AFL漏洞挖掘技术漫谈 之 第五点:构建被测试程序
如果使用 LLVM 模式进行源码插桩,可以更快的进行 Fuzz,安装 AFL 时可根据需要选择安装 LLVM:
1 | sudo apt-get install clang |
选择初始语料库
AFL需要一些初始输入数据(也叫种子文件)作为 Fuzzing 的起点,就像遗传算法中的初始群体,这些输入甚至可以是毫无意义的数据。
可以从网上找语料库,AFL漏洞挖掘技术漫谈 之 第四点:构建语料库 中列出了一些语料库。
也可以自己随便建个文件输入一些字符串,比如 “Hello world” ,虽然有时效率不高,但对简单测试还是能用的。
精简语料库
一般是当所有文件都用 tmin 处理完后,再利用 cmin 工具处理一次,从而达到最精简。
AFL-TMIN
AFL-TMIN 工具可以把我们提供的单个文件放到经过 afl 编译好的程序里执行,然后再按一定方式进行精简,从而缩减该测试用例的大小。它减小单个输入文件的大小,作用单位是单个文件。
命令:
afl-tmin -i 需要精简的文件 -o 精简后的文件 测试程序
afl-tmin有两种工作模式,instrumented mode和crash mode。
默认的工作方式是 instrumented mode,如果指定了参数 -x,就会调用 crash mode 模式,会把导致程序非正常退出的文件直接剔除。
AFL-CMIN
AFL-CMIN 是移除覆盖了相同的代码的输入文件,作用单位是一堆文件。
命令:
afl-cmin -i 测试输入文件的文件夹 -o 筛选后的输入文件的文件夹 测试程序
afl-cmin是每次用一个文件跟之前的比较,如果能达到上一个的效果,就替换掉上一个文件,因此至少会留下一个具有相同效果的文件。
运行 AFL
afl-showmap 工具用于跟踪单个输入用例的执行路径,可以进行最简单的实验。
而 afl-fuzz 用法如下:
afl-fuzz -i 输入文件的文件夹 -o 输出文件夹 测试程序
如果进入一个状态窗口,就表明已经开始 Fuzzing 了。以下是一些状态含义:
- process timing:Fuzzer运行时长、以及距离最近发现的路径、崩溃和挂起经过了多长时间。
- overall results:Fuzzer当前状态的概述。
- cycle progress:我们输入队列的距离。
- map coverage:目标二进制文件中的插桩代码所观察到覆盖范围的细节。
- stage progress:Fuzzer现在正在执行的文件变异策略、执行次数和执行速度。
- findings in depth:有关我们找到的执行路径,异常和挂起数量的信息。
- fuzzing strategy yields:关于突变策略产生的最新行为和结果的详细信息。
- path geometry:有关Fuzzer找到的执行路径的信息。
- CPU :CPU利用率
查看结果
查看输出文件夹,通常里面有三个文件夹:
Crashes文件夹:包含导致测试程序接收致命信号的测试用例,进入 crashes 可以看到触发得到的 crash,递增序列号代表每一个具有独特执行路径的测试用例。
分别查看各个 crash 样例,可以输入:
1 | xxd 文件名 |
hangs:包含导致测试程序超时的测试用例。
queue:包含每个独特执行路径的测试用例,以及用户给出的所有起始文件。
4. 黑盒测试(对二进制文件测试)
使用 AFL 的 QEMU 模式。待补充。
参考:
从AFL开始FUZZ之旅
AFL漏洞挖掘技术漫谈(一):用AFL开始你的第一次Fuzzing
经典 Fuzzer 工具 AFL 模糊测试指南
AFL学习笔记(上)