avatar

Catalog
AFL源码分析01:afl-gcc.c

前言

阅读AFL源码是深入理解Fuzz的第一步,也是为日后对AFL进行魔改或打造自己的Fuzz工具打下基础,本篇从插桩编译开始,一步步了解AFL进行Fuzz的完整流程。

afl-gcc.c源码分析

概述

  • AFL(普通)插桩部分源码主要有3个:afl-gcc.c、afl-as.h、afl-as.c
  • 本质上afl-gcc是对gcc/clang的一个封装(wrapper),通过对程序的不同分支进行插桩,从而记录程序的执行路径,检测样本的覆盖率等程序运行情况的反馈信息
  • 为了阅读方便,本篇及之后的分析均保留源码本身,并将分析以注释的方式标记在源码附近,同时根据情况修剪源码中原本的注释

关键变量

c
1
2
3
4
5
static u8*  as_path;                /* 指向afl-as的路径(一个AFL对as的wrapper) */
static u8** cc_params; /* 一个数组,用于存放实际传递给编译器CC的参数 */
static u32 cc_par_cnt = 1; /* 用于计算在数组cc_params中参数个数 */
static u8 be_quiet, /* 静默模式 */
clang_mode; /* afl-clang模式 */

main

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 主程序入口点 */
int main(int argc, char** argv) {

if (isatty(2) && !getenv("AFL_QUIET")) {
SAYF(cCYA "afl-cc " cBRI VERSION cRST " by \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);
execvp(cc_params[0], (char**)cc_params);
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0;

}

删去主函数中不值得关注的部分,我们可以做一个简化,如下所示:

c
1
2
3
4
5
6
7
int main(int argc, char** argv) {
find_as(argv[0]); /* 找到afl-as,将其加入路径 */
edit_params(argc, argv); /* 设置gcc所需的参数 */
execvp(cc_params[0], (char**)cc_params); /* 调用实际的gcc进行编译 */

return 0;
}

find_as

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/* 查找AFL自己的汇编器afl-as的路径,并将其设置为as_path */
static void find_as(u8* argv0) {

/* 获取环境变量AFL_PATH的值 */
u8 *afl_path = getenv("AFL_PATH");
u8 *slash, *tmp;

/* 如果afl_path指向的路径不为空
判断afl_path/as是否存在
如果存在就将as_path的值设置为afl_path */
if (afl_path) {
tmp = alloc_printf("%s/as", afl_path);

if (!access(tmp, X_OK)) {
as_path = afl_path;
ck_free(tmp);
return;
}

ck_free(tmp);

}

/* 获取到argv0中最后一个出现'/'的地方 */
slash = strrchr(argv0, '/');

if (slash) {
u8 *dir;

/* 截断最后一个'/'后面的字符串
dir获取到最后一个'/'出现之前的字符串
将tmp设置为dir/afl-as */
*slash = 0;
dir = ck_strdup(argv0);
*slash = '/';

tmp = alloc_printf("%s/afl-as", dir);

/* 若该路径存在,则设置as_path的值为dir */
if (!access(tmp, X_OK)) {
as_path = dir;
ck_free(tmp);
return;
}

ck_free(tmp);
ck_free(dir);

}

/* 如果上述两种方案都没找到,就直接拼接AFL_PATH和/as去找(感觉和第一种方式是一样的)
如果还是没找到,就通过FATAL输出错误信息后exit(1)退出 */
if (!access(AFL_PATH "/as", X_OK)) {
as_path = AFL_PATH;
return;
}

FATAL("Unable to find AFL wrapper binary for 'as'. Please set AFL_PATH");

}

edit_params

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/* 将参数argv传递给参数数组cc_params, 并根据编译需求做一些必要的修改 */
static void edit_params(u32 argc, char** argv) {

u8 fortify_set = 0, asan_set = 0;
u8 *name;

#if defined(__FreeBSD__) && defined(__x86_64__)
u8 m32_set = 0;
#endif

/* 给cc_params分配内存,大小为(argc+128)*8 */
cc_params = ck_alloc((argc + 128) * sizeof(u8*));

/* 找到argv[0]中最后一个出现'/'的地方,
若argv[0]中不存在'/',则设置name为argv[0],
如果argv[0]中存在'/',则设置name的值为'/'后面的字符串 */
name = strrchr(argv[0], '/');
if (!name) name = argv[0]; else name++;

/* 如果name的前9个字符是afl-clang:
设置clang_mode值为1
设置环境变量CLANG_ENV_VAR的值为1 */
if (!strncmp(name, "afl-clang", 9)) {

clang_mode = 1;
setenv(CLANG_ENV_VAR, "1", 1);

/* 如果name的值是afl-clang++
获取环境变量AFL_CXX
如果AFL_CXX存在,则将其添加至cc_params数组的第一个元素
如果AFL_CXX不存在,则将clang++添加至cc_params数组的第一个元素
否则
获取环境变量AFL_CC
如果AFL_CC存在,则将其添加至cc_params数组的第一个元素
如果AFL_CC不存在,则将clang添加至cc_params数组的第一个元素 */
if (!strcmp(name, "afl-clang++")) {
u8* alt_cxx = getenv("AFL_CXX");
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++";
} else {
u8* alt_cc = getenv("AFL_CC");
cc_params[0] = alt_cc ? alt_cc : (u8*)"clang";
}

} else {

#ifdef __APPLE__

/* 若afl-gcc运行在Apple平台下,则会进入#ifdef __APPLE__ 进行如下操作
如果name的值是afl-g++,则将环境变量AFL_CXX的值其添加至cc_params数组的第一个元素
如果name的值是afl-gcj,则将环境变量AFL_GCJ其添加至cc_params数组的第一个元素
否则,将环境变量AFL_CC其添加至cc_params数组的第一个元素 */
if (!strcmp(name, "afl-g++")) cc_params[0] = getenv("AFL_CXX");
else if (!strcmp(name, "afl-gcj")) cc_params[0] = getenv("AFL_GCJ");
else cc_params[0] = getenv("AFL_CC");

if (!cc_params[0]) {

SAYF("\n" cLRD "[-] " cRST
"On Apple systems, 'gcc' is usually just a wrapper for clang. Please use the\n"
" 'afl-clang' utility instead of 'afl-gcc'. If you really have GCC installed,\n"
" set AFL_CC or AFL_CXX to specify the correct path to that compiler.\n");

FATAL("AFL_CC or AFL_CXX required on MacOS X");

}

#else

/* 如果是Apple、x86_64、FreeBSD以外的系统架构
则会通过下面的指令去设置cc_param数组的第一个元素 */
if (!strcmp(name, "afl-g++")) {
u8* alt_cxx = getenv("AFL_CXX");
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"g++";
} else if (!strcmp(name, "afl-gcj")) {
u8* alt_cc = getenv("AFL_GCJ");
cc_params[0] = alt_cc ? alt_cc : (u8*)"gcj";
} else {
u8* alt_cc = getenv("AFL_CC");
cc_params[0] = alt_cc ? alt_cc : (u8*)"gcc";
}

#endif /* __APPLE__ */

}

while (--argc) {

/* 从第二个参数开始遍历argv中的参数,进行过滤 */
u8* cur = *(++argv);

/* 跳过参数'-B':'-B'选项用于设置编译器的搜索路径,这里跳过
因为之前已经处理过as_path了 */
if (!strncmp(cur, "-B", 2)) {
if (!be_quiet) WARNF("-B is already set, overriding");

if (!cur[2] && argc > 1) { argc--; argv++; }
continue;

}

/* 跳过参数'-integrated-as'
跳过参数'-pipe' */
if (!strcmp(cur, "-integrated-as")) continue;
if (!strcmp(cur, "-pipe")) continue;

#if defined(__FreeBSD__) && defined(__x86_64__)
if (!strcmp(cur, "-m32")) m32_set = 1;
#endif

/* 如果参数为'-fsanitize=address'或者'-fsanitize=memory'(告诉gcc检查内存访问的错
误,比如数组越界之类)
则设置asan_set的值为1
如果参数的子串中包含'FORTIFY_SOURCE'(FORTIFY_SOURCE主要进行缓冲区溢出问题的检查,
通常会检查memcpy, mempcpy, memmove, memset, strcpy, stpcpy, strncpy,
strcat, strncat, sprintf, vsprintf, snprintf, gets等函数)
则设置fortify_set的值为1 */
if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory")) asan_set = 1;

if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;

/* 没有被跳过的参数,将被依次加入cc_params数组中 */
cc_params[cc_par_cnt++] = cur;

}

/* 添加参数'-B'
添加汇编器as的路径作为参数 */
cc_params[cc_par_cnt++] = "-B";
cc_params[cc_par_cnt++] = as_path;

/* 如果是clang_mode(即第一个参数是afl-clang/afl-clang++)
则添加参数'-no-integrated-as' */
if (clang_mode)
cc_params[cc_par_cnt++] = "-no-integrated-as";

/* 如果设置了环境变量AFL_HARDEN
添加参数'-fstack-protector-all'
如果fortify_set的值没有被设置
则添加参数'-D_FORTIFY_SOURCE=2' */
if (getenv("AFL_HARDEN")) {
cc_params[cc_par_cnt++] = "-fstack-protector-all";

if (!fortify_set)
cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
}

/* 如果asan_set被设置了(前面的参数为'-fsanitize=address'或者'-fsanitize=memory')
就设置环境变量AFL_USE_ASAN的值为1
否则
如果存在环境变量AFL_USE_ASAN
则添加参数'-U_FORTIFY_SOURCE'和'-fsanitize=address'
这里不能同时指定AFL_HARDEN或者AFL_USE_MSAN,会导致运行时速度过慢
如果存在环境变量AFL_USE_MSAN
则添加参数'-U_FORTIFY_SOURCE'和'-fsanitize=memory'
同上不能同时指定AFL_HARDEN或者AFL_USE_ASAN,会导致运行时速度过慢 */
if (asan_set) {

/* 传递这个参数给afl-as用于调整映射密度 */
setenv("AFL_USE_ASAN", "1", 1);

} else if (getenv("AFL_USE_ASAN")) {

if (getenv("AFL_USE_MSAN"))
FATAL("ASAN and MSAN are mutually exclusive");

if (getenv("AFL_HARDEN"))
FATAL("ASAN and AFL_HARDEN are mutually exclusive");

cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=address";

} else if (getenv("AFL_USE_MSAN")) {

if (getenv("AFL_USE_ASAN"))
FATAL("ASAN and MSAN are mutually exclusive");

if (getenv("AFL_HARDEN"))
FATAL("MSAN and AFL_HARDEN are mutually exclusive");

cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=memory";


}

if (!getenv("AFL_DONT_OPTIMIZE")) {

#if defined(__FreeBSD__) && defined(__x86_64__)

/* 在64位FreeBSD系统上,clang -g -m32已损坏,但'-m32'本身工作正常
这与我们无关,但尽量避免触发这个错误 */
if (!clang_mode || !m32_set)
cc_params[cc_par_cnt++] = "-g";

#else

cc_params[cc_par_cnt++] = "-g";

#endif

/* 如果不存在环境变量AFL_DONT_OPTIMIZE
则依次添加下列参数:
'-O3'
'-funroll-loops'
'-D__AFL_COMPILER=1'
'- DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1'
其中后面2个参数意味你正在构建模糊测试,
其中之一是AFL特有的,另一个是与libfuzzer共享的*/
cc_params[cc_par_cnt++] = "-O3";
cc_params[cc_par_cnt++] = "-funroll-loops";
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";

}

/* 如果存在环境变量AFL_NO_BUILTIN,则表示允许进行优化
则依次添加下面这些参数...... */
if (getenv("AFL_NO_BUILTIN")) {
cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strstr";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr";
}

/* 最后截断cc_params,完成对cc_params参数数组的编辑 */
cc_params[cc_par_cnt] = NULL;

}

execvp

执行编译命令,生成目标文件。这个函数看手册就行了

(普通)插桩流程

插桩的过程如上图所示,看上去就是在普通程序编译的过程中,将gcc替换成了afl-gcc。为了更好的理解这个过程,我们做如下操作:

打开afl-gcc.c,在edit_params之后添加如下代码,打印cc_params来查看实际执行的命令

c
1
2
3
for(int i = 0; i < sizeof(cc_params); i++) {
printf("\targ%d: %s\n", i, cc_params[i]);
}

然后执行

shell
1
2
$ make
$ sudo make install

查看打印的参数

可以看到,afl-gcc.c帮我们添加了3个参数-B, /usr/local/lib/afl, -g。这是因为在Linux机器上使用gcc进行编译的时候,默认会使用GNU as作为汇编器,因此这里使用”-B“参数指定使用afl-as。之后afl-as读取并分析输入的.s文件,然后添加instrumentation trampoline 和 main payload,之后再调用GNU as,本质上afl-as也是一个对GNU as的wrapper。

在下一篇,我们会继续分析与(普通)插桩相关的afl-as.c,去研究afl-as到底做了什么。

参考资料

  1. hollk:AFL源码分析之afl-gcc.c详细注释
  2. skr:sakuraのAFL源码全注释
  3. Seebug:AFL 二三事——源码分析
  4. AFL内部实现细节小记
  5. AFL:afl-gcc.c
  6. ScUpax0s:AFL源码阅读笔记之gcc与fuzz部分
  7. HICOOKIE:AFL-Learning
  8. 简书:AFL源码分析
Author: cataLoc
Link: http://cata1oc.github.io/2022/01/02/AFL%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%9001/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶