本来想一周内完成,看来要延期了,尽量希望能在这几天内完成吧,31号晚上还有qax的一面😭😭,水平果然还是太次了。
前言
AFL,全称“American Fuzzy Lop”,是由安全研究员Michal Zalewski开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具,它通过记录输入样本的代码覆盖率(代码执行路径的覆盖情况),以此进行反馈,对输入样本进行调整以提高覆盖率,从而提升发现漏洞的可能性。AFL可以针对有源码和无源码的程序进行模糊测试,其设计思想和实现方案在模糊测试领域具有十分重要的意义。
各模块简要说明
源码模块图像一览,图源来自文章:AFL二三事 - FreeBuf,侵删
插桩模块
afl-as.h, afl-as.c, afl-gcc.c
:普通插桩模式,针对源码插桩,编译器可以使用gcc, clang;llvm_mode
:llvm 插桩模式,针对源码插桩,编译器使用clang;qemu_mode
:qemu 插桩模式,针对二进制文件插桩。
fuzzer 模块
afl-fuzz.c
:fuzzer 实现的核心代码,AFL 的主体。
其他辅助模块
afl-analyze
:对测试用例进行分析,通过分析给定的用例,确定是否可以发现用例中有意义的字段;afl-plot
:生成测试任务的状态图;afl-tmin
:对测试用例进行最小化;afl-cmin
:对语料库进行精简操作;afl-showmap
:对单个测试用例进行执行路径跟踪;afl-whatsup
:各并行例程fuzzing结果统计;afl-gotcpu
:查看当前CPU状态。
部分头文件说明
alloc-inl.h
:定义带检测功能的内存分配和释放操作;config.h
:定义配置信息;debug.h
:与提示信息相关的宏定义;hash.h
:哈希函数的实现定义;types.h
:部分类型及宏的定义。
普通插桩
afl-gcc.c
afl-gcc.c本质上是对GCC 和 clang的封装,正如其在注释中写到的wrapper for GCC and clang。最常见的使用方式,便是在使用**./configure**时,直接将afl-gcc或着afl-clang的路径传给CC;同理对于C++代码,只需用afl-g++或者afl-clang++路径传给CXX即可。
几个静态全局变量:
1 | static u8* as_path; /* Path to the AFL 'as' wrapper */ |
main
main函数的核心主体如下
1 | int main(int argc, char** argv) { |
find_as
: try to find our “fake” GNU assembler in AFL_PATH or at the location derived from argv[0]. If that fails, abort.edit_params
: Copy argv to cc_params, making the necessary edits.execvp
: 执行命令
edit_params
在edit_params
中,关键逻辑如下
1 | ... |
总体逻辑比较简单,实际就是对参数做一些预处理,会用gcc
替换cc_params[0]
,利用-B as_path
替换汇编器。因而在调用execvp
执行命令时,本质上就是执行类似gcc ... -B as_path ...
这样的效果。
afl-as.c
在理解afl-gcc.c
之后,我们就自然而然会想到,核心点在于关注afl-as
和默认as的区别。类似于afl-gcc,afl-as也是一个对as的封装
一些全局静态变量:
1 | static u8** as_params; /* Parameters passed to the real 'as' */ |
main
main()
函数的核心主体如下,与afl-gcc.c中的main
十分类似。比较明显,都是通过先对参数进行处理,然后调用execvp()
去执行命令。
1 | int main(int argc, char** argv) { |
- 第一部分,比较明显,利用时间和pid即生成随机数
edit_params()
编辑参数add_instrumentation()
插桩- 最后利用execvp调用指令
edit_params
与afl-gcc的逻辑基本一致,替换参数,不过多赘述,核心部分如下:
1 | static void edit_params(int argc, char** argv) { |
add_instrumentation
比较明显的看到其他部分与afl-gcc基本相同,因而我们关键来看看add_instrumentation()
函数这部分内容。这个函数主要用于处理输入的文件,然后生成modified_file.并在恰当的地方插桩。此函数的核心部分如下:
1 | static void add_instrumentation(void) { |
从源代码的注释中我们也能看到,AFL插桩的位置偏好,主要为函数入口,分支标签,条件分支标签…….
1 | /* If we're in the right mood for instrumenting, check for function |
在add_instrumentation()
函数中的变量trampoline_fmt_64
和trampoline_fmt_32
即为插桩代码,我们可以在文件afl-as.h中找到定义,这里我们直接看看64位的插桩代码
1 | /* --- AFL TRAMPOLINE (64-BIT) --- */ |
流程非常简单易懂,大致如下:
- 入栈保存寄存器
call __afl_maybe_log
- 出栈恢复寄存器
这里直接来看看__afl_maybe_log
函数,由于这段汇编逻辑过长,大概200多行。这里直接放ScUpax0s师傅的流程图
流程图中涉及到的一些变量及含义如下:
1 | .lcomm __afl_area_ptr, 8 /*共享内存地址*/ |