深度剖析AFL++二进制模糊测试工具:原理、应用与实战
Ba1_Ma0 发表于 四川 历史精选 2923浏览 · 2024-08-23 09:56

什么是AFL++

AFL++(American Fuzzy Lop ++)是一款开源的模糊测试工具,用于发现软件中的漏洞。模糊测试(Fuzzing)是一种自动化的测试技术,旨在通过向软件输入大量随机或伪随机的数据,来发现潜在的安全漏洞或程序崩溃。AFL 是由 Michał Zalewski 开发的,被认为是最流行和有效的模糊测试工具之一。

环境安装

由于 AFL++ 在进行模糊测试时可能对测试环境产生影响,因此推荐在隔离的环境中运行,如 Docker 容器或虚拟机。这种隔离能够有效防止因模糊测试导致的系统不稳定或崩溃等问题,确保主系统的安全性和稳定性。如果使用虚拟机进行测试,建议定期创建快照,以便在测试过程中出现异常时快速恢复到先前的状态。
安装环境:

apt-get update && apt-get install -yq gcc make wget curl git vim gdb clang llvm python3 python3-pip bsdmainutils

afl++ 下载地址:

https://github.com/AFLplusplus/AFLplusplus

如果在解压 ZIP 文件时遇到权限不足的问题,可以将 ZIP 文件转移到 Linux 环境下,使用 sudo unzip [文件名.zip] 命令进行解压。这种方法可以有效绕过权限限制。
下载完成后,进入 AFL 文件夹,直接运行命令编译文件。

make && make install


当按下 Tab 键时,如果能够正确补全并显示工具名称,则表明编译已成功完成。

AFL++ 设置与编译

这里实战用nmap程序来演示,如果你要用AFL++去fuzz一个程序,那么就需要下载对应程序的源代码,nmap的源代码在github可以找到

https://github.com/nmap/nmap

解压完后,进入文件夹,这里讲一下特殊的文件


configure 文件通常存在于许多开源软件的源代码目录中,是自动配置脚本的一部分,用于准备构建软件。它是使用 GNU Autotools(例如 autoconf)生成的脚本,目的是在不同的系统环境下自动配置软件包的编译和安装,afl++会利用这个文件,编译程序时插桩,具体细节在AFL++模糊测试原理处会详细展开说明
使用afl-gcc编译程序

CC=afl-clang-fast ./configure --disable-shared

CC=afl-clang-fast 表示在运行 ./configure 时,将使用 aflafl-clang-fast 代替系统默认的 C 编译器执行configure文件,afl-clang-fast 是 AFL++ 的一个编译器包装器,基于 LLVM 的 clang 编译器。相比于 afl-gccafl-clang-fast 使用了更现代的插桩技术,提供了更高效的性能和更精确的代码覆盖率监控 ,--disable-shared 选项,可以确保目标程序只编译成静态链接的二进制文件。所有的代码都会被直接链接到生成的二进制文件中,AFL++ 能够插桩并跟踪所有代码的执行路径,而不会遗漏掉因为动态链接而无法插桩的部分
如果之后make编译失败,也可以使用afl-gcc

CC=afl-gcc ./configure --disable-shared


然后执行make,make 是一个构建自动化工具,用于根据 Makefile 中的指令来编译和构建程序


make

如何Fuzz程序命令行参数

现在编译完成后,可以使用 AFL++ 进行模糊测试。但需要注意的是,如果不在目标程序中引入 argv-fuzz-inl.h 头文件,AFL++ 将默认对程序的标准输入(stdin)进行模糊测试,而不是针对命令行(argv)参数。因此,AFL++ 只会测试程序运行后等待用户输入的部分。如果希望对程序的命令行参数进行模糊测试(例如测试 nmap-iL 参数),则必须在目标程序的源码中包含 argv-fuzz-inl.h 头文件,并对相应代码进行调整,以便 AFL++ 能够正确生成并测试不同的命令行参数组合
argv-fuzz-inl.h文件下载地址:

https://github.com/google/AFL/blob/master/experimental/argv_fuzzing/argv-fuzz-inl.h

原理:


这里调用了afl_init_argv函数来覆盖argv。argv是传递给main函数的字符串数组,包含程序的命令行参数。因此,无论afl_init_argv函数执行了何种操作,最终结果都会覆盖argv


随后调用了read函数,从标准输入中读取了大量数据


ptr是一个指向缓冲区的指针,用于将数据读入其中。接下来,通过while循环检查指针所指向的内容是否为非零值。在循环中,指针会递增,并向前移动以处理接下来的数据


在这个循环中还有一个名为rc的计数器,它从1开始并在每次迭代时递增。循环开始时,当前指针的位置会被存储在ret数组中,以便后续使用


在while循环内还有一个嵌套的while循环,这个内层循环的作用是检查是否存在空字节,并在发现空字节时递增指针。同时,它会将数据存储在ret数组中,继续处理接下来的内容


接下来,函数会创建一个伪造的argv结构,并将其返回给主函数。这意味着在main函数中获取的argv将全部使用由AFL++生成的数据,从而替换原有的命令行数据
下载 argv-fuzz-inl.h 文件后,打开目标程序的源代码文件,并在代码的头部加入以下代码行,引入该头文件:
nmap的源代码为nmap.cc

#include "/{path}/argv-fuzz-inl.h"

其中,{path} 应替换为 argv-fuzz-inl.h 文件的实际路径。如果文件已放在同一目录下,可以直接使用相对路径,如 #include "argv-fuzz-inl.h"


ps:如果需要不fuzz程序的参数,而是直接对 argv[0] 进行模糊测试,需要修改argv-fuzz-inl.h源内容

int rc = 0


回到正题,在被fuzz测试程序的main函数第一行加入:

AFL_INIT_ARGV();


重新编译文件

CC=afl-gcc ./configure --disable-shared & make


编译好的nmap程序就在当前目录下

AFL++进行FUZZ

设置fuzz时输入输出文件夹

mkdir /tmp/in
mkdir /tmp/out

在/tmp/in文件夹下是要测试的参数,这里只测试nmap的-iL参数

echo -en "-iL\x00" > /tmp/in/1

-en 选项用于确保 echo 不添加额外的换行符,并且 \x00 表示字符串的结束符(null 终止符)
开始进行fuzz

afl-fuzz -i /tmp/in -o /tmp/out ./nmap

-i 指定输入目录,-o 指定输出目录,最后是需要fuzz的程序


如果提示这个,直接输入echo core >/proc/sys/kernel/core_pattern,然后重新运行即可


最重要的一个区域是findings in depth

favored paths: 表示被认为最有可能找到新路径或错误的路径数量,以及占总路径数量的百分比。
new edges on: 显示新路径中发现的边缘数量及其占比。
total crashes: 显示发现的崩溃数量以及其中独特崩溃的数量。
total tmouts: 显示发现的超时数量及其中独特超时的数量。

如果出现total crashes,那么意味着程序中存在潜在的漏洞 , AFL++会将导致崩溃的输入文件保存到/tmp/out目录下,随后可以结合GDB进行调试,以进一步分析并定位问题
多线程可以让fuzz效率变高,在不同的终端里执行以下命令:

afl-fuzz -i /tmp/in -o /tmp/out -M f1 ./nmap
afl-fuzz -i /tmp/in -o /tmp/out -S f2 ./nmap
afl-fuzz -i /tmp/in -o /tmp/out -S f3 ./nmap
……


fuzz一次时间比较久,1天都是常见的事

AFL++模糊测试原理

现在将使用正常gcc编译的nmap和afl-gcc编译的nmap进行对比,这是正常编译的nmap


这是使用afl-gcc编译的nmap


在每个跳转分支中,AFL++插入了afl_maybe_log函数。在调用afl_maybe_log之前,还存在一个mov指令,该指令的作用是设置校准参数。当执行afl_maybe_log时,AFL++会记录程序采用了哪个分支。如果在大多数情况下分支指令都是向左执行时,突然发现有一个输入可以触发向右分支,AFL++就知道到达了程序的新路径或功能点,继续对程序进行模糊测试。

0 条评论
某人
表情
可输入 255

没有评论