写在前面
接下来是 afl 的编译,分成两个部分 gcc 编译部分和 llvm 模式,首先来介绍 gcc 部分。
afl-gcc
1. 文件描述
afl-gcc是gcc的一个wrapper,能够实现对一些关键节点进行插桩,利用插桩代码来记录程序的执行路径等信息。该文件的主要作用是进行源码编译,并在编译前对输入的参数进行处理。
2. 文件架构
文件涉及的头文件调用关系如下:
大部分属于标准库中的头文件,AFL自定义头文件有alloc-inl.h
、debug.h
、config.h
、types.h
总计4个,在后面涉及到的源码部分再对这4个头文件进行展开详解。
afl-gcc.c
文件主要包含三个函数:main
, find_as
, edit_params
。函数的主要作用如下所示:
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*? */
u8
是 uint8_t
类型。
as_path
存放AFL的as封装的路径,cc_params
是传递给真正的cc的参数,cc_par_cnt
是传递给cc的参数的个数,be_quiet
是AFL是否使用quiet模式,clang_mode
是是否使用afl-clang进行编译。
2. main函数
main
函数的主要逻辑是进行简单的程序启动设置,其核心是调用find_as(argv[0])
和 edit_params(argc, argv)
。
/* Main entry point */
int main(int argc, char** argv) {
if (isatty(2) && !getenv("AFL_QUIET")) {
SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
} else be_quiet = 1;
if (argc < 2) {
SAYF("\n"
"This is a helper application for afl-fuzz. It serves as a drop-in replacement\n"
"for gcc or clang, letting you recompile third-party code with the required\n"
"runtime instrumentation. A common use pattern would be one of the following:\n\n"
" CC=%s/afl-gcc ./configure\n"
" CXX=%s/afl-g++ ./configure\n\n"
"You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.\n"
"Setting AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
BIN_PATH, BIN_PATH);
exit(1);
}
find_as(argv[0]);
edit_params(argc, argv);
printf("\n");
for (int i =0 ;i < sizeof(cc_params); i++){
printf("\tag%d: %s\n", i, cc_params[i]);
}
execvp(cc_params[0], (char**)cc_params);
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0;
}
这里我们添加一个 for
循环来打印出 main
处理的各个参数,就可以看到程序处理时真正完整的命令。
3. find_as函数
该函数主要用于查找对应的汇编器as,流程如下:
-
第一步,获取环境变量
AFL_PATH
。如果成功,则调用alloc_printf("%s/as", afl_path)
动态分配内存空间来存储路径,并确保这片内存可访问,然后进行free操作,最后return。如果不可访问,则直接free掉。 -
如果获取
AFL_PATH
失败,则匹配argv[0]
,然后通过ck_strdup
来提取当前路径dir
, 确保{dir}/afl-as
可访问后,赋值给as_path
,然后free,最后return。不可访问则直接return。 -
前面的方法都失败,会尝试直接去找as,如果再失败就输出错误信息然后程序退出。
函数使用了3个判断来实现as的查找流程,可以看到 AFL_PATH
的环境变量的优先级是最高的,在AFL的官方文档中,也是建议去设置 AFL_PATH
环境变量,这样可以确保编译时不会出问题。所以在使用AFL前,可以先设置一下 AFL_PATH
这个环境变量。
4. edit_params函数
该函数主要是把 argv的内容copy到 cc_params
中,并做必要的处理。函数的整体流程如下:
- 首先,调用
ck_alloc
为cc_params
进行内存分配,长度为(argc + 128)*sizeof(u8*)
; - 查找最后一个
/
来确认使用的对应的编译器(比如afl-gcc,afl-clang),将名称保存在name
中; - 将
name
的值和afl-clang
对比:- 如果相同,设置
clang_mode
值为1,设置环境变量CLANG_ENV_VAR
值为1,说明使用的是clang
编译器:- 比较
name
和afl-clang++
,相同则获取环境变量AFL_CXX
的值,成功后赋值给cc_params[0]
,说明使用的编译器是afl-clang++
,失败设置为clang++
;不相同则获取环境变量AFL_CC
的值,成功后赋值给cc_params[0]
,说明使用的编译器是afl-clang
,失败设置为clang
;
- 比较
- 如果不相同,比较
name
和afl-g++
:- 相同则获取环境变量
AFL_CXX
的值,成功后赋值给cc_params[0]
,说明使用的编译器是afl-g++
,失败设置为g++
;不相同则获取环境变量AFL_CC
的值,成功后赋值给cc_params[0]
,说明使用的编译器是afl-gcc
,失败设置为gcc
;
- 相同则获取环境变量
- 如果相同,设置
- 通过
while(--argc)
循环来遍历所有的 argv 参数进行处理,并放入cc_params
:- 跳过
-B/integreated-as/pipe
选项; - 存在
-fsanitize=address/memory
,则设置asan_set = 1
; - 存在
FORTIFY_SOURCE
,则设置fortify_set = 1
。 cc_params[cc_par_cnt++] = cur
- 跳过
- 其他参数选项设置:
- 获取之前计算的
as_path
,设置成-B as_path
,-B选项用于设置编译器的搜索路径; - 如果是clang模式,则追加
-no-integrated-as
选项; - 获取环境变量
AFL_HARDEN
,如果存在则追加-fstack-protector-all
,如果没有设置fortify_set
则追加-D_FORTIFY_SOURCE=2
。该环境变量是一个自动添加代码强化的选项,添加上该选析那个后,可以捕获一些non-crashing类型的内存错误; - 检查
asan_set
是否进行了设置,如果进行了设置, 则设置AFL_USE_ASAN
环境变量为1;如果没有设置,获取环境变量AFL_USE_ASAN
,成功则追加-U_FORTIFY_SOURCE
和-fsanitize=address
参数选项;如果前面两种都没有,再获取环境变量AFL_USE_MSAN
,成功则追加-U_FORTIFY_SOURCE
和-fsanitize=memory
参数选项。 - 获取环境变量
AFL_DONT_OPTIMIZE
,如果不存在,则设置-g -O3 -funroll-loops -D__AFL_COMPILER=1 -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1
- 获取环境变量
AFL_NO_BUILTIN
,如果存在,则设置-fno-builtin-strcmp -fno-builtin-strncmp -fno-builtin-strcasecmp -fno-builtin-strncasecmp -fno-builtin-memcmp -fno-builtin-strstr -fno-builtin-strcasestr
- 获取之前计算的
- 最后设置
cc_params[cc_par_cnt] = NULL
,结束对cc_params
的编辑。
可以看到,edit_params
主要是根据环境变量的设置对一些编译参数进行设置,基本涵盖了所有的编译情况。不同的情况下,会有不同的编译选项被添加。