AFL源码分析(三)

本文最后更新于:2023年10月6日 晚上

AFL源码分析(三)

afl-fuzz.c

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
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
#ifndef AFL_LIB

/* Main entry point */

int main(int argc, char** argv) {

s32 opt;
u64 prev_queued = 0;
u32 sync_interval_cnt = 0, seek_to;
u8 *extras_dir = 0;
u8 mem_limit_given = 0;
u8 exit_1 = !!getenv("AFL_BENCH_JUST_ONE"); // 获取环境变量AFL_BENCH_JUST_ONE,并转换为相应的布尔值
char** use_argv;

struct timeval tv;
struct timezone tz;

SAYF(cCYA "afl-fuzz " cBRI VERSION cRST " by <lcamtuf@google.com>\n");

doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH;

gettimeofday(&tv, &tz);
srandom(tv.tv_sec ^ tv.tv_usec ^ getpid()); // 初始化随机种子

while ((opt = getopt(argc, argv, "+i:o:f:m:t:T:dnCB:S:M:x:Q")) > 0) // 循环处理命令参数

switch (opt) {

case 'i': /* input dir */

if (in_dir) FATAL("Multiple -i options not supported");
in_dir = optarg;

if (!strcmp(in_dir, "-")) in_place_resume = 1;

break;

case 'o': /* output dir */

if (out_dir) FATAL("Multiple -o options not supported");
out_dir = optarg;
break;

case 'M': { /* master sync ID */

u8* c;

if (sync_id) FATAL("Multiple -S or -M options not supported");
sync_id = ck_strdup(optarg);

if ((c = strchr(sync_id, ':'))) {

*c = 0;

if (sscanf(c + 1, "%u/%u", &master_id, &master_max) != 2 ||
!master_id || !master_max || master_id > master_max ||
master_max > 1000000) FATAL("Bogus master ID passed to -M");

}

force_deterministic = 1;

}

break;

case 'S':

if (sync_id) FATAL("Multiple -S or -M options not supported");
sync_id = ck_strdup(optarg);
break;

case 'f': /* target file */

if (out_file) FATAL("Multiple -f options not supported");
out_file = optarg;
break;

case 'x': /* dictionary */

if (extras_dir) FATAL("Multiple -x options not supported");
extras_dir = optarg;
break;

case 't': { /* timeout */

u8 suffix = 0;

if (timeout_given) FATAL("Multiple -t options not supported");

if (sscanf(optarg, "%u%c", &exec_tmout, &suffix) < 1 ||
optarg[0] == '-') FATAL("Bad syntax used for -t");

if (exec_tmout < 5) FATAL("Dangerously low value of -t");

if (suffix == '+') timeout_given = 2; else timeout_given = 1;

break;

}

case 'm': { /* mem limit */

u8 suffix = 'M';

if (mem_limit_given) FATAL("Multiple -m options not supported");
mem_limit_given = 1;

if (!strcmp(optarg, "none")) {

mem_limit = 0;
break;

}

if (sscanf(optarg, "%llu%c", &mem_limit, &suffix) < 1 ||
optarg[0] == '-') FATAL("Bad syntax used for -m");

switch (suffix) {

case 'T': mem_limit *= 1024 * 1024; break;
case 'G': mem_limit *= 1024; break;
case 'k': mem_limit /= 1024; break;
case 'M': break;

default: FATAL("Unsupported suffix or bad syntax for -m");

}

if (mem_limit < 5) FATAL("Dangerously low value of -m");

if (sizeof(rlim_t) == 4 && mem_limit > 2000)
FATAL("Value of -m out of range on 32-bit systems");

}

break;

case 'd': /* skip deterministic */

if (skip_deterministic) FATAL("Multiple -d options not supported");
skip_deterministic = 1;
use_splicing = 1;
break;

case 'B': /* load bitmap */

/* This is a secret undocumented option! It is useful if you find
an interesting test case during a normal fuzzing process, and want
to mutate it without rediscovering any of the test cases already
found during an earlier run.

To use this mode, you need to point -B to the fuzz_bitmap produced
by an earlier run for the exact same binary... and that's it.

I only used this once or twice to get variants of a particular
file, so I'm not making this an official setting. */

if (in_bitmap) FATAL("Multiple -B options not supported");

in_bitmap = optarg;
read_bitmap(in_bitmap);
break;

case 'C': /* crash mode */

if (crash_mode) FATAL("Multiple -C options not supported");
crash_mode = FAULT_CRASH;
break;

case 'n': /* dumb mode 简易模式 */

if (dumb_mode) FATAL("Multiple -n options not supported");
if (getenv("AFL_DUMB_FORKSRV")) dumb_mode = 2; else dumb_mode = 1;

break;

case 'T': /* banner */

if (use_banner) FATAL("Multiple -T options not supported");
use_banner = optarg;
break;

case 'Q': /* QEMU mode */

if (qemu_mode) FATAL("Multiple -Q options not supported");
qemu_mode = 1;

if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; // mem_limit = 200

break;

default:

usage(argv[0]); // 显示参数用法,并退出

}

if (optind == argc || !in_dir || !out_dir) usage(argv[0]);

setup_signal_handlers(); // 设置信号处理函数
check_asan_opts(); // 检测asan与msan模式

if (sync_id) fix_up_sync(); // 修正out_dir与sync_dir

if (!strcmp(in_dir, out_dir)) // 若in_dir与out_dir相同,报错并退出
FATAL("Input and output directories can't be the same");

if (dumb_mode) {

if (crash_mode) FATAL("-C and -n are mutually exclusive"); // 互斥
if (qemu_mode) FATAL("-Q and -n are mutually exclusive"); // 互斥

}

// 获取环境变量并设置对应值
if (getenv("AFL_NO_FORKSRV")) no_forkserver = 1;
if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1;
if (getenv("AFL_NO_ARITH")) no_arith = 1;
if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1;
if (getenv("AFL_FAST_CAL")) fast_cal = 1;

if (getenv("AFL_HANG_TMOUT")) { // 获取环境变量AFL_HANG_TMOUT,并设置hang_tmout
hang_tmout = atoi(getenv("AFL_HANG_TMOUT"));
if (!hang_tmout) FATAL("Invalid value of AFL_HANG_TMOUT");
}

if (dumb_mode == 2 && no_forkserver)
FATAL("AFL_DUMB_FORKSRV and AFL_NO_FORKSRV are mutually exclusive"); // 互斥

if (getenv("AFL_PRELOAD")) { // 获取环境变量AFL_PRELOAD,若不为空,则设置对应环境变量的值
setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1);
setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1);
}

if (getenv("AFL_LD_PRELOAD")) // 获取环境变量AFL_LD_PRELOAD,若不为空,报错并退出
FATAL("Use AFL_PRELOAD instead of AFL_LD_PRELOAD");

save_cmdline(argc, argv); // 保存argv变量到orig_cmdline

fix_up_banner(argv[optind]); // 获取-T参数指定的目标程序名称或者根据程序路径生成的程序名称

check_if_tty(); // 检测是否在tty终端上运行

get_core_count(); // 获取cpu核心数量

#ifdef HAVE_AFFINITY
bind_to_free_cpu(); // 获取空闲cpu
#endif /* HAVE_AFFINITY */

check_crash_handling(); // 检测core,确保core dump不会进入程序
check_cpu_governor(); // 检测cpu管理者

setup_post(); // 根据环境变量AFL_POST_LIBRARY,判断是否加载afl_postprocess函数
setup_shm(); // 设置共享内存trace_bits,初始化virgin_bits
init_count_class16(); // 初始化count_class_lookup16数组

setup_dirs_fds(); // 创建输出文件夹
read_testcases(); // 读取所有测试用例,并插入到路径queue中
load_auto(); // 自动加载生成的extra

pivot_inputs(); // 为测试用例在output文件夹中创建硬链接

if (extras_dir) load_extras(extras_dir); // 若extras_dir不为空,即指定-x参数,加载目录下的token到extras数组

if (!timeout_given) find_timeout(); // 若timeout_given为空,即未指定-t参数,设置exec_tmout

detect_file_args(argv + optind + 1); // 检测@@,并加载输入文件

if (!out_file) setup_stdio_file(); // 若out_file为空,即未指定-f参数,设置out_file

check_binary(argv[optind]); // 检测二进程程序是否合法

start_time = get_cur_time();

if (qemu_mode)
use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); // 获取qemu模式的参数
else
use_argv = argv + optind;

perform_dry_run(use_argv); // 核心函数,执行所有的测试用例,检测是否正常执行

cull_queue(); // 精简queue

show_init_stats(); // 显示初始状态信息

seek_to = find_start_position(); // 查找queue起始位置

write_stats_file(0, 0, 0); // 更新统计信息
save_auto(); // 自动保存自动生成的extra

if (stop_soon) goto stop_fuzzing; // 若stop_soon不为空,跳转到stop_fuzzing

/* Woop woop woop */

if (!not_on_tty) { // 若在tty终端
sleep(4);
start_time += 4000;
if (stop_soon) goto stop_fuzzing;
}

while (1) {

u8 skipped_fuzz;

cull_queue();

if (!queue_cur) { // 如果queue_cur为空,代表所有queue都被执行完一轮

queue_cycle++; // 代表所有queue被完整执行了多少轮
current_entry = 0;
cur_skipped_paths = 0;
queue_cur = queue; // 开始新一轮fuzz

while (seek_to) { // resume fuzz
current_entry++;
seek_to--;
queue_cur = queue_cur->next; // 从seek_to指定的queue项开始执行
}

show_stats(); // 显示stat信息

if (not_on_tty) {
ACTF("Entering queue cycle %llu.", queue_cycle);
fflush(stdout);
}

/* If we had a full queue cycle with no new finds, try
recombination strategies next. */

if (queued_paths == prev_queued) { // 代表在完整的一轮执行里都没有发现任何一个新的case

if (use_splicing) cycles_wo_finds++; else use_splicing = 1; // 设置use_splicing为1,代表接下来要通过splice重组queue里的case

} else cycles_wo_finds = 0;

prev_queued = queued_paths;

if (sync_id && queue_cycle == 1 && getenv("AFL_IMPORT_FIRST"))
sync_fuzzers(use_argv); // 读取其他sync文件夹下的queue,保存到主queue中

}

skipped_fuzz = fuzz_one(use_argv); // 对queue_cur进行测试,若不执行返回1,否则返回0

if (!stop_soon && sync_id && !skipped_fuzz) {

if (!(sync_interval_cnt++ % SYNC_INTERVAL))
sync_fuzzers(use_argv);

}

if (!stop_soon && exit_1) stop_soon = 2;

if (stop_soon) break; // 跳出死循环唯一途径

queue_cur = queue_cur->next;
current_entry++;

}

if (queue_cur) show_stats(); // 显示stat信息

/* If we stopped programmatically, we kill the forkserver and the current runner.
If we stopped manually, this is done by the signal handler. */
if (stop_soon == 2) {
if (child_pid > 0) kill(child_pid, SIGKILL);
if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL);
}
/* Now that we've killed the forkserver, we wait for it to be able to get rusage stats. */
if (waitpid(forksrv_pid, NULL, 0) <= 0) { // 等待forksrv结束
WARNF("error waitpid\n");
}

write_bitmap(); // 将virgin_bits信息,即发现路径写入out_dir/fuzz_bitmap文件
write_stats_file(0, 0, 0); // 更新统计信息
save_auto(); // 自动保存生成的extra

stop_fuzzing:

SAYF(CURSOR_SHOW cLRD "\n\n+++ Testing aborted %s +++\n" cRST,
stop_soon == 2 ? "programmatically" : "by user");

/* Running for more than 30 minutes but still doing first cycle? */

if (queue_cycle == 1 && get_cur_time() - start_time > 30 * 60 * 1000) {

SAYF("\n" cYEL "[!] " cRST
"Stopped during the first cycle, results may be incomplete.\n"
" (For info on resuming, see %s/README.)\n", doc_path);

}

fclose(plot_file);
destroy_queue(); // 销毁queue信息
destroy_extras(); // 销毁extras
ck_free(target_path);
ck_free(sync_id);

alloc_report();

OKF("We're done here. Have a nice day!\n");

exit(0);

}

#endif /* !AFL_LIB */

setup_signal_handlers

设置信号处理函数。

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
/* Set up signal handlers. More complicated that needs to be, because libc on
Solaris doesn't resume interrupted reads(), sets SA_RESETHAND when you call
siginterrupt(), and does other unnecessary things. */

EXP_ST void setup_signal_handlers(void) {

struct sigaction sa; // 创建sigaction结构体

sa.sa_handler = NULL;
sa.sa_flags = SA_RESTART;
sa.sa_sigaction = NULL;

sigemptyset(&sa.sa_mask); // 创建空的信号屏蔽字,即不屏蔽任何信息

/* Various ways of saying "stop". */

sa.sa_handler = handle_stop_sig; // 设置处理stop信号的句柄
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);

/* Exec timeout notifications. */

sa.sa_handler = handle_timeout; // 设置处理超时信号的句柄
sigaction(SIGALRM, &sa, NULL);

/* Window resize */

sa.sa_handler = handle_resize; // 设置处理窗口大小变化的句柄
sigaction(SIGWINCH, &sa, NULL);

/* SIGUSR1: skip entry */

sa.sa_handler = handle_skipreq; // 设置处理用户自定义信号的句柄
sigaction(SIGUSR1, &sa, NULL);

/* Things we don't care about. */

sa.sa_handler = SIG_IGN; // 不必关心的信号
sigaction(SIGTSTP, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);

}

check_asan_opts

检测asan与msan选项。

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
/* Check ASAN options. */

static void check_asan_opts(void) {
u8* x = getenv("ASAN_OPTIONS"); // 获取环境变量ASAN_OPTIONS

if (x) { // 检测环境变量的值,若遇到错误设置则退出

if (!strstr(x, "abort_on_error=1"))
FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!");

if (!strstr(x, "symbolize=0"))
FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!");

}

x = getenv("MSAN_OPTIONS"); // 获取环境变量MSAN_OPTIONS

if (x) { // // 检测环境变量的值,若遇到错误设置则退出

if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR)))
FATAL("Custom MSAN_OPTIONS set without exit_code="
STRINGIFY(MSAN_ERROR) " - please fix!");

if (!strstr(x, "symbolize=0"))
FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!");

}

}

fix_up_sync

修正out_dir与sync_dir。

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
/* Validate and fix up out_dir and sync_dir when using -S. */

static void fix_up_sync(void) {

u8* x = sync_id;

if (dumb_mode) // 若是简易模式,报错并退出
FATAL("-S / -M and -n are mutually exclusive");

if (skip_deterministic) { // 若设置skip_deterministic,报错并退出

if (force_deterministic)
FATAL("use -S instead of -M -d");
else
FATAL("-S already implies -d");

}

while (*x) { // 遍历sync_id,并检测格式是否正确

if (!isalnum(*x) && *x != '_' && *x != '-')
FATAL("Non-alphanumeric fuzzer ID specified via -S or -M");

x++;

}

if (strlen(sync_id) > 32) FATAL("Fuzzer ID too long"); // 若sync_id长度大于32,报错并退出

x = alloc_printf("%s/%s", out_dir, sync_id);

sync_dir = out_dir; // 设置sync_dir为out_dir
out_dir = x; // 设置out_dir为out_dir/sync_id的形式

if (!force_deterministic) { // 若force_deterministic为空,则设置skip_deterministic与use_splicing为1
skip_deterministic = 1;
use_splicing = 1;
}

}

save_cmdline

保存argv变量到orig_cmdline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void save_cmdline(u32 argc, char** argv) {

u32 len = 1, i;
u8* buf;

for (i = 0; i < argc; i++) // 遍历argv,获取参数总长度
len += strlen(argv[i]) + 1;

buf = orig_cmdline = ck_alloc(len); // orig_cmdline与buf分配空间

for (i = 0; i < argc; i++) { // 遍历argv,并将argv[i]复制到buf中,若不是最后一个参数,则添加' '分隔参数

u32 l = strlen(argv[i]);

memcpy(buf, argv[i], l);
buf += l;

if (i != argc - 1) *(buf++) = ' ';

}

*buf = 0; // 结尾赋值为空

}

fix_up_banner

获取-T参数指定的目标程序名称或者根据程序路径生成的程序名称。

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
/* Trim and possibly create a banner for the run. */

static void fix_up_banner(u8* name) {

if (!use_banner) { // 若use_banner为空,该值通过-T参数设置

if (sync_id) { // 若sync_id不为空,设置use_banner为sync_id

use_banner = sync_id;

} else { // 否则提取name最后一个'/'后的值,也即目标程序名作为use_banner

u8* trim = strrchr(name, '/');
if (!trim) use_banner = name; else use_banner = trim + 1;

}

}

if (strlen(use_banner) > 40) { // 若use_banner长度大于40,分配长度为44的空间存储

u8* tmp = ck_alloc(44);
sprintf(tmp, "%.40s...", use_banner);
use_banner = tmp;

}

}

check_if_tty

检测是否在tty终端上运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* Check if we're on TTY. */

static void check_if_tty(void) {

struct winsize ws;

if (getenv("AFL_NO_UI")) { // 获取环境变量AFL_NO_UI,若不为空,打印提示消息并设置not_on_tty=1,返回
OKF("Disabling the UI because AFL_NO_UI is set.");
not_on_tty = 1;
return;
}

if (ioctl(1, TIOCGWINSZ, &ws)) { // 动态获取窗口值大小

if (errno == ENOTTY) { // 若error == ENOTTY,打印提示消息并设置not_on_tty=1,返回
OKF("Looks like we're not running on a tty, so I'll be a bit less verbose.");
not_on_tty = 1;
}

return;
}

}

get_core_count

获取CPU核心数量。

bind_to_free_cpu

绑定空闲的cpu。

check_crash_handling

检测core,确保core dump不会进入程序。

check_cpu_governor

检测cpu管理者。

setup_post

若设置环境变量AFL_POST_LIBRARY,则加载函数afl_postprocess,后续common_fuzz_stuff会调用该函数

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
/* Load postprocessor, if available. */

static void setup_post(void) {

void* dh;
u8* fn = getenv("AFL_POST_LIBRARY"); // 获取环境变量AFL_POST_LIBRARY
u32 tlen = 6;

if (!fn) return; // 若fn为空,直接返回

ACTF("Loading postprocessor from '%s'...", fn); // 打印提示信息

dh = dlopen(fn, RTLD_NOW); // 打开动态链接库fn,并指定模式为RTLD_NOW
if (!dh) FATAL("%s", dlerror()); // 若打开失败,报错并退出

post_handler = dlsym(dh, "afl_postprocess"); // 加载afl_postprocess,并设置为post_handler,后续common_fuzz_stuff函数会调用post_handler
if (!post_handler) FATAL("Symbol 'afl_postprocess' not found.");

/* Do a quick test. It's better to segfault now than later =) */

post_handler("hello", &tlen); // afl_postprocess函数测试

OKF("Postprocessor installed successfully.");

}

setup_shm

设置共享内存trace_bits,初始化virgin_bits

Linux进程间通信(六):共享内存 shmget()、shmat()、shmdt()、shmctl()

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
/* Configure shared memory and virgin_bits. This is called at startup. */

EXP_ST void setup_shm(void) {

u8* shm_str;

if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE); // 若in_bitmap为空,初始化virgin_bits为0xff

memset(virgin_tmout, 255, MAP_SIZE); // 初始化virgin_tmout为0xff
memset(virgin_crash, 255, MAP_SIZE); // 初始化virgin_crash为0xff

shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600); // 创建MAP_size大小的共享内存,并返回共享id

if (shm_id < 0) PFATAL("shmget() failed");

atexit(remove_shm); // 注册程序终止函数,调用remove_shm函数删除共享内存

shm_str = alloc_printf("%d", shm_id); // 为shm_str分配空间

/* If somebody is asking us to fuzz instrumented binaries in dumb mode,
we don't want them to detect instrumentation, since we won't be sending
fork server commands. This should be replaced with better auto-detection
later on, perhaps? */

if (!dumb_mode) setenv(SHM_ENV_VAR, shm_str, 1); // 若dump_mode为空,设置shm_str为环境变量SHM_ENV_VAR
// __afl_maybe_log获取shm_id,从而获取到共享内存trace_bits,并设置对应值

ck_free(shm_str);

trace_bits = shmat(shm_id, NULL, 0); // 启动共享内存的访问,并链接到当前进程空间,赋值给trace_bits

if (trace_bits == (void *)-1) PFATAL("shmat() failed");

}

init_count_class16

初始化count_class_lookup16数组,加快trace_bits路径的统计速度。

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
static const u8 count_class_lookup8[256] = {    // 调整0~255之间的命中次数为指定值

[0] = 0,
[1] = 1,
[2] = 2,
[3] = 4,
[4 ... 7] = 8,
[8 ... 15] = 16,
[16 ... 31] = 32,
[32 ... 127] = 64,
[128 ... 255] = 128

};

static u16 count_class_lookup16[65536];


EXP_ST void init_count_class16(void) { // 将count_class_lookup8映射到count_class_lookup16中
// AFL后续统计命中次数时,一次性处理两个字节。所以有了count_class_lookup16,只是为了加快处理速度,提高处理效率

u32 b1, b2;

for (b1 = 0; b1 < 256; b1++)
for (b2 = 0; b2 < 256; b2++)
count_class_lookup16[(b1 << 8) + b2] =
(count_class_lookup8[b1] << 8) |
count_class_lookup8[b2];

}

setup_dirs_fds

为out_dir文件夹创建多个目录并保存打开文件的句柄。

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
/* Prepare output directories and fds. */

EXP_ST void setup_dirs_fds(void) {

u8* tmp;
s32 fd;

ACTF("Setting up output directories..."); // 设置输出目录

if (sync_id && mkdir(sync_dir, 0700) && errno != EEXIST) // 若sync_id不为空,创建目录sync_dir
PFATAL("Unable to create '%s'", sync_dir);

if (mkdir(out_dir, 0700)) { // 创建目录out_dir

if (errno != EEXIST) PFATAL("Unable to create '%s'", out_dir);

maybe_delete_out_dir(); // 删除out_dir目录的原来文件

} else {

if (in_place_resume) // 若in_place_resume不为空,报错并退出
FATAL("Resume attempted but old output directory not found");

out_dir_fd = open(out_dir, O_RDONLY); // 打开out_dir

#ifndef __sun

if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB)) // 若打开失败,或者flock创建互斥锁失败,报错并退出
PFATAL("Unable to flock() output directory.");

#endif /* !__sun */

}

/* Queue directory for any starting & discovered paths. */

tmp = alloc_printf("%s/queue", out_dir); // 创建目录out_dir/queue
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

/* Top-level directory for queue metadata used for session
resume and related tasks. */

tmp = alloc_printf("%s/queue/.state/", out_dir); // 创建目录out_dir/.state
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

/* Directory for flagging queue entries that went through
deterministic fuzzing in the past. */

tmp = alloc_printf("%s/queue/.state/deterministic_done/", out_dir); // 创建目录out_dir/queue/.state/deterministic_done
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

/* Directory with the auto-selected dictionary entries. */

tmp = alloc_printf("%s/queue/.state/auto_extras/", out_dir); // 创建out_dir/queue/.state/auto_extras
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

/* The set of paths currently deemed redundant. */

tmp = alloc_printf("%s/queue/.state/redundant_edges/", out_dir); // 创建out_dir/queue/.state/redundant_edges
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

/* The set of paths showing variable behavior. */

tmp = alloc_printf("%s/queue/.state/variable_behavior/", out_dir); // 创建out_dir/queue/.state/variable_behavior
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

/* Sync directory for keeping track of cooperating fuzzers. */

if (sync_id) { // 若sync_id不为空

tmp = alloc_printf("%s/.synced/", out_dir);

if (mkdir(tmp, 0700) && (!in_place_resume || errno != EEXIST)) // 创建out_dir/.synced
PFATAL("Unable to create '%s'", tmp);

ck_free(tmp);

}

/* All recorded crashes. */

tmp = alloc_printf("%s/crashes", out_dir);
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); // 创建out_dir/crashes文件夹
ck_free(tmp);

/* All recorded hangs. */

tmp = alloc_printf("%s/hangs", out_dir); // 创建out_dir/hangs文件夹
if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

/* Generally useful file descriptors. */

dev_null_fd = open("/dev/null", O_RDWR); // 以读写模式打开/dev/null文件
if (dev_null_fd < 0) PFATAL("Unable to open /dev/null");

dev_urandom_fd = open("/dev/urandom", O_RDONLY); // 以只读模式打开/dev/urandom
if (dev_urandom_fd < 0) PFATAL("Unable to open /dev/urandom");

/* Gnuplot output file. */

tmp = alloc_printf("%s/plot_data", out_dir);
fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); // 创建out_dir/plot_data
if (fd < 0) PFATAL("Unable to create '%s'", tmp);
ck_free(tmp);

plot_file = fdopen(fd, "w"); // 创建out_dir/plot_data的链接文件
if (!plot_file) PFATAL("fdopen() failed");

fprintf(plot_file, "# unix_time, cycles_done, cur_path, paths_total, "
"pending_total, pending_favs, map_size, unique_crashes, "
"unique_hangs, max_depth, execs_per_sec\n"); // 向plot_data写出头部信息
/* ignore errors */

}

read_testcases

读取所有的测试用例,并插入到queue中。

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
/* Read all testcases from the input directory, then queue them for testing.
Called at startup. */

static void read_testcases(void) {

struct dirent **nl;
s32 nl_cnt;
u32 i;
u8* fn;

/* Auto-detect non-in-place resumption attempts. */

fn = alloc_printf("%s/queue", in_dir);
if (!access(fn, F_OK)) in_dir = fn; else ck_free(fn); // 判断in_dir/queue是否存在,若存在,则in_dir = fn

ACTF("Scanning '%s'...", in_dir); // 使用scandir函数扫描in_dir文件夹

/* We use scandir() + alphasort() rather than readdir() because otherwise,
the ordering of test cases would vary somewhat randomly and would be
difficult to control. */

nl_cnt = scandir(in_dir, &nl, NULL, alphasort); // 返回文件个数,并将目录信息存放到nl中

if (nl_cnt < 0) {

if (errno == ENOENT || errno == ENOTDIR) // 若errno为ENOENT或者ENOTDIR, 打印报错信息

SAYF("\n" cLRD "[-] " cRST
"The input directory does not seem to be valid - try again. The fuzzer needs\n"
" one or more test case to start with - ideally, a small file under 1 kB\n"
" or so. The cases must be stored as regular files directly in the input\n"
" directory.\n");

PFATAL("Unable to open '%s'", in_dir); // 报错并退出

}

if (shuffle_queue && nl_cnt > 1) { // 若指定shuffle_queue,且文件个数大于1

ACTF("Shuffling queue...");
shuffle_ptrs((void**)nl, nl_cnt); // 打乱文件序数

}

// 以in_dir/testcase文件为例
for (i = 0; i < nl_cnt; i++) { // 遍历nl数组中的每项

struct stat st;

u8* fn = alloc_printf("%s/%s", in_dir, nl[i]->d_name); // in_dir/testcase
u8* dfn = alloc_printf("%s/.state/deterministic_done/%s", in_dir, nl[i]->d_name); // in_dir/.state/deterministic_done/testcase

u8 passed_det = 0;

free(nl[i]); /* not tracked */

if (lstat(fn, &st) || access(fn, R_OK)) // 将文件信息保存到st结构体中,检测文件是否存在,不存在则报错
PFATAL("Unable to access '%s'", fn);

/* This also takes care of . and .. */

if (!S_ISREG(st.st_mode) || !st.st_size || strstr(fn, "/README.txt")) { // 去除干扰文件.或者..或者/README.txt

ck_free(fn);
ck_free(dfn);
continue;

}

if (st.st_size > MAX_FILE) // 若testcase大于MAX_FILE(1M),报错并退出
FATAL("Test case '%s' is too big (%s, limit is %s)", fn,
DMS(st.st_size), DMS(MAX_FILE));

/* Check for metadata that indicates that deterministic fuzzing
is complete for this entry. We don't want to repeat deterministic
fuzzing when resuming aborted scans, because it would be pointless
and probably very time-consuming. */

if (!access(dfn, F_OK)) passed_det = 1; // 若dfn文件存在,则设置passed_det为1,表示经过了deterministic fuzzing
ck_free(dfn);

add_to_queue(fn, st.st_size, passed_det); // 将testcase添加到queue中

}

free(nl); /* not tracked */

if (!queued_paths) { // 若queue_paths为空,即没有入队的文件,报错并退出

SAYF("\n" cLRD "[-] " cRST
"Looks like there are no valid test cases in the input directory! The fuzzer\n"
" needs one or more test case to start with - ideally, a small file under\n"
" 1 kB or so. The cases must be stored as regular files directly in the\n"
" input directory.\n");

FATAL("No usable test cases in '%s'", in_dir);

}

last_path_time = 0; // 设置last_path_time为0
queued_at_start = queued_paths; // 设置queued_at_start为queued_paths

}

add_to_queue

queue中链接新的queue。

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
/* Append new test case to the queue. */

static void add_to_queue(u8* fname, u32 len, u8 passed_det) {

struct queue_entry* q = ck_alloc(sizeof(struct queue_entry)); // 创建queue_entry结构体

q->fname = fname; // 为q设置一系列初始值
q->len = len;
q->depth = cur_depth + 1;
q->passed_det = passed_det;

if (q->depth > max_depth) max_depth = q->depth; // 若q->depth > max_depth,设置max_depth为q->depth,即最大queue个数

if (queue_top) { // 若queue_top不为空,插入q,并把queue_top置为q

queue_top->next = q;
queue_top = q;

} else q_prev100 = queue = queue_top = q; // 否则设置q_prev100 = queue = queue_top = q

queued_paths++; // queued_paths + 1,测试的路径+1
pending_not_fuzzed++; // pending_not_fuzzed + 1

cycles_wo_finds = 0;

if (!(queued_paths % 100)) { // 若queue数量是100的倍数,使用q_prev100连接queue

q_prev100->next_100 = q;
q_prev100 = q;

}

last_path_time = get_cur_time(); // 设置last_path_time为当前时间

}

load_auto

load自动生成的字典token

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
/* Load automatically generated extras. */

static void load_auto(void) {

u32 i;

for (i = 0; i < USE_AUTO_EXTRAS; i++) { // i < 50

u8 tmp[MAX_AUTO_EXTRA + 1]; // tmp[32 + 1]
u8* fn = alloc_printf("%s/.state/auto_extras/auto_%06u", in_dir, i);
s32 fd, len;

fd = open(fn, O_RDONLY, 0600); // 以读写权限打开in_dir/.state/auto_extras/auto_i

if (fd < 0) { // 打开失败,报错并返回

if (errno != ENOENT) PFATAL("Unable to open '%s'", fn);
ck_free(fn);
break;

}

/* We read one byte more to cheaply detect tokens that are too
long (and skip them). */

len = read(fd, tmp, MAX_AUTO_EXTRA + 1); // 读取文件内容到tmp中

if (len < 0) PFATAL("Unable to read from '%s'", fn); // 若len < 0,报错

if (len >= MIN_AUTO_EXTRA && len <= MAX_AUTO_EXTRA) // 若3 <= len <= 32,调用maybe_add_auto函数
maybe_add_auto(tmp, len);

close(fd);
ck_free(fn);

}

if (i) OKF("Loaded %u auto-discovered dictionary tokens.", i); // 打印信息
else OKF("No auto-generated dictionary tokens to reuse.");

}

maybe_add_auto

判断是否添加新的a_extras数组项。

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
/* Maybe add automatic extra. */

static void maybe_add_auto(u8* mem, u32 len) {

u32 i;

/* Allow users to specify that they don't want auto dictionaries. */

if (!MAX_AUTO_EXTRAS || !USE_AUTO_EXTRAS) return; // 若MAX_AUTO_EXTRAS或者USE_AUTO_EXTRAS为空,直接返回

/* Skip runs of identical bytes. */

for (i = 1; i < len; i++) // 循环遍历mem,若mem[0] ^ mem[i]不同,结束循环
if (mem[0] ^ mem[i]) break;

if (i == len) return; // 若i == len,即mem中所有字节都相同,则直接返回

/* Reject builtin interesting values. */

if (len == 2) { // 若len为2

i = sizeof(interesting_16) >> 1;

while (i--) // 循环遍历interesting_16数组,若找到与mem相同的值,直接返回
if (*((u16*)mem) == interesting_16[i] ||
*((u16*)mem) == SWAP16(interesting_16[i])) return;

}

if (len == 4) { // 若len为4

i = sizeof(interesting_32) >> 2;

while (i--) // 循环遍历interesting_32数组,若找到与mem相同的值,直接返回
if (*((u32*)mem) == interesting_32[i] ||
*((u32*)mem) == SWAP32(interesting_32[i])) return;

}

/* Reject anything that matches existing extras. Do a case-insensitive
match. We optimize by exploiting the fact that extras[] are sorted
by size. */

for (i = 0; i < extras_cnt; i++) // extras_cnt保存的顺序是从小到大的,依次遍历extras数组,找到第一个大于等于len的索引
if (extras[i].len >= len) break;

for (; i < extras_cnt && extras[i].len == len; i++) // 与extras数组中已经存在的值比较,忽略大小写,若存在相同的值,直接返回
if (!memcmp_nocase(extras[i].data, mem, len)) return;

/* Last but not least, check a_extras[] for matches. There are no
guarantees of a particular sort order. */

auto_changed = 1;

for (i = 0; i < a_extras_cnt; i++) { // 遍历整个a_extras数组

if (a_extras[i].len == len && !memcmp_nocase(a_extras[i].data, mem, len)) { // 忽略大小写比较mem,若相同,a_extras++,跳转到sort_a_extras

a_extras[i].hit_cnt++;
goto sort_a_extras;

}

}

/* At this point, looks like we're dealing with a new entry. So, let's
append it if we have room. Otherwise, let's randomly evict some other
entry from the bottom half of the list. */

// 发现了一个新的entry
if (a_extras_cnt < MAX_AUTO_EXTRAS) { // 若a_extras_cnt < 500

a_extras = ck_realloc_block(a_extras, (a_extras_cnt + 1) *
sizeof(struct extra_data)); // 为发现的new extra分配空间

a_extras[a_extras_cnt].data = ck_memdup(mem, len);
a_extras[a_extras_cnt].len = len;
a_extras_cnt++;

} else { // 否则随机覆盖一个a_extra

i = MAX_AUTO_EXTRAS / 2 +
UR((MAX_AUTO_EXTRAS + 1) / 2);

ck_free(a_extras[i].data);

a_extras[i].data = ck_memdup(mem, len);
a_extras[i].len = len;
a_extras[i].hit_cnt = 0;

}

sort_a_extras:

/* First, sort all auto extras by use count, descending order. */

qsort(a_extras, a_extras_cnt, sizeof(struct extra_data),
compare_extras_use_d); // 快排,按照使用次数,降序排列

/* Then, sort the top USE_AUTO_EXTRAS entries by size. */

qsort(a_extras, MIN(USE_AUTO_EXTRAS, a_extras_cnt),
sizeof(struct extra_data), compare_extras_len); // 按size进行排序

}

pivot_inputs

为测试用例在output文件夹中创建硬链接。

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
/* Create hard links for input test cases in the output directory, choosing
good names and pivoting accordingly. */

static void pivot_inputs(void) {

struct queue_entry* q = queue;
u32 id = 0;

ACTF("Creating hard links for all input files...");

/*
testcase -> id:000000,orig:testcase
*/
while (q) { // 遍历queue数组

u8 *nfn, *rsl = strrchr(q->fname, '/'); // 获取q->fname最后一个'/'后的字符串
u32 orig_id;

if (!rsl) rsl = q->fname; else rsl++; // 若rsl为空,则直接设置rsl为q->fname,否则忽略掉'/'

/* If the original file name conforms to the syntax and the recorded
ID matches the one we'd assign, just use the original file name.
This is valuable for resuming fuzzing runs. */

#ifndef SIMPLE_FILES
# define CASE_PREFIX "id:" // 定义前缀为"id:"
#else
# define CASE_PREFIX "id_"
#endif /* ^!SIMPLE_FILES */

if (!strncmp(rsl, CASE_PREFIX, 3) &&
sscanf(rsl + 3, "%06u", &orig_id) == 1 && orig_id == id) { // 若rsl存在前缀,将id:后的序号赋给orig_id,判断orig_id == id

u8* src_str;
u32 src_id;

resuming_fuzz = 1; // 设置resuming_fuzz = 1,表示重启的fuzz
nfn = alloc_printf("%s/queue/%s", out_dir, rsl); // nfn = out_dir/queue/rsl

/* Since we're at it, let's also try to find parent and figure out the
appropriate depth for this entry. */

src_str = strchr(rsl + 3, ':'); // 查找字符':'


if (src_str && sscanf(src_str + 1, "%06u", &src_id) == 1) { // 将rsl序号赋值给src_id,串联起queue

struct queue_entry* s = queue;
while (src_id-- && s) s = s->next;
if (s) q->depth = s->depth + 1;

if (max_depth < q->depth) max_depth = q->depth;

}

} else {

/* No dice - invent a new name, capturing the original one as a
substring. */

#ifndef SIMPLE_FILES

u8* use_name = strstr(rsl, ",orig:"); // 在rsl中查找,orig:

if (use_name) use_name += 6; else use_name = rsl; // 若存在,设置use_name为use_name+6, 否则直接设置为rsl
nfn = alloc_printf("%s/queue/id:%06u,orig:%s", out_dir, id, use_name); // out_dir/queue/id:id,orig:use_name

#else

nfn = alloc_printf("%s/queue/id_%06u", out_dir, id);

#endif /* ^!SIMPLE_FILES */

}

/* Pivot to the new queue entry. */

link_or_copy(q->fname, nfn); // 创建q-fname文件的硬链接
ck_free(q->fname);
q->fname = nfn;

/* Make sure that the passed_det value carries over, too. */

if (q->passed_det) mark_as_det_done(q); // 若q->passed_det不为空,标记queue已经fuzz过了,并保持q->passed_det为1

q = q->next;
id++;

}

if (in_place_resume) nuke_resume_dir(); // 若in_placce_resume不为空,调用nuke_resume_dir删除out_dir/_resume目录下所有内容,
// 若有一个删除失败,报错并退出

}


#ifndef SIMPLE_FILES

load_extras

若extras_dir不为空(通过-x参数指定),加载目录下的token到extras数组,并按照大小排序。

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
/* Read extras from the extras directory and sort them by size. */

static void load_extras(u8* dir) {

DIR* d;
struct dirent* de;
u32 min_len = MAX_DICT_FILE, max_len = 0, dict_level = 0;
u8* x;

/* If the name ends with @, extract level and continue. */

if ((x = strchr(dir, '@'))) { // 检测dir中是否存在@

*x = 0; // 将@替换为空字节
dict_level = atoi(x + 1); // 将@后的数字转换为int型

}

ACTF("Loading extra dictionary from '%s' (level %u)...", dir, dict_level);

d = opendir(dir); // 打开dir文件夹

if (!d) { // 若打开失败

if (errno == ENOTDIR) { // 若报错信息为ENOTDIR,表示不是一个目录
load_extras_file(dir, &min_len, &max_len, dict_level); // 打开dir文件
goto check_and_sort; // 跳转到check_and_sort
}

PFATAL("Unable to open '%s'", dir);

}

if (x) FATAL("Dictionary levels not supported for directories.");

while ((de = readdir(d))) { // 循环遍历dir目录

struct stat st;
u8* fn = alloc_printf("%s/%s", dir, de->d_name);
s32 fd;

if (lstat(fn, &st) || access(fn, R_OK)) // 判断文件是否存在,将文件信息保存到stat结构体中
PFATAL("Unable to access '%s'", fn);

/* This also takes care of . and .. */
if (!S_ISREG(st.st_mode) || !st.st_size) { //

ck_free(fn);
continue;

}

if (st.st_size > MAX_DICT_FILE) // 若文件大小大于MAX_DICT_FILE,报错并退出
FATAL("Extra '%s' is too big (%s, limit is %s)", fn,
DMS(st.st_size), DMS(MAX_DICT_FILE));

if (min_len > st.st_size) min_len = st.st_size; // 根据文件大小设置min_len与max_Len
if (max_len < st.st_size) max_len = st.st_size;

extras = ck_realloc_block(extras, (extras_cnt + 1) *
sizeof(struct extra_data)); // 为extras重分配空间

extras[extras_cnt].data = ck_alloc(st.st_size); // 为extra.data分配空间
extras[extras_cnt].len = st.st_size;

fd = open(fn, O_RDONLY); // 打开文件

if (fd < 0) PFATAL("Unable to open '%s'", fn); // 若打开失败,退出并返回

ck_read(fd, extras[extras_cnt].data, st.st_size, fn); // 读取文件内容到extras数组中

close(fd); // 关闭文件
ck_free(fn);

extras_cnt++; // 继续遍历下一个文件

}

closedir(d); // 关闭目录

check_and_sort:

if (!extras_cnt) FATAL("No usable files in '%s'", dir);

qsort(extras, extras_cnt, sizeof(struct extra_data), compare_extras_len); // 将extras按照len排序

OKF("Loaded %u extra tokens, size range %s to %s.", extras_cnt,
DMS(min_len), DMS(max_len));

if (max_len > 32)
WARNF("Some tokens are relatively large (%s) - consider trimming.",
DMS(max_len));

if (extras_cnt > MAX_DET_EXTRAS)
WARNF("More than %u tokens - will use them probabilistically.",
MAX_DET_EXTRAS);

}

find_timeout

timeout_given(通过-t参数指定)不为空,设置exec_tmout为fuzzer_stats文件中的exec_tmout

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
/* The same, but for timeouts. The idea is that when resuming sessions without
-t given, we don't want to keep auto-scaling the timeout over and over
again to prevent it from growing due to random flukes. */

static void find_timeout(void) {

static u8 tmp[4096]; /* Ought to be enough for anybody. */

u8 *fn, *off;
s32 fd, i;
u32 ret;

if (!resuming_fuzz) return; // 若resuming_fuzz为空,直接返回

// in_place_resume通过-i参数设置
if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir); // 若in_place_resume不为空,out_dir/fuzzer_stats
else fn = alloc_printf("%s/../fuzzer_stats", in_dir); // in_dir/../fuzzer_stats

fd = open(fn, O_RDONLY); // 打开fn文件
ck_free(fn);

if (fd < 0) return;

i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */
/*这个操作的目的是为了防止编译器发出未使用变量 i 的警告。
因为代码中没有进一步使用 i,所以可以使用 (void) 来明确
告诉编译器我们不打算使用它的值。*/
close(fd);

off = strstr(tmp, "exec_timeout : "); // 提取fuzzer_stats文件中的exex_timeout作为exec_timeout
if (!off) return;

ret = atoi(off + 20);
if (ret <= 4) return;

exec_tmout = ret;
timeout_given = 3;

}

detect_file_args

检测argv中是否存在@@,替换成out_dir/.cur_input。其中out_file参数可通过命令行参数-f设置。

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
/* Detect @@ in args. */

EXP_ST void detect_file_args(char** argv) {

u32 i = 0;
u8* cwd = getcwd(NULL, 0); // 获取当前文件路径

if (!cwd) PFATAL("getcwd() failed");

while (argv[i]) { // 遍历argv参数

u8* aa_loc = strstr(argv[i], "@@"); // 查找参数是否包含@@

if (aa_loc) { // 若包含@@,则

u8 *aa_subst, *n_arg;

/* If we don't have a file name chosen yet, use a safe default. */

if (!out_file) // 通过命令行参数-f设置
out_file = alloc_printf("%s/.cur_input", out_dir); // out_dir/.cur_input

/* Be sure that we're always using fully-qualified paths. */

if (out_file[0] == '/') aa_subst = out_file; // 判断路径是否以"/"开始,猜测判断是否为绝对路径
else aa_subst = alloc_printf("%s/%s", cwd, out_file);

/* Construct a replacement argv value. */

*aa_loc = 0;
n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); // 替换argv中@@为.cur_input文件
argv[i] = n_arg;
*aa_loc = '@';

if (out_file[0] != '/') ck_free(aa_subst);

}

i++;

}

free(cwd); /* not tracked */

}

setup_stdio_file

若没有设置-f命令行参数,则创建一个新的out_dir/.cur_input文件并打开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Setup the output file for fuzzed data, if not using -f. */

EXP_ST void setup_stdio_file(void) {

u8* fn = alloc_printf("%s/.cur_input", out_dir); // out_dir/.cur_input

unlink(fn); /* Ignore errors */ // 删除原来的out_dir/.cur_input

out_fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600); // 打开out_dir/.cur_input

if (out_fd < 0) PFATAL("Unable to create '%s'", fn);

ck_free(fn);

}

check_binary

检测指定路径的二进制文件是否存在,是否为shell脚本,是否是elf文件格式,是否被插桩

perform_dry_run

执行所有的测试用例,检测是否正常执行。

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
/* Perform dry run of all test cases to confirm that the app is working as
expected. This is done only for the initial inputs, and only once. */

static void perform_dry_run(char** argv) {

struct queue_entry* q = queue;
u32 cal_failures = 0;
u8* skip_crashes = getenv("AFL_SKIP_CRASHES"); // 获取环境变量AFL_SKIP_CRASHES

while (q) { // 遍历queue,以测试用例out_dir/id:000000,orig:testcase为例

u8* use_mem;
u8 res;
s32 fd;

u8* fn = strrchr(q->fname, '/') + 1; // 获取测试用例的名称, id:000000,orig:testcase

ACTF("Attempting dry run with '%s'...", fn); // 尝试直接运行测试用例

fd = open(q->fname, O_RDONLY); // 以只读模式打开q->fname文件,即out_dir/id:000000,orig:testcase
if (fd < 0) PFATAL("Unable to open '%s'", q->fname);

use_mem = ck_alloc_nozero(q->len); // 分配q->len长度的空间作为use_mem

if (read(fd, use_mem, q->len) != q->len) // 读取测试用例内容到use_mem
FATAL("Short read from '%s'", q->fname);

close(fd);

res = calibrate_case(argv, q, use_mem, 0, 1); // 评估测试用例,并返回res
ck_free(use_mem);

if (stop_soon) return; // 若stop_soon不为空,直接返回

if (res == crash_mode || res == FAULT_NOBITS) // 若res为crash_mode(-C设置)或者res为FAULT_NOBITS,打印执行信息
SAYF(cGRA " len = %u, map size = %u, exec speed = %llu us\n" cRST,
q->len, q->bitmap_size, q->exec_us);

switch (res) { // 捕获其它FAULT类型

case FAULT_NONE:

if (q == queue) check_map_coverage(); // 若q是第一个测试用例,调用check_map_coverage,评估覆盖率

if (crash_mode) FATAL("Test case '%s' does *NOT* crash", fn); // 若crash_mode不为空,报错并退出

break;

case FAULT_TMOUT:

if (timeout_given) { // 若timeout_given不为空

/* The -t nn+ syntax in the command line sets timeout_given to '2' and
instructs afl-fuzz to tolerate but skip queue entries that time
out. */

if (timeout_given > 1) { // 若timeout_given大于1,提示警告信息,并退出switch
WARNF("Test case results in a timeout (skipping)");
q->cal_failed = CAL_CHANCES;
cal_failures++;
break;
}

SAYF("\n" cLRD "[-] " cRST
"The program took more than %u ms to process one of the initial test cases.\n"
" Usually, the right thing to do is to relax the -t option - or to delete it\n"
" altogether and allow the fuzzer to auto-calibrate. That said, if you know\n"
" what you are doing and want to simply skip the unruly test cases, append\n"
" '+' at the end of the value passed to -t ('-t %u+').\n", exec_tmout,
exec_tmout);

FATAL("Test case '%s' results in a timeout", fn);

} else {

SAYF("\n" cLRD "[-] " cRST
"The program took more than %u ms to process one of the initial test cases.\n"
" This is bad news; raising the limit with the -t option is possible, but\n"
" will probably make the fuzzing process extremely slow.\n\n"

" If this test case is just a fluke, the other option is to just avoid it\n"
" altogether, and find one that is less of a CPU hog.\n", exec_tmout);

FATAL("Test case '%s' results in a timeout", fn);

}

case FAULT_CRASH:

if (crash_mode) break; // 若crash_mode不为空,直接退出switch

if (skip_crashes) { // 若skip_crashes不为空,打印警告信息并退出switch
WARNF("Test case results in a crash (skipping)");
q->cal_failed = CAL_CHANCES;
cal_failures++;
break;
}

if (mem_limit) { // 若mem_limit不为空,即设置了内存限制,打印报错消息并退出

SAYF("\n" cLRD "[-] " cRST
"Oops, the program crashed with one of the test cases provided. There are\n"
" several possible explanations:\n\n"

" - The test case causes known crashes under normal working conditions. If\n"
" so, please remove it. The fuzzer should be seeded with interesting\n"
" inputs - but not ones that cause an outright crash.\n\n"

" - The current memory limit (%s) is too low for this program, causing\n"
" it to die due to OOM when parsing valid files. To fix this, try\n"
" bumping it up with the -m setting in the command line. If in doubt,\n"
" try something along the lines of:\n\n"

#ifdef RLIMIT_AS
" ( ulimit -Sv $[%llu << 10]; /path/to/binary [...] <testcase )\n\n"
#else
" ( ulimit -Sd $[%llu << 10]; /path/to/binary [...] <testcase )\n\n"
#endif /* ^RLIMIT_AS */

" Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
" estimate the required amount of virtual memory for the binary. Also,\n"
" if you are using ASAN, see %s/notes_for_asan.txt.\n\n"

#ifdef __APPLE__

" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" binaries. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

" - Least likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
DMS(mem_limit << 20), mem_limit - 1, doc_path);

} else {

SAYF("\n" cLRD "[-] " cRST
"Oops, the program crashed with one of the test cases provided. There are\n"
" several possible explanations:\n\n"

" - The test case causes known crashes under normal working conditions. If\n"
" so, please remove it. The fuzzer should be seeded with interesting\n"
" inputs - but not ones that cause an outright crash.\n\n"

#ifdef __APPLE__

" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" binaries. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

" - Least likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");

}

FATAL("Test case '%s' results in a crash", fn);

case FAULT_ERROR: // 执行target程序失败

FATAL("Unable to execute target application ('%s')", argv[0]);

case FAULT_NOINST: // 未检测到插桩

FATAL("No instrumentation detected");

case FAULT_NOBITS: // 如果这个样例有出现路径信息,但是没有任何新路径,抛出警告
// 认为这是无用路径。useless_at_start计数器加一

useless_at_start++;

if (!in_bitmap && !shuffle_queue)
WARNF("No new instrumentation output, test case may be useless.");

break;

}
// 若样例q的var_behavior为真,则代表它多次运行,同样的输入条件下,却出现不同的覆盖信息。
if (q->var_behavior) WARNF("Instrumentation output varies across runs.");

q = q->next;

}

if (cal_failures) { // 若cal_failures不为空

if (cal_failures == queued_paths) // 若cal_failures等于queued_paths,报错并推出
FATAL("All test cases time out%s, giving up!",
skip_crashes ? " or crash" : "");

WARNF("Skipped %u test cases (%0.02f%%) due to timeouts%s.", cal_failures,
((double)cal_failures) * 100 / queued_paths,
skip_crashes ? " or crashes" : ""); // 打印警告信息

if (cal_failures * 5 > queued_paths)
WARNF(cLRD "High percentage of rejected test cases, check settings!");

}

OKF("All test cases processed.");

}

calibrate_case

评估input文件夹下的case,来发现这些testcase的行为是否异常;以及在发现新的路径时,用以评估这个新发现的testcase的行为是否可变(这里的可变是指多次执行这个case,发现的路径不同)。

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
/* Calibrate a new test case. This is done when processing the input directory
to warn about flaky or otherwise problematic test cases early on; and when
new paths are discovered to detect variable behavior and so on. */

static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem,
u32 handicap, u8 from_queue) {

static u8 first_trace[MAP_SIZE];

u8 fault = 0, new_bits = 0, var_detected = 0, hnb = 0,
first_run = (q->exec_cksum == 0); /* 如果q->exec_cksum为0,代表这是这个case第一次运行,
即来自input文件夹下,将first_run置为1. */

u64 start_us, stop_us;

s32 old_sc = stage_cur, old_sm = stage_max;
u32 use_tmout = exec_tmout;
u8* old_sn = stage_name;

/* Be a bit more generous about timeouts when resuming sessions, or when
trying to calibrate already-added finds. This helps avoid trouble due
to intermittent latency. */

/* 如果from_queue是0或者resuming_fuzz被置为1,即代表不来自于queue中或者
在resuming sessions的时候,则use_tmout的值被设置的更大。*/

if (!from_queue || resuming_fuzz)
use_tmout = MAX(exec_tmout + CAL_TMOUT_ADD,
exec_tmout * CAL_TMOUT_PERC / 100);

q->cal_failed++;

stage_name = "calibration";
stage_max = fast_cal ? 3 : CAL_CYCLES; /* 每个新测试用例(以及显示出可变行为的测试用例)
的校准周期数,也就是说这个stage要执行几次的意思 */

/* Make sure the forkserver is up before we do anything, and let's not
count its spin-up time toward binary calibration. */

if (dumb_mode != 1 && !no_forkserver && !forksrv_pid)
init_forkserver(argv); // 启动forkserver

if (q->exec_cksum) { // new case
// 如果这个queue不是来自input文件夹,而是评估新case,则此时q->exec_cksum不空

memcpy(first_trace, trace_bits, MAP_SIZE); // 拷贝trace_bits到first_trace
hnb = has_new_bits(virgin_bits); // 调用has_new_bits检测新路径或者路径执行次数
if (hnb > new_bits) new_bits = hnb;

}

start_us = get_cur_time_us();

for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {

u32 cksum;

if (!first_run && !(stage_cur % stats_update_freq)) show_stats(); // 不是第一次运行的情况下,且当前执行轮次是stats_update_freq的整数倍
// 调用show_stats刷新一次展示界面

write_to_testcase(use_mem, q->len); // 将从q->fname中读取的内容写入到.cur_input中

fault = run_target(argv, use_tmout); // 执行目标程序

/* stop_soon is set by the handler for Ctrl+C. When it's pressed,
we want to bail out quickly. */

if (stop_soon || fault != crash_mode) goto abort_calibration; // 若stop_soon不为空,或者fault不为crash_modw,跳转

/* 如果 calibration stage第一次运行,且不在dumb_mode,共享内存中没有任何路径 */
if (!dumb_mode && !stage_cur
&& !count_bytes(trace_bits)) { // 计算共享内存里有多少字节被置位
fault = FAULT_NOINST;
goto abort_calibration;
}

cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); // 计算路径hash值

// 不等于exec_cksum,表示第一次运行,或在相同参数下,每次执行,cksum不同,表示是一个路径可变的queue
if (q->exec_cksum != cksum) {

hnb = has_new_bits(virgin_bits);
if (hnb > new_bits) new_bits = hnb;

if (q->exec_cksum) { // 判断是否为可变queue

u32 i;

for (i = 0; i < MAP_SIZE; i++) {

if (!var_bytes[i] && first_trace[i] != trace_bits[i]) { // 如果first_trace[i]不等于trace_bits[i]
// 代表发现了可变queue

var_bytes[i] = 1;
stage_max = CAL_CYCLES_LONG;

}

}

var_detected = 1;

} else { // 代表第一次执行

q->exec_cksum = cksum;
memcpy(first_trace, trace_bits, MAP_SIZE);

}

}

}

stop_us = get_cur_time_us();

total_cal_us += stop_us - start_us; // 计算评估stage_max轮的时间
total_cal_cycles += stage_max;

/* OK, let's collect some stats about the performance of this test case.
This is used for fuzzing air time calculations in calculate_score(). */

q->exec_us = (stop_us - start_us) / stage_max; // 设置每轮平均执行时间
q->bitmap_size = count_bytes(trace_bits); // 执行queue覆盖的路径数
q->handicap = handicap;
q->cal_failed = 0;

total_bitmap_size += q->bitmap_size; // queue所覆盖到的路径总数
total_bitmap_entries++;

update_bitmap_score(q); // 每当我们发现一个新的路径,都会调用这个函数来判断其是不是更加地favorable

/* If this case didn't result in new output from the instrumentation, tell
parent. This is a non-critical problem, but something to warn the user
about. */

if (!dumb_mode && first_run && !fault && !new_bits) fault = FAULT_NOBITS;

abort_calibration:

if (new_bits == 2 && !q->has_new_cov) {
q->has_new_cov = 1;
queued_with_cov++; // 代表发现了新路径
}

/* Mark variable paths. */
// 标记为可变路径
if (var_detected) { // 若var_detected不为空

var_byte_count = count_bytes(var_bytes); // 计算总路径数

if (!q->var_behavior) {
mark_as_variable(q);
queued_variable++;
}

}

stage_name = old_sn; // 恢复stage信息
stage_cur = old_sc;
stage_max = old_sm;

if (!first_run) show_stats(); // 若不是第一次运行,调用show_stats

return fault; // 返回错误信息

}

init_forkserver

fork子进程作为fork server,创建管道用于进程间通信,后续执行目标程序都是再子进程的基础上fork。

Linux 的进程间通信:管道

进程间通信管道进阶篇:linux下dup/dup2函数的用法

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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/* Spin up fork server (instrumented mode only). The idea is explained here:

http://lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html

In essence, the instrumentation allows us to skip execve(), and just keep
cloning a stopped child. So, we just execute once, and then send commands
through a pipe. The other part of this logic is in afl-as.h. */

EXP_ST void init_forkserver(char** argv) {

static struct itimerval it;
int st_pipe[2], ctl_pipe[2];
int status;
s32 rlen;

ACTF("Spinning up the fork server...");

if (pipe(st_pipe) || pipe(ctl_pipe)) PFATAL("pipe() failed"); // 创建两个匿名管道

forksrv_pid = fork(); // fork出子进程,返回值为子进程的pid

if (forksrv_pid < 0) PFATAL("fork() failed");

if (!forksrv_pid) { /* Child Process */

// 针对OpenBSD的特殊处理

struct rlimit r;

/* Umpf. On OpenBSD, the default fd limit for root users is set to
soft 128. Let's try to fix that... */

if (!getrlimit(RLIMIT_NOFILE, &r) && r.rlim_cur < FORKSRV_FD + 2) {

r.rlim_cur = FORKSRV_FD + 2;
setrlimit(RLIMIT_NOFILE, &r); /* Ignore errors */

}

if (mem_limit) {

r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;

#ifdef RLIMIT_AS

setrlimit(RLIMIT_AS, &r); /* Ignore errors */

#else

/* This takes care of OpenBSD, which doesn't have RLIMIT_AS, but
according to reliable sources, RLIMIT_DATA covers anonymous
maps - so we should be getting good protection against OOM bugs. */

setrlimit(RLIMIT_DATA, &r); /* Ignore errors */

#endif /* ^RLIMIT_AS */


}

/* Dumping cores is slow and can lead to anomalies if SIGKILL is delivered
before the dump is complete. */

r.rlim_max = r.rlim_cur = 0;

setrlimit(RLIMIT_CORE, &r); /* Ignore errors */

/* Isolate the process and configure standard descriptors. If out_file is
specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */

setsid(); // 为子进程创建独立的会话和守护进程,脱离父进程

dup2(dev_null_fd, 1); // 重定向stdout、stderr到/dev/null
dup2(dev_null_fd, 2);

if (out_file) { // 若out_file不为空

dup2(dev_null_fd, 0); // 重定向stdin到/dev/null

} else {

dup2(out_fd, 0); // 重定向stdin到out_fd
close(out_fd);

}

/* Set up control and status pipes, close the unneeded original fds. */

if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed"); // 重定向管道输入到FORKSRV_FD
if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed"); // 定向管道输出到FORKSRV_FD + 1

// 关闭不必要的fds,父子进程共享文件描述符fds
close(ctl_pipe[0]);
close(ctl_pipe[1]);
close(st_pipe[0]);
close(st_pipe[1]);

close(out_dir_fd);
close(dev_null_fd);
close(dev_urandom_fd);
close(fileno(plot_file));

/* This should improve performance a bit, since it stops the linker from
doing extra work post-fork(). */

if (!getenv("LD_BIND_LAZY")) setenv("LD_BIND_NOW", "1", 0); // 若未设置环境变量LD_BIND_LAZY,则设置为"1"

/* Set sane defaults for ASAN if nothing else specified. */

setenv("ASAN_OPTIONS", "abort_on_error=1:"
"detect_leaks=0:"
"symbolize=0:"
"allocator_may_return_null=1", 0); // 设置ASCN相关环境变量

/* MSAN is tricky, because it doesn't support abort_on_error=1 at this
point. So, we do this in a very hacky way. */

setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":"
"symbolize=0:"
"abort_on_error=1:"
"allocator_may_return_null=1:"
"msan_track_origins=0", 0); // 设置MSAN相关环境变量

execv(target_path, argv); // 执行目标程序,该程序作为fork server,若无意外产生,执行到fuzz结束

/* Use a distinctive bitmap signature to tell the parent about execv()
falling through. */

*(u32*)trace_bits = EXEC_FAIL_SIG; // 设置报错信息
exit(0);

}

/* Close the unneeded endpoints. */

close(ctl_pipe[0]);
close(st_pipe[1]);

/* 管道通信,父进程具有ctl写,具有st读的权限 */
fsrv_ctl_fd = ctl_pipe[1];
fsrv_st_fd = st_pipe[0];

/* Wait for the fork server to come up, but don't wait too long. */
// 等待forkserver启动

it.it_value.tv_sec = ((exec_tmout * FORK_WAIT_MULT) / 1000);
it.it_value.tv_usec = ((exec_tmout * FORK_WAIT_MULT) % 1000) * 1000;

setitimer(ITIMER_REAL, &it, NULL);

// 从管道中读取fork server状态
rlen = read(fsrv_st_fd, &status, 4);

// 关闭定时器
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;

setitimer(ITIMER_REAL, &it, NULL);

/* If we have a four-byte "hello" message from the server, we're all set.
Otherwise, try to figure out what went wrong. */
// 若读取成功,标志fork server启动成功
if (rlen == 4) {
OKF("All right - fork server is up.");
return;
}

if (child_timed_out) // fork server超时
FATAL("Timeout while initializing fork server (adjusting -t may help)");

if (waitpid(forksrv_pid, &status, 0) <= 0) // 等待fork server结束,并返回status
PFATAL("waitpid() failed");

if (WIFSIGNALED(status)) { // 根据status,判断错误原因

if (mem_limit && mem_limit < 500 && uses_asan) {

SAYF("\n" cLRD "[-] " cRST
"Whoops, the target binary crashed suddenly, before receiving any input\n"
" from the fuzzer! Since it seems to be built with ASAN and you have a\n"
" restrictive memory limit configured, this is expected; please read\n"
" %s/notes_for_asan.txt for help.\n", doc_path);

} else if (!mem_limit) {

SAYF("\n" cLRD "[-] " cRST
"Whoops, the target binary crashed suddenly, before receiving any input\n"
" from the fuzzer! There are several probable explanations:\n\n"

" - The binary is just buggy and explodes entirely on its own. If so, you\n"
" need to fix the underlying problem or find a better replacement.\n\n"

#ifdef __APPLE__

" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

" - Less likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");

} else {

SAYF("\n" cLRD "[-] " cRST
"Whoops, the target binary crashed suddenly, before receiving any input\n"
" from the fuzzer! There are several probable explanations:\n\n"

" - The current memory limit (%s) is too restrictive, causing the\n"
" target to hit an OOM condition in the dynamic linker. Try bumping up\n"
" the limit with the -m setting in the command line. A simple way confirm\n"
" this diagnosis would be:\n\n"

#ifdef RLIMIT_AS
" ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#else
" ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#endif /* ^RLIMIT_AS */

" Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
" estimate the required amount of virtual memory for the binary.\n\n"

" - The binary is just buggy and explodes entirely on its own. If so, you\n"
" need to fix the underlying problem or find a better replacement.\n\n"

#ifdef __APPLE__

" - On MacOS X, the semantics of fork() syscalls are non-standard and may\n"
" break afl-fuzz performance optimizations when running platform-specific\n"
" targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n"

#endif /* __APPLE__ */

" - Less likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
DMS(mem_limit << 20), mem_limit - 1);

}

FATAL("Fork server crashed with signal %d", WTERMSIG(status));

}

if (*(u32*)trace_bits == EXEC_FAIL_SIG)
FATAL("Unable to execute target application ('%s')", argv[0]);

if (mem_limit && mem_limit < 500 && uses_asan) {

SAYF("\n" cLRD "[-] " cRST
"Hmm, looks like the target binary terminated before we could complete a\n"
" handshake with the injected code. Since it seems to be built with ASAN and\n"
" you have a restrictive memory limit configured, this is expected; please\n"
" read %s/notes_for_asan.txt for help.\n", doc_path);

} else if (!mem_limit) {

SAYF("\n" cLRD "[-] " cRST
"Hmm, looks like the target binary terminated before we could complete a\n"
" handshake with the injected code. Perhaps there is a horrible bug in the\n"
" fuzzer. Poke <lcamtuf@coredump.cx> for troubleshooting tips.\n");

} else {

SAYF("\n" cLRD "[-] " cRST
"Hmm, looks like the target binary terminated before we could complete a\n"
" handshake with the injected code. There are %s probable explanations:\n\n"

"%s"
" - The current memory limit (%s) is too restrictive, causing an OOM\n"
" fault in the dynamic linker. This can be fixed with the -m option. A\n"
" simple way to confirm the diagnosis may be:\n\n"

#ifdef RLIMIT_AS
" ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#else
" ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n"
#endif /* ^RLIMIT_AS */

" Tip: you can use http://jwilk.net/software/recidivm to quickly\n"
" estimate the required amount of virtual memory for the binary.\n\n"

" - Less likely, there is a horrible bug in the fuzzer. If other options\n"
" fail, poke <lcamtuf@coredump.cx> for troubleshooting tips.\n",
getenv(DEFER_ENV_VAR) ? "three" : "two",
getenv(DEFER_ENV_VAR) ?
" - You are using deferred forkserver, but __AFL_INIT() is never\n"
" reached before the program terminates.\n\n" : "",
DMS(mem_limit << 20), mem_limit - 1);

}

FATAL("Fork server handshake failed");

}

has_new_bits

检测是否发现了新的路径或者某个路径的执行次数是否不同。

若发现了新的路径,返回2;若某个路径执行次数不同,返回1;否则返回0。

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
/* Check if the current execution path brings anything new to the table.
Update virgin bits to reflect the finds. Returns 1 if the only change is
the hit-count for a particular tuple; 2 if there are new tuples seen.
Updates the map, so subsequent calls will always return 0.

This function is called after every exec() on a fairly large buffer, so
it needs to be fast. We do this in 32-bit and 64-bit flavors. */

static inline u8 has_new_bits(u8* virgin_map) {

#ifdef WORD_SIZE_64 // 64位版本

u64* current = (u64*)trace_bits;
u64* virgin = (u64*)virgin_map;

u32 i = (MAP_SIZE >> 3);

#else // 32位版本

u32* current = (u32*)trace_bits;
u32* virgin = (u32*)virgin_map;

u32 i = (MAP_SIZE >> 2);

#endif /* ^WORD_SIZE_64 */

u8 ret = 0;

while (i--) { // 循环遍历virgin_bits

/* Optimize for (*current & *virgin) == 0 - i.e., no bits in current bitmap
that have not been already cleared from the virgin map - since this will
almost always be the case. */

// 如果current不为0,且current & virgin不为0,即代表current发现了新路径或者某条路径的执行次数和之前有所不同
if (unlikely(*current) && unlikely(*current & *virgin)) {

if (likely(ret < 2)) {

u8* cur = (u8*)current;
u8* vir = (u8*)virgin;

/* Looks like we have not found any new bytes yet; see if any non-zero
bytes in current[] are pristine in virgin[]. */

#ifdef WORD_SIZE_64

if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) ||
(cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff) ||
(cur[4] && vir[4] == 0xff) || (cur[5] && vir[5] == 0xff) ||
(cur[6] && vir[6] == 0xff) || (cur[7] && vir[7] == 0xff)) ret = 2; // cur[i]不为空,vir[i]不变,代表发现了新路径
else ret = 1; // 代表改变了某个queue的命中次数

#else

if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) ||
(cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff)) ret = 2;
else ret = 1;

#endif /* ^WORD_SIZE_64 */

}

*virgin &= ~*current; // 设置virgin_map

}

current++;
virgin++;

}

if (ret && virgin_map == virgin_bits) bitmap_changed = 1; // 若ret > 0且virgin_map等于virgin_bits,设置bitmap_changed为1

return ret;

}

count_bytes

统计发现的路径个数。

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
#define FF(_b)  (0xff << ((_b) << 3))

/* Count the number of bytes set in the bitmap. Called fairly sporadically,
mostly to update the status screen or calibrate and examine confirmed
new paths. */

static u32 count_bytes(u8* mem) {

u32* ptr = (u32*)mem;
u32 i = (MAP_SIZE >> 2);
u32 ret = 0;

while (i--) { // 每次处理32位数据

u32 v = *(ptr++);

if (!v) continue;
if (v & FF(0)) ret++; // v & 0xff
if (v & FF(1)) ret++; // v & 0xff00
if (v & FF(2)) ret++; // v & 0xff0000
if (v & FF(3)) ret++; // v & 0xff000000

}

return ret; // 返回统计的路径数量

}

run_target

执行目标程序,并更新queue情况。

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
/* Execute target application, monitoring for timeouts. Return status
information. The called program will update trace_bits[]. */

static u8 run_target(char** argv, u32 timeout) {

static struct itimerval it;
static u32 prev_timed_out = 0;
static u64 exec_ms = 0;

int status = 0;
u32 tb4;

child_timed_out = 0; // 设置子进程超时时间

/* After this memset, trace_bits[] are effectively volatile, so we
must prevent any earlier operations from venturing into that
territory. */

memset(trace_bits, 0, MAP_SIZE); // 初始化trave_bits, 也即清空共享内存,0x10000
MEM_BARRIER();

/* If we're running in "dumb" mode, we can't rely on the fork server
logic compiled into the target program, so we will just keep calling
execve(). There is a bit of code duplication between here and
init_forkserver(), but c'est la vie. */

if (dumb_mode == 1 || no_forkserver) { // 如果是简易模式,或者no_forkserver,类似于init_forkserver流程,不再赘述

child_pid = fork();

if (child_pid < 0) PFATAL("fork() failed");

if (!child_pid) {

struct rlimit r;

if (mem_limit) {

r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;

#ifdef RLIMIT_AS

setrlimit(RLIMIT_AS, &r); /* Ignore errors */

#else

setrlimit(RLIMIT_DATA, &r); /* Ignore errors */

#endif /* ^RLIMIT_AS */

}

r.rlim_max = r.rlim_cur = 0;

setrlimit(RLIMIT_CORE, &r); /* Ignore errors */

/* Isolate the process and configure standard descriptors. If out_file is
specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */

setsid();

dup2(dev_null_fd, 1);
dup2(dev_null_fd, 2);

if (out_file) {

dup2(dev_null_fd, 0);

} else {

dup2(out_fd, 0);
close(out_fd);

}

/* On Linux, would be faster to use O_CLOEXEC. Maybe TODO. */

close(dev_null_fd);
close(out_dir_fd);
close(dev_urandom_fd);
close(fileno(plot_file));

/* Set sane defaults for ASAN if nothing else specified. */

setenv("ASAN_OPTIONS", "abort_on_error=1:"
"detect_leaks=0:"
"symbolize=0:"
"allocator_may_return_null=1", 0);

setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":"
"symbolize=0:"
"msan_track_origins=0", 0);

execv(target_path, argv);

/* Use a distinctive bitmap value to tell the parent about execv()
falling through. */

*(u32*)trace_bits = EXEC_FAIL_SIG;
exit(0);

}

} else { // 执行到此,代表forkserver成功启动

s32 res;

/* In non-dumb mode, we have the fork server up and running, so simply
tell it to have at it, and then read back PID. */

if ((res = write(fsrv_ctl_fd, &prev_timed_out, 4)) != 4) { // 向fork server写入控制命令,表示启动目标程序

if (stop_soon) return 0; // 若stop_soon不为空,直接返回
RPFATAL(res, "Unable to request new process from fork server (OOM?)");

}

if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) { // fork server返回子进程pid,读取管道中子进程pid

if (stop_soon) return 0; // 若stop_soon不为空,直接返回
RPFATAL(res, "Unable to request new process from fork server (OOM?)");

}

if (child_pid <= 0) FATAL("Fork server is misbehaving (OOM?)");

}

/* Configure timeout, as requested by user, then wait for child to terminate. */
// 为目标程序设置计时器
it.it_value.tv_sec = (timeout / 1000);
it.it_value.tv_usec = (timeout % 1000) * 1000;

setitimer(ITIMER_REAL, &it, NULL);

/* The SIGALRM handler simply kills the child_pid and sets child_timed_out. */

if (dumb_mode == 1 || no_forkserver) { // 如果是简易模式,或者no_forkserver,等待子进程结束并获取status

if (waitpid(child_pid, &status, 0) <= 0) PFATAL("waitpid() failed");

} else { // fork server模式下

s32 res;

if ((res = read(fsrv_st_fd, &status, 4)) != 4) { // 从管道中获取子进程执行状态

if (stop_soon) return 0;
RPFATAL(res, "Unable to communicate with fork server (OOM?)");

}

}

if (!WIFSTOPPED(status)) child_pid = 0; // 判断子进程是否执行结束,设置child_pid为0

getitimer(ITIMER_REAL, &it);
exec_ms = (u64) timeout - (it.it_value.tv_sec * 1000 +
it.it_value.tv_usec / 1000);
// 关闭计时器
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;

setitimer(ITIMER_REAL, &it, NULL);

total_execs++; // 执行次数 + 1

/* Any subsequent operations on trace_bits must not be moved by the
compiler below this point. Past this location, trace_bits[] behave
very normally and do not have to be treated as volatile. */

MEM_BARRIER();

tb4 = *(u32*)trace_bits;

#ifdef WORD_SIZE_64 // 对trace_bit进行分类
classify_counts((u64*)trace_bits);
#else
classify_counts((u32*)trace_bits);
#endif /* ^WORD_SIZE_64 */

prev_timed_out = child_timed_out; // 设置prev_timed_out为child_timed_out

/* Report outcome to caller. */

if (WIFSIGNALED(status) && !stop_soon) { // 若为异常结束子进程状态,且stop_soon为空

kill_signal = WTERMSIG(status);

if (child_timed_out && kill_signal == SIGKILL) return FAULT_TMOUT;

return FAULT_CRASH;

}

/* A somewhat nasty hack for MSAN, which doesn't support abort_on_error and
must use a special exit code. */

// 这是对MSAN的一种令人讨厌的攻击,它不支持abort_on_error,必须使用特殊的退出代码。
if (uses_asan && WEXITSTATUS(status) == MSAN_ERROR) {
kill_signal = 0;
return FAULT_CRASH;
}

if ((dumb_mode == 1 || no_forkserver) && tb4 == EXEC_FAIL_SIG)
return FAULT_ERROR;

/* It makes sense to account for the slowest units only if the testcase was run
under the user defined timeout. */
if (!(timeout > exec_tmout) && (slowest_exec_ms < exec_ms)) {
slowest_exec_ms = exec_ms;
}

return FAULT_NONE;

}

classify_counts

  • 具体地,target是将每个分支的执行次数用1个byte来储存,而fuzzer则进一步把这个执行次数归入到buckets中,举个例子,如果某分支执行了1次,那么落入第2个bucket,其计数byte仍为1;如果某分支执行了4次,那么落入第5个bucket,其计数byte将变为8,等等。
  • 这样处理之后,对分支执行次数就会有一个简单的归类。例如,如果对某个测试用例处理时,分支A执行了32次;对另外一个测试用例,分支A执行了33次,那么AFL就会认为这两次的代码覆盖是相同的。当然,这样的简单分类肯定不能区分所有的情况,不过在某种程度上,处理了一些因为循环次数的微小区别,而误判为不同执行结果的情况.
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
#ifdef WORD_SIZE_64

static inline void classify_counts(u64* mem) {

u32 i = MAP_SIZE >> 3;

while (i--) { // 遍历trace_bits,对trace_bits进行归类

/* Optimize for sparse bitmaps. */

if (unlikely(*mem)) { // 8个字节为一组执行,每次取两个字节

u16* mem16 = (u16*)mem;

mem16[0] = count_class_lookup16[mem16[0]];
mem16[1] = count_class_lookup16[mem16[1]];
mem16[2] = count_class_lookup16[mem16[2]];
mem16[3] = count_class_lookup16[mem16[3]];

}

mem++;

}

}

#else

static inline void classify_counts(u32* mem) {

u32 i = MAP_SIZE >> 2;

while (i--) { // 遍历trace_bits,对trace_bits进行归类

/* Optimize for sparse bitmaps. */

if (unlikely(*mem)) { // 4个字节为一组执行,每次取两个字节

u16* mem16 = (u16*)mem;

mem16[0] = count_class_lookup16[mem16[0]];
mem16[1] = count_class_lookup16[mem16[1]];

}

mem++;

}

}

#endif /* ^WORD_SIZE_64 */

update_bitmap_score

每当我们发现一个新的路径,都会调用这个函数来判断其是不是更加地favorable。

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
/* When we bump into a new path, we call this to see if the path appears
more "favorable" than any of the existing ones. The purpose of the
"favorables" is to have a minimal set of paths that trigger all the bits
seen in the bitmap so far, and focus on fuzzing them at the expense of
the rest.

The first step of the process is to maintain a list of top_rated[] entries
for every byte in the bitmap. We win that slot if there is no previous
contender, or if the contender has a more favorable speed x size factor. */

static void update_bitmap_score(struct queue_entry* q) {

u32 i;
u64 fav_factor = q->exec_us * q->len; // 计算case的fav_factor

/* For every byte set in trace_bits[], see if there is a previous winner,
and how it compares to us. */

for (i = 0; i < MAP_SIZE; i++) // 遍历trace_bits

if (trace_bits[i]) { // 代表已经覆盖到的path

if (top_rated[i]) { // 若top_tated[i]不为空

/* Faster-executing or smaller test cases are favored. */

// 若发现fav_factor大于原来top_tated的fav_factor,continue
if (fav_factor > top_rated[i]->exec_us * top_rated[i]->len) continue;

/* Looks like we're going to win. Decrease ref count for the
previous winner, discard its trace_bits[] if necessary. */

// fav_factor更小,表示更优,释放top_rated[i]->trace_mini
if (!--top_rated[i]->tc_ref) {
ck_free(top_rated[i]->trace_mini);
top_rated[i]->trace_mini = 0;
}

}

/* Insert ourselves as the new winner. */

top_rated[i] = q; // 设置top_rated[i]为当前queue
q->tc_ref++; // tc_ref++

if (!q->trace_mini) { // 若q->trace_mini为空
q->trace_mini = ck_alloc(MAP_SIZE >> 3); // 为q->trace_mini分配空间
minimize_bits(q->trace_mini, trace_bits); // 压缩q->trace_mini
}

score_changed = 1; // 设置score_changed为1

}

}

minimize_bits

将1byte压缩为1bit处理。

简单的理解就是把原本是包括了是否覆盖到和覆盖了多少次的byte,压缩成是否覆盖到的bit。

经典算法系列之(一) - BitMap 数据的压缩存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* Compact trace bytes into a smaller bitmap. We effectively just drop the
count information here. This is called only sporadically, for some
new paths. */

static void minimize_bits(u8* dst, u8* src) {

u32 i = 0;

while (i < MAP_SIZE) {

if (*(src++)) dst[i >> 3] |= 1 << (i & 7); // 将对应字节放到对应的位上,用1bit表示1bytes
i++;

}

}

cull_queue

精简队列。

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
/* The second part of the mechanism discussed above is a routine that
goes over top_rated[] entries, and then sequentially grabs winners for
previously-unseen bytes (temp_v) and marks them as favored, at least
until the next run. The favored entries are given more air time during
all fuzzing steps. */

static void cull_queue(void) {

struct queue_entry* q;
static u8 temp_v[MAP_SIZE >> 3];
u32 i;

if (dumb_mode || !score_changed) return; // 如果处于dumb模式或者score没有发生变化(top_rated没有发生变化),直接返回

score_changed = 0; // 将score_changed设置为0

memset(temp_v, 255, MAP_SIZE >> 3); // 初始化temp_v为0xff

queued_favored = 0;
pending_favored = 0;

q = queue;

while (q) { // 遍历queue,将所有的q->favored置为0
q->favored = 0;
q = q->next;
}

/* Let's see if anything in the bitmap isn't captured in temp_v.
If yes, and if it has a top_rated[] contender, let's use it. */

// 将i从0到MAP_SIZE迭代,其实就是筛选出一组queue entry,能够覆盖到所有已经覆盖到的路径
for (i = 0; i < MAP_SIZE; i++)
if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) { // 若top_tated[i]不为空,且temp_v对应的bit有没有被置空

u32 j = MAP_SIZE >> 3;

/* Remove all bits belonging to the current entry from temp_v. */

while (j--) // 从temp_v中清除掉所有top_rated[i]覆盖到的queue,即将对应的bit置为0
if (top_rated[i]->trace_mini[j])
temp_v[j] &= ~top_rated[i]->trace_mini[j];

top_rated[i]->favored = 1; // 设置top_tated[i]->favored为1
queued_favored++; // queued_favored数量+1

if (!top_rated[i]->was_fuzzed) pending_favored++; // 为0代表没有被fuzz过

}

q = queue;

while (q) { // 遍历q
mark_as_redundant(q, !q->favored); // 根据q->favored标志位标记是否为无用的路径
q = q->next;
}

}

mark_as_redundant

标记无用的/冗余的queue。

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
/* Mark / unmark as redundant (edge-only). This is not used for restoring state,
but may be useful for post-processing datasets. */

static void mark_as_redundant(struct queue_entry* q, u8 state) {

u8* fn;
s32 fd;

if (state == q->fs_redundant) return; // 若state = q->fs_redundant, 代表已经被标记过的,直接返回

q->fs_redundant = state;

fn = strrchr(q->fname, '/'); // 获取q->fname名称
fn = alloc_printf("%s/queue/.state/redundant_edges/%s", out_dir, fn + 1); // out_dir/queue/.state/redundant_edges/fn+1

if (state) { // 如果state不为空,即not favored

fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", fn);
close(fd);

} else {

if (unlink(fn)) PFATAL("Unable to remove '%s'", fn);

}

ck_free(fn);

}

show_init_stats

显示统计信息,以及设置一些基本量。

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
/* Display quick statistics at the end of processing the input directory,
plus a bunch of warnings. Some calibration stuff also ended up here,
along with several hardcoded constants. Maybe clean up eventually. */

static void show_init_stats(void) {

struct queue_entry* q = queue;
u32 min_bits = 0, max_bits = 0;
u64 min_us = 0, max_us = 0;
u64 avg_us = 0;
u32 max_len = 0;

if (total_cal_cycles) avg_us = total_cal_us / total_cal_cycles; // 若total_cal_cycles不为空,计算每轮平均执行时间

while (q) { // 遍历queue,获取最小的min_us,最大的max_us,以及最小的min_bits,最大的max_bits,最大长度max_len

if (!min_us || q->exec_us < min_us) min_us = q->exec_us;
if (q->exec_us > max_us) max_us = q->exec_us;

if (!min_bits || q->bitmap_size < min_bits) min_bits = q->bitmap_size;
if (q->bitmap_size > max_bits) max_bits = q->bitmap_size;

if (q->len > max_len) max_len = q->len;

q = q->next;

}

SAYF("\n");

if (avg_us > (qemu_mode ? 50000 : 10000)) // 若avg_us大于10000,或者qemu模式的50000,打印警告信息
WARNF(cLRD "The target binary is pretty slow! See %s/perf_tips.txt.",
doc_path);

/* Let's keep things moving with slow binaries. */
// 展示执行速度
if (avg_us > 50000) havoc_div = 10; /* 0-19 execs/sec */
else if (avg_us > 20000) havoc_div = 5; /* 20-49 execs/sec */
else if (avg_us > 10000) havoc_div = 2; /* 50-100 execs/sec */

if (!resuming_fuzz) { // 若resuming_fuzz不为空,打印警告信息

if (max_len > 50 * 1024)
WARNF(cLRD "Some test cases are huge (%s) - see %s/perf_tips.txt!",
DMS(max_len), doc_path);
else if (max_len > 10 * 1024)
WARNF("Some test cases are big (%s) - see %s/perf_tips.txt.",
DMS(max_len), doc_path);

if (useless_at_start && !in_bitmap)
WARNF(cLRD "Some test cases look useless. Consider using a smaller set.");

if (queued_paths > 100)
WARNF(cLRD "You probably have far too many input files! Consider trimming down.");
else if (queued_paths > 20)
WARNF("You have lots of input files; try starting small.");

}

OKF("Here are some useful stats:\n\n"

cGRA " Test case count : " cRST "%u favored, %u variable, %u total\n"
cGRA " Bitmap range : " cRST "%u to %u bits (average: %0.02f bits)\n"
cGRA " Exec timing : " cRST "%s to %s us (average: %s us)\n",
queued_favored, queued_variable, queued_paths, min_bits, max_bits,
((double)total_bitmap_size) / (total_bitmap_entries ? total_bitmap_entries : 1),
DI(min_us), DI(max_us), DI(avg_us)); // 打印执行信息

if (!timeout_given) { // 若timeout_given不为空,设置timeout_given

/* Figure out the appropriate timeout. The basic idea is: 5x average or
1x max, rounded up to EXEC_TM_ROUND ms and capped at 1 second.

If the program is slow, the multiplier is lowered to 2x or 3x, because
random scheduler jitter is less likely to have any impact, and because
our patience is wearing thin =) */

// 根据avg_us设置exec_tmount
if (avg_us > 50000) exec_tmout = avg_us * 2 / 1000;
else if (avg_us > 10000) exec_tmout = avg_us * 3 / 1000;
else exec_tmout = avg_us * 5 / 1000;

exec_tmout = MAX(exec_tmout, max_us / 1000);
exec_tmout = (exec_tmout + EXEC_TM_ROUND) / EXEC_TM_ROUND * EXEC_TM_ROUND;

if (exec_tmout > EXEC_TIMEOUT) exec_tmout = EXEC_TIMEOUT;

ACTF("No -t option specified, so I'll use exec timeout of %u ms.",
exec_tmout);

timeout_given = 1; // 设置timeout_given为1

} else if (timeout_given == 3) { // 若timeout_given为3

ACTF("Applying timeout settings from resumed session (%u ms).", exec_tmout);

}

/* In dumb mode, re-running every timing out test case with a generous time
limit is very expensive, so let's select a more conservative default. */

if (dumb_mode && !getenv("AFL_HANG_TMOUT")) // 若为简易模式,且环境变量AFL_HANG_TMOUT不为空,设置hang_tmout
hang_tmout = MIN(EXEC_TIMEOUT, exec_tmout * 2 + 100);

OKF("All set and ready to roll!");

}

find_start_position

resume时,查找queue起始位置。

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
/* When resuming, try to find the queue position to start from. This makes sense
only when resuming, and when we can find the original fuzzer_stats. */

static u32 find_start_position(void) {

static u8 tmp[4096]; /* Ought to be enough for anybody. */

u8 *fn, *off;
s32 fd, i;
u32 ret;

if (!resuming_fuzz) return 0; // 若resuming_fuzz为空,直接返回

if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir); // 若in_place_resume不为空
else fn = alloc_printf("%s/../fuzzer_stats", in_dir);

fd = open(fn, O_RDONLY); // 打开fn文件
ck_free(fn);

if (fd < 0) return 0;

i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */ // 读取fuzzer_stats内容到tmp数组
close(fd);

off = strstr(tmp, "cur_path : "); // 若查找到cur_path,提取出当前path
if (!off) return 0;

ret = atoi(off + 20); // 将off转换为int型
if (ret >= queued_paths) ret = 0;
return ret;

}

write_stats_file

更新统计信息。

save_auto

自动保存自动生成的extra。

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
/* Save automatically generated extras. */

static void save_auto(void) {

u32 i;

if (!auto_changed) return; // 若auto_changed不为空,直接返回
auto_changed = 0;

for (i = 0; i < MIN(USE_AUTO_EXTRAS, a_extras_cnt); i++) { // 遍历a_extras数组

u8* fn = alloc_printf("%s/queue/.state/auto_extras/auto_%06u", out_dir, i);
s32 fd;

fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600); // 以只写模式打开fn文件

if (fd < 0) PFATAL("Unable to create '%s'", fn);

ck_write(fd, a_extras[i].data, a_extras[i].len, fn); // 将a_extras写入文件

close(fd); // 关闭文件
ck_free(fn);

}

}

参考链接

https://eternalsakura13.com/2020/08/23/afl/

https://hollk.blog.csdn.net/category_11470526.html

https://paper.seebug.org/1732/

https://www.z1r0.top/2023/03/23/AFL-fuzz%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/


AFL源码分析(三)
http://example.com/2023/10/04/AFL源码分析三/
作者
l1s00t
发布于
2023年10月4日
更新于
2023年10月6日
许可协议