AFL fuzz_one函数,有个地方判断skip_deterministic ,而AFLNet的例子里面,定义了-d参数,也就是skip_deterministic=1,直接进入到havoc_stage,所以这里想分析下havoc_stage。
if (skip_deterministic || queue_cur->was_fuzzed || queue_cur->passed_det)goto havoc_stage;
havoc_stage:stage_cur_byte = -1;stage_name = "havoc";stage_short = "havoc";// fuzz次数和perf_score有关stage_max = (doing_det ? HAVOC_CYCLES_INIT : HAVOC_CYCLES) *perf_score / havoc_div / 100;if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN;// temp_leng = testcase的长度,输入文件的长度temp_len = len;orig_hit_cnt = queued_paths + unique_crashes;havoc_queued = queued_paths;/* We essentially just do several thousand runs (depending on perf_score)where we take the input file and make random stacked tweaks. */for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {// 生成一个2-256的随机数u32 use_stacking = 1 << (1 + UR(HAVOC_STACK_POW2));stage_cur_val = use_stacking;// 每次会对输入文件进行一种随机操作 for (i = 0; i < use_stacking; i++) {// 随机选择一种突变操作switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) {case 0:/* Flip a single bit somewhere. Spooky! */FLIP_BIT(out_buf, UR(temp_len << 3));break;case 1: case 2:case 16:}}// 这个地方完成突变// 执行突变后的的testcaseif (common_fuzz_stuff(argv, out_buf, temp_len))goto abandon_entry;/* out_buf might have been mangled a bit, so let's restore it to itsoriginal size and shape. */if (temp_len < len) out_buf = ck_realloc(out_buf, len);temp_len = len;memcpy(out_buf, in_buf, len);/* If we're finding new stuff, let's run for a bit longer, limitspermitting. */if (queued_paths != havoc_queued) {if (perf_score <= HAVOC_MAX_MULT * 100) {stage_max *= 2;perf_score *= 2;}havoc_queued = queued_paths;}}new_hit_cnt = queued_paths + unique_crashes;if (!splice_cycle) {stage_finds[STAGE_HAVOC] += new_hit_cnt - orig_hit_cnt;stage_cycles[STAGE_HAVOC] += stage_max;} else {stage_finds[STAGE_SPLICE] += new_hit_cnt - orig_hit_cnt;stage_cycles[STAGE_SPLICE] += stage_max;}
这段代码比较关键的部分是这个循环:
for (i = 0; i < use_stacking; i++) {switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) {
这里需要搞明白2个问题:use_stacking的含义,UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))的含义
use_stacking,结合注释
/* We essentially just do several thousand runs (depending on perf_score)where we take the input file and make random stacked tweaks. */
take the input file and make random stacked tweaks.,对输入文件进行随机的 堆叠的 微调。
所以use_stacking是一个随机值,表示对某输入文件进行突变的次数。
UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0)),这也是一个随机数,随机选择突变的方法。
接下来单独看一个突变,加深下理解,这里就看case 0,FLIP_BIT
#define FLIP_BIT(_ar, _b) do { \u8* _arf = (u8*)(_ar); \u32 _bf = (_b); \_arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \} while (0)
这段代码也很简单,反转输入_ar(也就是testcase)的第_b位。
综合来看,前面那段代码的意思就是,对于当前的testcase,突变use_stacking次,每次随机选择1种突变策略。
继续往下,执行common_fuzz_stuff函数:
/* Write a modified test case, run program, process results. Handleerror conditions, returning 1 if it's time to bail out. This isa helper function for fuzz_one(). */EXP_ST u8 common_fuzz_stuff(char** argv, u8* out_buf, u32 len) {u8 fault;if (post_handler) {out_buf = post_handler(out_buf, &len);if (!out_buf || !len) return 0;}write_to_testcase(out_buf, len);fault = run_target(argv, exec_tmout);if (fault == FAULT_TMOUT) {if (subseq_tmouts++ > TMOUT_LIMIT) {cur_skipped_paths++;return 1;}} else subseq_tmouts = 0;/* This handles FAULT_ERROR for us: */queued_discovered += save_if_interesting(argv, out_buf, len, fault);if (!(stage_cur % stats_update_freq) || stage_cur + 1 == stage_max)show_stats();return 0;}
根据注释,Write a modified test case, run program, process results.,这里应该要执行代码。
post_handler函数,不太明白
write_to_testcase,这个函数在calibrate_case中也有使用,后续有需要就研究下。
接下来就是调用run_target,参考之前的博客:https://blog.csdn.net/weixin_44033321/article/details/136430578:
调用save_if_interesting来处理run_target返回的错误:
/* Check if the result of an execve() during routine fuzzing is interesting,save or queue the input test case for further analysis if so. Returns 1 ifentry is saved, 0 otherwise. */
// 如果保存了有意思的testcase,就返回1;没保存,返回0
static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) {u8 *fn = "";u8 hnb;s32 fd;u8 keeping = 0, res;switch (fault) {case FAULT_TMOUT:/* Timeouts are not very interesting, but we're still obliged to keepa handful of samples. We use the presence of new bits in thehang-specific bitmap as a signal of uniqueness. In "dumb" mode, wejust keep everything. */total_tmouts++;if (unique_hangs >= KEEP_UNIQUE_HANG) return keeping;if (!dumb_mode) {simplify_trace((u64*)trace_bits);// 如果没有触发新的覆盖,那么就返回函数if (!has_new_bits(virgin_tmout)) return keeping;}unique_tmouts++;/* Before saving, we make sure that it's a genuine hang by re-runningthe target with a more generous timeout (unless the default timeoutis already generous). */// 用一个更宽松的时间,重新执行代码,如果还是返回错误,那么就处理该错误if (exec_tmout < hang_tmout) {u8 new_fault;write_to_testcase(mem, len);new_fault = run_target(argv, hang_tmout);/* A corner case that one user reported bumping into: increasing thetimeout actually uncovers a crash. Make sure we don't discard it ifso. */if (!stop_soon && new_fault == FAULT_CRASH) goto keep_as_crash;if (stop_soon || new_fault != FAULT_TMOUT) return keeping;}// 记录错误到输出文件夹fn = alloc_printf("%s/hangs/id_%06llu", out_dir, unique_hangs);unique_hangs++;last_hang_time = get_cur_time();break;case FAULT_CRASH:keep_as_crash:/* This is handled in a manner roughly similar to timeouts,except for slightly different limits and no need to re-run testcases. */total_crashes++;if (unique_crashes >= KEEP_UNIQUE_CRASH) return keeping;if (!dumb_mode) {simplify_trace((u64*)trace_bits);// 如果没有触发新的路径,那么就丢弃这个testcaseif (!has_new_bits(virgin_crash)) return keeping;}if (!unique_crashes) write_crash_readme();fn = alloc_printf("%s/crashes/id_%06llu_%02u", out_dir, unique_crashes, kill_signal);unique_crashes++;last_crash_time = get_cur_time();last_crash_execs = total_execs;break;case FAULT_ERROR: FATAL("Unable to execute target application");default: return keeping;}/* If we're here, we apparently want to save the crash or hangtest case, too. */fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600);if (fd < 0) PFATAL("Unable to create '%s'", fn);ck_write(fd, mem, len, fn);close(fd);ck_free(fn);return keeping;}
这里又调用了simplify_trace:
static void simplify_trace(u64* mem) {u32 i = MAP_SIZE >> 3;while (i--) {/* Optimize for sparse bitmaps. */if (unlikely(*mem)) {u8* mem8 = (u8*)mem;mem8[0] = simplify_lookup[mem8[0]];mem8[1] = simplify_lookup[mem8[1]];mem8[2] = simplify_lookup[mem8[2]];mem8[3] = simplify_lookup[mem8[3]];mem8[4] = simplify_lookup[mem8[4]];mem8[5] = simplify_lookup[mem8[5]];mem8[6] = simplify_lookup[mem8[6]];mem8[7] = simplify_lookup[mem8[7]];} else *mem = 0x0101010101010101ULL;mem++;}}
/* Destructively simplify trace by eliminating hit count informationand replacing it with 0x80 or 0x01 depending on whether the tupleis hit or not. Called on every new crash or timeout, should bereasonably fast. */static const u8 simplify_lookup[256] = { [0] = 1,[1 ... 255] = 128};
如果trace_bits的某个字节是0,也就是没覆盖到,那么通过simplify_lookup,这一位被转化为1,也就是u8的最低位置1。
如果trace_bits的某个字节是非0,也就是覆盖到,那么通过simplify_lookup,这一位被转化为128,也就是u8的最高位置1。
simplify_trace函数就是把trace_bits进行了一个转化,转化为了0x80,或者0x01。
整个save_if_interesting的作用就是,根据错误类型,以及是否触发了新的覆盖率,来判断是否保存该突变出来的testcase。
但是这里我有3个问题,save_if_interesting函数,经过一系列判断之后,保存当前testcase,并返回keeping。
第一个问题是,keeping当时的值为0,应该返回1?
第二个问题是,这个testcase应该被保存到队列中,具体实现保存的代码是哪里呢?
第三个问题是,has_new_bits对trace_bits进行了转化,转化为0x80,或者0x01,这个会不会影响has_new_bits(virgin_crash)函数的执行结果?
先验证下第一个问题:在return keeping;前面打印下keeping的值,看看是0还是1
结果这个地方确实=0
那么queued_discovered += save_if_interesting(argv, out_buf, len, fault);,queued_discovered 参数就不会+1
第二个问题,我觉得得先继续往下看,后续可能有对队列的处理
第三个问题,转到分析 trace_bits 和 virgin_bits的博客,继续分析。
目前3个问题都还没有完全搞清楚,先继续往下看了