AFL-源码分析 01 afl-gcc
注:由于青训营-前端会员开通时间比较晚,第一天的还没看完,正在看,所以先看了春日打卡计划【青训营 x 字节前端训练营】春日打卡计划来啦!【在沸点打卡,本文章下评论打卡无效】 - 掘金 (juejin.cn)中第一篇,用于学习。此文参加笔记计划【青训营 x 字节前端训练营】✍🏻 笔记创作活动 - 掘金 (juejin.cn)。另一方面用于记录自己在青训营期间学习到的可以多种知识。
1. afl-gcc
1.1 代码目的
- 总的来说,是让Fuzzing过程自动化的部分
- 通过编译时插入AFL-fuzzing代码,让执行文件时可以接受来自AFL的输入并测试
- “插桩”扩展功能并监控覆盖率,以此来指导AFL-fuzzing功能。
- GCC或者Clang的替代品
1.2 流程
- 检查参数与环境变量
- 调用find_as获得汇编器访问路径
- 调用edit_params编辑各类即将传递给编译器的参数,主要是cc_par_cnt与cc_params
- 使用execvp()调用编译器并传递给这个被调用的编译器编辑好的参数列表。
1.3 细节分析
1.3.1 全局变量
static u8* as_path; /* Path to the AFL 'as' wrapper */
static u8** cc_params; /* Parameters passed to the real CC */
static u32 cc_par_cnt = 1; /* Param count, including argv0 */
static u8 be_quiet, /* Quiet mode */
clang_mode; /* Invoked as afl-clang*?
定义全局静态变量,as-path表示 afl-as 汇编器的路径,cc-params表示传递给gcc(魔改过的)的参数,cc_par_cnt表示传给gcc的参数个数,其中argv0也算一个。be_quiet表示使用quiet mode 应该是不输出调试信息之类的,clang_mode 应该是使用clang作为编译器。
1.3.2 函数
两个函数、一个主函数
- 函数 find_as 表示找到 指定的AFL_as 汇编器。
- edit_params 将argc(参数数量)与argv(参数列表)传递给gcc/其他编译器
- main 函数调用以上两个函数,先找到汇编器,然后编辑参数,最后在上下文中直接进行系统调用,按照第一个参数调用修改过的GCC编译器,将Fuzzing代码插入到编译过程中,以在运行时进行自动化测试。(编译器调用后检查传递给编译器的参数,进一步调用了汇编器)
1.3.3 find_as
作用是:查找系统中可用的汇编器as 并将其存储在全局变量as_path中。查找顺序是:环境变量,argv0,AFL-PATH,失败则返回错误信息。最终将后文需要执行的程序存储在as_path中。
slash是指向参数0,也就是路径中从后向前的第一个'/'的位置指针。引入这样一个变量主要是为了在不同的系统中构造路径字符串时方便,即兼容性与跨平台性,因为windows下使用的是反斜杠。
首先通过getenv(char)系统调用获得AFL_PATH环境变量的值,AFL_PATH是用于指定AFL目录路径的环境变量。如果afl-path存在,则将AFL_PATH连同as一起打印到tmp字符串,使用access系统调用检查tmp是不是可以被当前进程访问的,如果可以访问,就将AFL_PATH设置给全局变量as_path,然后退出,否则释放tmp指针并继续查找。
如果上面没有找到,则将argv0使用封装了strdup的函数ck_strdup将argv0拷贝到一块新开辟的空间,并返回指向空间的指针dir,接下来将afl-as连接到dir后面,变为tmp,判断新的tmp是否可以访问。
如果还是没有找到,在afl-gcc中指定的宏AFL_PATH查找是否存在可用的as汇编器。(注意区分,前面第一次是在AFL_PATH环境变量中查找,后面按照AFL_PATH宏查找,宏只在定义的本文件中有用,编译时使用-D 参数,加上宏名=''可以对宏进行具体设置。)
如果以上都没有找到,则返回错误。
afl-as将会在调用gcc -B 参数后由这个文件指定路径到,afl-as将会作为汇编器,执行汇编操作,在此时进行代码插桩。
1.3.4 edit_params 函数
作用:解析命令行参数,保存在全局变量中,在后续调用其他函数时使用。
fortify_set与asan_set用于保存编译器状态信息。name是保存编译器名称,cc_params保存编译器参数。其中find_as找到的汇编器路径保存在cc_params[1]或者靠后的位置
fortify_set和asan_set是用于保存编译器状态信息的变量,具体含义如下:
fortify_set:用于保存编译器中是否启用了Fortify Source(FORTIFY_SOURCE)选项的状态信息。FORTIFY_SOURCE是一种代码安全机制,用于防范缓冲区溢出漏洞,它会在编译时添加一些检查代码以确保程序的安全性。
asan_set:用于保存编译器中是否启用了AddressSanitizer(ASan)选项的状态信息。AddressSanitizer是一种内存检查工具,用于检测内存错误、堆栈溢出、缓冲区溢出等问题,能够有效地提高程序的安全性和稳定性。
在代码中,如果设置了-fsanitize=address或-fsanitize=memory选项,就会将asan_set设置为1,表示编译器中启用了ASan选项;如果代码中包含FORTIFY_SOURCE宏定义,就会将fortify_set设置为1,表示编译器中启用了FORTIFY_SOURCE选项。
strrchr函数查找一个字符串在另一个字符串末次出现的位置,并返回指针。本次调用及后面的if判断,是将第一个参数设置到name,即编译器名称。
通过判断编译器是不是clang或者clang++,设置clang_mode或者相关的环境变量,总之中间部分主要是通过编译器参数,来设置自己要选用的环境变量以及在后面调用中,需要使用哪些参数。
while部分开始,是解析所有的命令行参数,主要包括是否使用clang默认的汇编器,是否检测各种错误,是否开启所有的安全机制,是否使用优化等等。最后的getenv(“AFL_NO_BUILTIN”)表示AFL为了安全与稳定的考虑禁用那些内建函数。
-B参数在gcc中用于设置编译器的搜索路径
1.3.5 main函数
调用isatty检查输入的文件描述符是否是可以访问的终端设备
SAYF是使用define宏定义将fprintf 到 stderr 流。
execvp()会从环境变量所指的目录中查找符合参数 file 的文件名, 找到后执行该文件, 然后将第二个参数argv 传给该执行的文件。
1.4 可选更改汇总
- 此处汇总可选的更改选项,一方面便于使用AFL,另一方面在后续可能进行AFL的魔改会用到
- AFL_CC AFL_CXX AFL_AS 更改下一阶段的工具链
- AFL_HARDEN 在编译代码中进行强化优化
- 等等的参数