AFL¶
种子同步问题¶
如果我有一个好的种子,想要加入一个正在运行的AFL实例中,怎么办
distributed mode¶
如果AFL以并行模式运行(-M / -S),比较好办
AFL并行模式同步机制¶
关键数据sync_dir和sync_id
EXP_ST u8 *sync_dir, /* Synchronization directory */ *sync_id, /* Fuzzer ID */
sync_id就是 -M / -S 后面的参数
case 'M': { /* master sync ID */ u8* c; if (sync_id) FATAL("Multiple -S or -M options not supported"); sync_id = ck_strdup(optarg);
sync_dir的值等于-o 后面的参数,也就是说并行模式下out目录变成sync_dir, 真正的out目录变成 sync_dir/sync_id
static void fix_up_sync(void) { //... x = alloc_printf("%s/%s", out_dir, sync_id); sync_dir = out_dir; out_dir = x; //...
执行同步的函数 sync_fuzzers
static void sync_fuzzers(char** argv) { DIR* sd; struct dirent* sd_ent; u32 sync_cnt = 0; sd = opendir(sync_dir); if (!sd) PFATAL("Unable to open '%s'", sync_dir); stage_max = stage_cur = 0; cur_depth = 0; /* Look at the entries created for every other fuzzer in the sync directory. */ while ((sd_ent = readdir(sd))) { static u8 stage_tmp[128]; DIR* qd; struct dirent* qd_ent; u8 *qd_path, *qd_synced_path; u32 min_accept = 0, next_min_accept; s32 id_fd; /* Skip dot files and our own output directory. */ if (sd_ent->d_name[0] == '.' || !strcmp(sync_id, sd_ent->d_name)) continue; /* Skip anything that doesn't have a queue/ subdirectory. */ /* 跳过没有queue这个子目录的文件夹 */ qd_path = alloc_printf("%s/%s/queue", sync_dir, sd_ent->d_name); if (!(qd = opendir(qd_path))) { ck_free(qd_path); continue; } /* Retrieve the ID of the last seen test case. */ qd_synced_path = alloc_printf("%s/.synced/%s", out_dir, sd_ent->d_name); id_fd = open(qd_synced_path, O_RDWR | O_CREAT, 0600); if (id_fd < 0) PFATAL("Unable to create '%s'", qd_synced_path); if (read(id_fd, &min_accept, sizeof(u32)) > 0) lseek(id_fd, 0, SEEK_SET); /* 获取最小可接受id值,其值等于上次同步的种子id + 1 next_min_accept = min_accept; /* Show stats */ sprintf(stage_tmp, "sync %u", ++sync_cnt); stage_name = stage_tmp; stage_cur = 0; stage_max = 0; /* For every file queued by this fuzzer, parse ID and see if we have looked at it before; exec a test case if not. */ while ((qd_ent = readdir(qd))) { /* 打开另一个fuzz实例的out目录下的queue文件夹 u8* path; s32 fd; struct stat st; /* 排除 '.'开头的文件,排除非id:XXXXXX开头的文件,排除XXXXXX小于上次记录的id的文件 */ if (qd_ent->d_name[0] == '.' || sscanf(qd_ent->d_name, CASE_PREFIX "%06u", &syncing_case) != 1 || syncing_case < min_accept) continue; /* OK, sounds like a new one. Let's give it a try. */ if (syncing_case >= next_min_accept) next_min_accept = syncing_case + 1; path = alloc_printf("%s/%s", qd_path, qd_ent->d_name); /* Allow this to fail in case the other fuzzer is resuming or so... */ fd = open(path, O_RDONLY); if (fd < 0) { ck_free(path); continue; } if (fstat(fd, &st)) PFATAL("fstat() failed"); /* Ignore zero-sized or oversized files. */ if (st.st_size && st.st_size <= MAX_FILE) { u8 fault; u8* mem = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mem == MAP_FAILED) PFATAL("Unable to mmap '%s'", path); /* See what happens. We rely on save_if_interesting() to catch major errors and save the test case. */ write_to_testcase(mem, st.st_size); fault = run_target(argv, exec_tmout); if (stop_soon) return; syncing_party = sd_ent->d_name; queued_imported += save_if_interesting(argv, mem, st.st_size, fault); syncing_party = 0; munmap(mem, st.st_size); if (!(stage_cur++ % stats_update_freq)) show_stats(); } ck_free(path); close(fd); } ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path); close(id_fd); closedir(qd); ck_free(qd_path); ck_free(qd_synced_path); } closedir(sd); }
大致流程
for 目录 in 打开sync_dir目录并获取目录下的所有目录 do if 目录没有queue这个子目录 -o 目录名称等于当前sync_id, 即不同步自己; then 跳过; fi 获取最小可接受id值,其值等于上次同步的种子id + 1 for seed in 打开queue目录下的所有文件 do if '.'开头的文件 -o 非id:XXXXXX开头的文件 -o id:XXXXXX小于最小可接受id值; then 跳过; fi 更新最小可接受id值,其值等于当前id + 1 save_if_interesting(argv, mem, st.st_size, fault); done done
save_if_interesting 流程
在非crash_mode下,同步的种子如果超时或者崩溃,是不会加入队列的。如果bitmap有更新,是会计入hangs或者crashes
在crash_mode下,同步的种子崩溃,加入队列,如果bitmap有更新计入crashes。超时处理与非crash_mode相同
static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) { // ... if (fault == crash_mode) { /* crash_mode 默认为0,也就是FAULT_NONE, '-C' 指定crash_mode为FAULT_CRASH */ /* bitmap不更新直接返回 */ // ... fn = alloc_printf("%s/queue/id:%06u,%s", out_dir, queued_paths, describe_op(hnb)); // ... keeping = 1; } switch (fault) { case FAULT_TMOUT: /* 如果没有更新或者大于上限直接返回 */ // .. fn = alloc_printf("%s/hangs/id:%06llu,%s", out_dir, unique_hangs, describe_op(0)); // ... case FAULT_CRASH: /* 如果没有更新或者大于上限直接返回 */ // ... fn = alloc_printf("%s/crashes/id:%06llu,sig:%02u,%s", out_dir, unique_crashes, kill_signal, describe_op(0)); // ... } /* If we're here, we apparently want to save the crash or hang test case, too. */ // ... return keeping; }
AFL_HANG_TMOUT
非parallel模式¶
除了改源码暂时没看到好的办法
一个种子最多可以变异多少次¶
执行一次,total_execs 自增1
static u8 run_target(char** argv, u32 timeout) { // ... total_execs++;
调用了 run_target 的函数
calibrate_case save_if_interesting // 可以忽略 只在exec_tmout < hang_tmout才有可能执行 trim_case common_fuzz_stuff sync_fuzzers // 忽略
函数fuzz_one
static u8 fuzz_one(char** argv) { // ... /******************************************* * CALIBRATION (only if failed earlier on) * *******************************************/ res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0); /************ * TRIMMING * ************/ u8 res = trim_case(argv, queue_cur, in_buf); /********************* * PERFORMANCE SCORE * *********************/ // none /********************************************* * SIMPLE BITFLIP (+dictionary construction) * *********************************************/ // stage_max 等于len << 3 , len 种子大小,一位一位地翻, 步长1个bit for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { stage_cur_byte = stage_cur >> 3; FLIP_BIT(out_buf, stage_cur); if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; FLIP_BIT(out_buf, stage_cur); // stage_max 等于len << 3 , len 种子大小,两位一翻, 步长1个bit for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { stage_cur_byte = stage_cur >> 3; FLIP_BIT(out_buf, stage_cur); FLIP_BIT(out_buf, stage_cur + 1); if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; FLIP_BIT(out_buf, stage_cur); FLIP_BIT(out_buf, stage_cur + 1); } // stage_max 等于len << 3 , len 种子大小,4位一翻, 步长1个bit // ... // stage_max 等于len , len 种子大小,一次翻一个字节,步长1个byte for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { stage_cur_byte = stage_cur; out_buf[stage_cur] ^= 0xFF; if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 一次翻2个字节,步长1个byte // ... // 一次翻4个字节,步长1个byte // ... /********************** * ARITHMETIC INC/DEC * **********************/
跟种子大小和优劣有很大关系,
暂停分析