抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

fuzz初试(AFL++)

简介:

模糊测试(Fuzzing/Fuzz)是一种自动化软件测试技术,它基于为程序提供随机或变异的输入值并监视它的异常和崩溃。

image-20210302163409721

 AFL 是一个覆盖引导的模糊器(coverage-guided fuzzer),这意味着它收集每个变异输入的覆盖信息,来发现新的执行路径和潜在的错误。当源代码可用时,AFL 可以使用插桩(instrumentation),在每个基本块(函数、循环等)的开头插入函数调用。

AFL++使用qemu用户模式模拟仿真来运行二进制文件,其使用的qemu是进行修改的版本,在程序执行时检测基本块,根据收集的信息生成测试用例,通过生成的大量测试用例触发不同的代码路径,从而提高代码的覆盖率,提高触发Crash的概率。

AFL的一些基础

1.jpg

AFL对开源代码进行fuzzing的过程可以用以下五步描述:

  1. 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage)

    #AFL的覆盖率,计算在一次运行中,相应边执行的次数,次数单位为2的幂(以缓解路径爆炸)。如果一个用例输入发现了至少一条边,也就是创建一个新的桶来装入新的边的次数,那么这个用例就是interesting用例,并放入队列。AFL用一个bitmap把这些装有边次数的桶整合起来,一个byte代表一条边

  2. 选择一些输入文件,作为初始测试集加入输入队列(queue)

  3. 将队列中的文件按一定的策略进行 “突变”

  4. 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中

  5. 上述过程会一直循环进行,期间触发了crash的文件会被记录下来

其大致思路是,对输入的seed文件不断地变化,并将这些mutated input喂给target执行,检查是否会造成崩溃。因此,fuzzing涉及到大量的fork和执行target的过程。也就是说,父进程fork出一个子进程执行输入用例,父进程等待结果。这样可以避免频繁调用execve()函数。
但是,fork也会有性能瓶颈的问题,AFL提出persistent mode,该模式下不会为每个测试用例fork。 取而代之的是,可以将循环的方式添加到目标程序中,也就是每次迭代执行一个测试用例。

AFL++

变异器(对测试输入进行一些操作

自定义变异器允许相关的模糊测试的研究在 AFL++ 之上构建新的调度算法、变异算法等等,而无需像当前许多工具那样fork 和修补 AFL。插件可以用 C ABI 兼容的语言编写,甚至可以用 Python 进行原型设计。例如,使用当前的 API,AFLSMART 可以作为 AFL++ 插件完全重写十次。
目前AFL++实现了以下功能:

  1. afl_custom_(de)init :初始化AFL++的伪随机数种子生成器
  2. afl_custom_queue_get:其是一个回调函数,用于确定自定义的FUZZer是否应该FUZZ当前队列的用例。
  3. afl_custom_fuzz:对给定的输入执行自定义变异。
  4. afl_custom_havoc_mutation:对给定的输入执行单个自定义变异。 这种突变与AFL的havoc阶段的其他变异策略叠加在一起。
  5. afl_custom_post_process:在某些情况下,从自定义 mutator 返回的变异数据的格式不适合作为输入到目标程序执行。例如,当使用 libprotobuf-mutator 时,返回的数据是对应于给定语法的 protobuf 格式,首先需要将其转换为目标的纯文本格式。 在这种情况下,或者要修复校验和以及大小,用户可以定义 afl_custom_post_process 函数。
  6. afl_custom_queue_new_entry:在将新测试用例添加到队列后调用,这是一个存储元数据的API。

支持用例修剪的API

修剪用例的目的是减少因为大量产生用例导致格式过于复杂,以至于不符合协议格式。

  1. afl_custom_init_trim:该API在每次修剪操作开始时被调用并接收初始缓冲区。它返回此次输入上可能的迭代次数(例如,如果输入有 n 个元素,其中一个应该被删除,则返回 n-1)。 如果实现的修剪算法不允许确定(剩余)步骤的数量,那么它可以返回 1 表示可以执行进一步的修剪,这将在 afl_custom_post_trim 返回 0 时执行。
  2. afl_custom_trim:每次修剪操作都会调用 afl_custom_trim。 它可以记住当前状态,因此可以保存每次迭代的重新分析的步骤。该API返回修剪后的输入缓冲区,其返回的数据长度不得超过初始输入数据。
  3. afl_custom_post_trim:该API在每次修剪操作后调用以通知修剪步骤是否成功。

插桩

1 LLVM

LLVM主要包含以下两种插桩方式:
上下文敏感的边缘覆盖:edge覆盖是将每个block被分配的ID与被调用者的唯一ID进行异或运算。
Ngram:在记录edge时不考虑前一个块和目标块,而是考虑目标块和前 N-1 个块,其中 N 是 2 到 16 之间的数字。

2 GCC

除了包含旧的 afl-gcc 包装器,AFL++ 还附带了一个 GCC 插件。 它包括对延迟初始化和persistent 模式的支持,例如 AFL LLVM 模式。

3 QEMU

该模式针对二进制程序进行模糊测试。

CVE-2019-13288 in XPDF 3.02

实验环境配置

虚拟机Ubuntu 20.04.2 LTS 镜像。用户名为 fuzz / fuzz

下载并构建

首先为Fuzz目标创建一个新目录:

1
2
cd $HOME
mkdir fuzzing_xpdf && cd fuzzing_xpdf/

可能需要安装一些额外的工具(即 make 和 gcc)

1
sudo apt install build-essential

下载 Xpdf 3.02:

1
2
wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz
tar -xvzf xpdf-3.02.tar.gz

构建 Xpdf:

1
2
3
4
5
cd xpdf-3.02
sudo apt update && sudo apt install -y build-essential gcc
./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install

现在可以开始测试Xpdf。首先,需要下载一些 PDF 示例:

1
2
3
4
5
cd $HOME/fuzzing_xpdf
mkdir pdf_examples && cd pdf_examples
wget https://github.com/mozilla/pdf.js-sample-files/raw/master/helloworld.pdf
wget http://www.africau.edu/images/default/sample.pdf
wget https://www.melbpc.org.au/wp-content/uploads/2017/10/small-example-pdf-file.pdf

可以使用以下命令测试 pdfinfo 二进制文件:

1
$HOME/fuzzing_xpdf/install/bin/pdfinfo -box -meta $HOME/fuzzing_xpdf/pdf_examples/helloworld.pdf

image-20220830151819839

安装AFLplusplus

安装依赖:

1
2
3
4
sudo apt-get update
sudo apt-get install -y build-essential python3-dev automake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools
sudo apt-get install -y lld-11 llvm-11 llvm-11-dev clang-11 || sudo apt-get install -y lld llvm llvm-dev clang
sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-dev

构建 AFL++:

1
2
3
4
5
cd $HOME
git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
export LLVM_CONFIG="llvm-config-11"
make distrib
sudo make install

输入afl-fuzz如下即为成功

image-20220830152035786

使用AFL++开始Fuzz

当源代码可用时,AFL 可以使用检测,在每个基本块(函数、循环等)的开头插入函数调用。为了为目标应用程序启用检测,所以需要使用 AFL 的编译器编译代码。简单来说就是需要使用afl编译器来编译目标。

首先,清理所有以前编译的目标文件和可执行文件:

1
2
3
rm -r $HOME/fuzzing_xpdf/install
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean

现在将使用afl-clang-fast编译器构建 xpdf

1
2
3
4
export LLVM_CONFIG="llvm-config-11"
CC=$HOME/AFLplusplus/afl-clang-fast CXX=$HOME/AFLplusplus/afl-clang-fast++ ./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install

现在可以使用以下命令运行Fuzz:

1
afl-fuzz -i $HOME/fuzzing_xpdf/pdf_examples/ -o $HOME/fuzzing_xpdf/out/ -s 123 -- $HOME/fuzzing_xpdf/install/bin/pdftotext @@ $HOME/fuzzing_xpdf/output

执行参数说明:

  • -i:表示输入文件目录
  • -o:表示 AFL++ 将存储变异文件的目录
  • -s:表示要使用的静态随机种子(AFL 使用非确定性测试算法,因此两个Fuzz会话永远不会相同。这就是为什么设置固定种子 -s 123的原因。用以保证Fuzz结果和示例相同。)
  • @@:是占位符目标的命令行,AFL 将用每个输入文件名替换它

如果收到「Hmm, your system is configured to send core dump notifications to an external utility…」类似的命令,执行以下操作关闭核心转储:

1
2
3
sudo su
echo core >/proc/sys/kernel/core_pattern
exit

image-20220830205709210

可以看到红色的uniq crashes值,显示找到的唯一崩溃的数量。可以在$HOME/fuzzing_xpdf/out/default/crashes目录中找到这些崩溃文件。一旦发现第一个崩溃,就可以使用control+c停止 fuzzer。

什么时候可以停止fuzzer?其中一个指标可以参考cycles done 的数字颜色,依次会出现洋葱红色,黄色,蓝色,绿色,变成绿色时就很难产生新的crash文件了。

image-20220830211256560

将此文件作为输入传递给 pdftotext 二进制文件(如果提示无法打开文件,将文件名称改为xxx.pdf再尝试)

1
$HOME/fuzzing_xpdf/install/bin/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/crash.pdf $HOME/fuzzing_xpdf/output

image-20220830211816706

使用 gdb 找出程序因该输入而崩溃的原因

首先,需要使用调试信息重建 Xpdf 以获得符号堆栈跟踪:

1
2
3
4
5
6
rm -r $HOME/fuzzing_xpdf/install
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean
CFLAGS="-g -O0" CXXFLAGS="-g -O0" ./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install

现在,可以运行 GDB:

1
gdb --args $HOME/fuzzing_xpdf/install/bin/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/crash.pdf $HOME/fuzzing_xpdf/output

然后,在 GDB 中输入:

1
run

如果一切顺利,应该会看到以下输出:

image-20220830212802894

然后输入bt以获取栈回溯:bt

image-20220830212922577

  • 报错信息Program received signal SIGSEGV, Segmentation fault,存在内存泄漏
  • 报错位置 _int_malloc (av=av@entry=0x7ffff7c63b80 <main_arena>, bytes=bytes@entry=7) at malloc.c:1210,glibc报了个错,显然是堆内存出了问题
  • 执行流信息,分析一下可以看出调用过程是循环的,判断为无限循环漏洞
  • 根据函数调用找到漏洞位置

漏洞修复

下个xpdf4.02源码对比一下就好,修复方式比较简单,加了个变量,记录循环次数,超过一定次数就结束进程。

参考:

[原创]AFL速通——流程及afl-fuzz.c源码简析-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com

AFL++ (PlusPlus) 介绍与实践 - WelkinChan - 博客园 (cnblogs.com)

模糊测试之AFL总结 (myfzy.top)

AFL(American Fuzzy Lop)实现细节与文件变异 (seebug.org)

评论