什么是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-gcc
,afl-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++就知道到达了程序的新路径或功能点,继续对程序进行模糊测试。
没有评论