main

首先从 main 来看:

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
/* 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);

execvp(cc_params[0], (char**)cc_params);

FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);

return 0;

}

会首先检查

  • isatty(2) 是否为 1 // stdout
  • 设置了 AFL_QUIET 环境变量

如果有一个不满足则切换为 quiet 模式

接下来主要流程分配三个阶段:

  • find_as
  • edit_params
  • execvp

find_as

该函数会去寻找所使用的汇编器,传入的 argv0 就是 afl-gcc s:

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
/* Try to find our "fake" GNU assembler in AFL_PATH or at the location derived
from argv[0]. If that fails, abort. */

static void find_as(u8* argv0) {

u8 *afl_path = getenv("AFL_PATH");
u8 *slash, *tmp;

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);

}

slash = strrchr(argv0, '/');

if (slash) {

u8 *dir;

*slash = 0;
dir = ck_strdup(argv0);
*slash = '/';

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

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

ck_free(tmp);
ck_free(dir);

}

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");

}

其步骤如下:

  • 获得环境变量 AFL_PATH 并赋值给 afl_path
    • 如果 afl_path 存在,则将 afl_path 拼接上 “/as” 作为 as 的路径,检查其是否可访问,可访问则返回
  • 从 argv0 中寻找是否有 “/” 字符
    • 如果有则将 “/” 字符之前的内容拆分,并拼接上 “/afl-as” 作为 as 的路径,检查其是否可访问,可访问则返回
  • 如果都不行则最后抛出异常

edit_params

edit_params 函数主要用于更改一些参数:

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
/* Copy argv to cc_params, making the necessary edits. */

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 = ck_alloc((argc + 128) * sizeof(u8*));

name = strrchr(argv[0], '/');
if (!name) name = argv[0]; else name++;

if (!strncmp(name, "afl-clang", 9)) {

clang_mode = 1;

setenv(CLANG_ENV_VAR, "1", 1);

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 {

/* With GCJ and Eclipse installed, you can actually compile Java! The
instrumentation will work (amazingly). Alas, unhandled exceptions do
not call abort(), so afl-fuzz would need to be modified to equate
non-zero exit codes with crash conditions when working with Java
binaries. Meh. */

#ifdef __APPLE__

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

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) {
u8* cur = *(++argv);

if (!strncmp(cur, "-B", 2)) {

if (!be_quiet) WARNF("-B is already set, overriding");

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

}

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

if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory")) asan_set = 1;

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

cc_params[cc_par_cnt++] = cur;

}

cc_params[cc_par_cnt++] = "-B";
cc_params[cc_par_cnt++] = as_path;

if (clang_mode)
cc_params[cc_par_cnt++] = "-no-integrated-as";

if (getenv("AFL_HARDEN")) {

cc_params[cc_par_cnt++] = "-fstack-protector-all";

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

}

if (asan_set) {

/* Pass this on to afl-as to adjust map density. */

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__)

/* On 64-bit FreeBSD systems, clang -g -m32 is broken, but -m32 itself
works OK. This has nothing to do with us, but let's avoid triggering
that bug. */

if (!clang_mode || !m32_set)
cc_params[cc_par_cnt++] = "-g";

#else

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

#endif

cc_params[cc_par_cnt++] = "-O3";
cc_params[cc_par_cnt++] = "-funroll-loops";

/* Two indicators that you're building for fuzzing; one of them is
AFL-specific, the other is shared with libfuzzer. */

cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";

}

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_par_cnt] = NULL;

}
  • 首先为 cc_params 分配空间,大小为 (argc + 128) * sizeof(u8*)
  • 随后判断 argv[0] 是否包含 afl-clang,如果包含
    • 设置 CLANG_ENV_VAR 为 1
    • 判断 argv[0] 包含的是 afl-clang 还是 afl-clang++
      • 如果是 afl-clang ,则获取环境变量 AFL_CC
        • 没有环境变量,将 cc_params[0] 设为 clang
        • 否则设置为 AFL_CC
      • 如果是 afl-clang++ ,则获取环境变量 AFL_CXX
        • 没有环境变量,将 cc_params[0] 设为 clang++
        • 否则设置为 AFL_CXX
  • 如果不包含
    • 如果是 apple 平台,分别判断
      • 为 afl-g++ ,设置 cc_params[0] 为环境变量中的 AFL_CXX
      • 为 afl-gcj ,设置 cc_params[0] 为环境变量中的 AFL_GCJ
      • 设置 cc_params[0] 为环境变量中的 AFL_CC
      • 如果都不行,抛出错误
    • 否则
      • 为 afl-g++ ,设置 cc_params[0] 为环境变量中的 AFL_CXX 或 g++
      • 为 afl-gcj ,设置 cc_params[0] 为环境变量中的 AFL_GCJ 或 gcj
      • 设置 cc_params[0] 为环境变量中的 AFL_CC 或 gcc
  • 随后根据 argc 进行 argv 的遍历
    • 默认设置 -B ,且跟着的参数为 as 的路径
    • 忽略 -integrated-as 和 -pipe
    • 如果是 clang 模式,则设置 -no-integrated-as
    • 如果有环境变量 AFL_HARDEN ,则设置 -fstack-protector-all
      • 如果遍历的时候有 FORTIFY_SOURCE ,则变为 -D_FORTIFY_SOURCE=2
    • 如果遍历的时候有 -fsanitize=address 或 -fsanitize=memory
      • 设置环境变量 AFL_USE_ASAN 为 1
    • 否则,如果有设置环境变量 AFL_USE_ASAN
      • 判断是否有环境变量 AFL_USE_MSAN 和 FL_HARDEN ,有的话则抛出错误
      • 设置 -U_FORTIFY_SOURCE 和 -fsanitize=address
    • 否则,如果有设置环境变量 AFL_USE_MSAN
      • 判断是否有环境变量 AFL_USE_ASAN 和 FL_HARDEN ,有的话则抛出错误
      • 设置 -U_FORTIFY_SOURCE 和 -fsanitize=memory
  • 接下来判断是否有环境变量 AFL_DONT_OPTIMIZE ,如果没有的话
    • 判断是否为 64-bit FreeBSD 平台,如果是的话则 clang -g -m32 会导致错误,但是 -m32 却可以,为了修复该 bug 直接加 -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

execvp

最后的 execvp 则是将替换过后的执行运行,我们可以使用以下代码打印出来进行对比:

1
2
3
4
5
6
7
8
9
for(int i = 0; i < argc; i++){
printf("\t[*] argv[%d] : %s\n",i,argv[i]);
}

printf("\n");

for(int i = 0; i < sizeof(cc_params); i++){
printf("\t[*] cc_params[%d] : %s\n",i,cc_params[i]);
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
afl-cc 2.57b by <lcamtuf@google.com>
[*] argv[0] : afl-gcc
[*] argv[1] : test.c
[*] argv[2] : -o
[*] argv[3] : test
[*] cc_params[0] : gcc
[*] cc_params[1] : test.c
[*] cc_params[2] : -o
[*] cc_params[3] : test
[*] cc_params[4] : -B
[*] cc_params[5] : /usr/local/lib/afl
[*] cc_params[6] : -g
[*] cc_params[7] : -O3

可以看得出来 afl-gcc 的本质就是一层 wrapper