AFL源码分析之afl-fuzz(学习笔记)(三)

`


25、u8 calibrate_case(char **argv, struct queue_entry *q, u8 *use_mem, u32 handicap, u8 from_queue)(对输入的测似用例判断是否正常,以及评估若多次执行测试用例发现路径是否相同,校准阶段)

/* 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];创建first_trace数组

  u8  fault = 0, new_bits = 0, var_detected = 0, hnb = 0,将这几个参数设置为0
      first_run = (q->exec_cksum == 0);如果q->exec_cksum为0 则代表第一次运行测试,所以将first_input置为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;保存原有的stage_cur、stage_max、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. */
     /*在恢复会话或尝试校准已添加的查找时,对超时要宽容一点。
     这有助于避免由于间歇性延迟而引起的故障*/

  if (!from_queue || resuming_fuzz)如果from_queue为0或者resuming_fuzz为1时
    use_tmout = MAX(exec_tmout + CAL_TMOUT_ADD,
                    exec_tmout * CAL_TMOUT_PERC / 100);校准选择更大的超时值,在config.h中CAL_TMOUT_ADD定义为50ms CAL_TMOUT_PERC定义为125

  q->cal_failed++;

  stage_name = "calibration";  将stage_name 设置为calibration
  stage_max  = fast_cal ? 3 : CAL_CYCLES;stage_max设置为3还是CAL_CYCLES,含义是stage要将执行几次的意思
 //校准周期 CAL_CYCLES(默认为8)

  /* Make sure the forkserver is up before we do anything, and let's not
     count its spin-up time toward binary calibration. */
     *在我们做任何事情之前,请确保forkserver已启动,
     并且不要计算二进制校准的启动时间*

  if (dumb_mode != 1 && !no_forkserver && !forksrv_pid)//如果当前不是以dumb mode(dumb_mode 就是和固定变异)运行,且no_forkserver(禁用forkserver)为0,且forksrv_pid为0,
    init_forkserver(argv);//则调用init_forkserver(argv)来启动forksever
    运行叉服务器
  if (q->exec_cksum) {//如果q->exec_cksum不为空
	//检测队列项目是否已被模糊
    memcpy(first_trace, trace_bits, MAP_SIZE);//拷贝trace_bits到first_trace里,MAP_SIZE仍为64kb
    hnb = has_new_bits(virgin_bits);
    if (hnb > new_bits) new_bits = hnb;

  }

  start_us = get_cur_time_us();//标记这一时刻然后进入轮次

  for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {//开始执行校验阶段,共执行stage_max轮

    u32 cksum;

    if (!first_run && !(stage_cur % stats_update_freq)) show_stats();//如果这个queue不是来自input文件夹,而是评估新case,且第一轮calibration stage执行结束时,刷新一次展示界面show_stats,用来展示这次执行的结果,此后不再展示

    write_to_testcase(use_mem, q->len);

    fault = run_target(argv, use_tmout);//判断是否超时,如果超时则返回0,赋值给fault

    /* 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;
如果这是calibration stage第一次运行,且不在dumb_mode(dumb_mode 就是和固定变异),且共享内存里没有任何路径(即没有任何byte被置位),设置fault为FAULT_NOINST,然后goto abort_calibration
    if (!dumb_mode && !stage_cur && !count_bytes(trace_bits)) {//通过count_bytes函数来计算共享内存有多少字节被置位了
      fault = FAULT_NOINST;
      goto abort_calibration;
    }

    cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);//计算校验和

    if (q->exec_cksum != cksum) {//如果q->exec_cksum不等于cksum,即代表这是第一次运行,或者在相同的参数下,每次执行,cksum却不同,是一个路径可变的queu

      hnb = has_new_bits(virgin_bits);//将new_bits合并到virgin_bits
      if (hnb > new_bits) new_bits = hnb;//如果hnb大于new_bits,设置new_bits的值为hnb

      if (q->exec_cksum) {//如果q->exec_cksum不等于0,即代表这是判断是否是可变queue

        u32 i;

        for (i = 0; i < MAP_SIZE; i++) {将i从0到MAP-SIZE遍历

          if (!var_bytes[i] && first_trace[i] != trace_bits[i]) {
          //如果first_trace[i]不等于trace_bits[i]且var_bytes为0
			则代表发现了可变的queue
            var_bytes[i] = 1;//将var_bytes设置为1
            stage_max    = CAL_CYCLES_LONG;//将stage_max设置为CAL_CYCLES_LONG,即需要执行40次。

          }

        }

        var_detected = 1;//将var_detected设置为1

      } else {
		//否则,即q->exec_cksum等于0,即代表这是第一次执行这个queue
        q->exec_cksum = cksum;设置q->exec_cksum的值为之前计算出来的本次执行的cksum
        memcpy(first_trace, trace_bits, MAP_SIZE);拷贝trace_bits到first_trace中。

      }

    }

  }
//标记时刻,以微秒为单位计算校准时间,并节省校准周期数
  stop_us = get_cur_time_us();

  total_cal_us     += stop_us - start_us;//保存所有轮次的执行时间加到total_cal_us里
  total_cal_cycles += stage_max;//总的执行轮次,加到total_cal_cycles里

  /* OK, let's collect some stats about the performance of this test case.
     This is used for fuzzing air time calculations in calculate_score(). */
//将指标保存到队列项目中。count_bytes()基本上统计trace_bits中与零不同的字节数。
  q->exec_us     = (stop_us - start_us) / stage_max;//计算出单次执行时间的平均值保存到q->exec_us里
  q->bitmap_size = count_bytes(trace_bits);//将最后一次执行所覆盖到的路径数保存到q->bitmap_size里
  q->handicap    = handicap;
  q->cal_failed  = 0;

  total_bitmap_size += q->bitmap_size;//total_bitmap_size里加上这个queue所覆盖到的路径数
  total_bitmap_entries++;

  update_bitmap_score(q);

  /* 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) {//代表有一个queue发现了新路径
    q->has_new_cov = 1;
    queued_with_cov++;
  }

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

    var_byte_count = count_bytes(var_bytes);//则计算var_bytes里被置位的tuple个数,保存到var_byte_count里,代表这些tuple具有可变的行为。

    if (!q->var_behavior) {
      mark_as_variable(q);
      queued_variable++;计数器加一
    }

  }

  stage_name = old_sn;
  stage_cur  = old_sc;
  stage_max  = old_sm;

  if (!first_run) show_stats();如果不是第一次运行这个queue,展示show_stats

  return fault;

}

26、init_forkserver(在插桩状态下启动forkserver fork出子进程并建立管道通信)

/* 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创建子进程 在子进程中fork返回结果为0,如果为父进程则返回结果为进程ID

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

  if (!forksrv_pid) {其为子进程的操作

    struct rlimit r;

    /* Umpf. On OpenBSD, the default fd limit for root users is set to
       soft 128. Let's try to fix that... */
       根用户默认设置fd为128

    if (!getrlimit(RLIMIT_NOFILE, &r) && r.rlim_cur < FORKSRV_FD + 2) {获取或者设置资源使用权限
    //指定比进程可打开的最大文件描述词(r)大一的值,超出此值,将会产生EMFILE错误

      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);重定向文件描述符12到dev_null_fd
    dup2(dev_null_fd, 2);

    if (out_file) {如果指定了out_file

      dup2(dev_null_fd, 0);

    } else {

      dup2(out_fd, 0);否则将文件描述符0重定向到out_file
      close(out_fd);

    }

    /* Set up control and status pipes, close the unneeded original fds. */
	设置控制和状态管道,关闭不需要的原始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到状态管道
	
	关闭一些文件描述符
    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. */
	设置环境变量ASAN_OPTIONS
    setenv("ASAN_OPTIONS", "abort_on_error=1:"
                           "detect_leaks=0:"
                           "symbolize=0:"
                           "allocator_may_return_null=1", 0);

    /* MSAN is tricky, because it doesn't support abort_on_error=1 at this
       point. So, we do this in a very hacky way. */
	设置环境变量MSAN
    setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":"
                           "symbolize=0:"
                           "abort_on_error=1:"
                           "allocator_may_return_null=1:"
                           "msan_track_origins=0", 0);

    execv(target_path, argv);
    带参数执行target(子进程),这个函数除了出错不会返回
  
     运行程序会替换当前进程,pid不变
     第一个目标程序会进入__afl_fork_wait_loop,并充当fork server
     在整个过程中,每次都会从fork server中fork出来一个子进程去fuzz
     流程就是:fuzzer -> fork server -> target


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

  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. */

  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);
 	setitimer函数来实现延时和定时的功能
  rlen = read(fsrv_st_fd, &status, 4);从状态管道读取四个字节到status里

  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. */

  if (rlen == 4) {如果读取成功则成功启动,如果超时则抛出异常
    OKF("All right - fork server is up.");
    return;
  }
	***下面全部是子进程启动失败的处理逻辑***不进行阐述
  if (child_timed_out)
    FATAL("Timeout while initializing fork server (adjusting -t may help)");


}

27、has_new_bits(u8 *virgin_map)检查是否有新路径或者路径执行次数不同

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

  u64* current = (u64*)trace_bits;初始化为trace_bits的u64首元素地址  trace_bits为当前用例执行情况,也是共享内存
  u64* virgin  = (u64*)virgin_map;

  u32  i = (MAP_SIZE >> 3);

#else

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

  u32  i = (MAP_SIZE >> 2);

#endif /* ^WORD_SIZE_64 */

  u8   ret = 0;

  while (i--) {

    /* 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. */

    if (unlikely(*current) && unlikely(*current & *virgin)) {如果current不为0,且*current & *virgin为0,,则代表某条路径的执行次数和之前不同

      if (likely(ret < 2)) {如果ret<2

        u8* cur = (u8*)current;取current的首字节地址为cur
        u8* vir = (u8*)virgin;virgin的首字节地址为vir

        /* 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
		比较cur[i] && vir[i] == 0xff,如果有一个为真,则设置ret为2
        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;
		这代表发现了之前没有出现的路径

        else ret = 1;否则ret=1,代表只是改变了命中数

#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_ bits在开始时设置为所有,并且*.virgin&=~*current清除相关位。如果仅更改命中次数,此函数返回1,如果我们看到新边,则返			回2,否则返回0。让我们继续校准。
    }

    current++;
    virgin++;

  }

  if (ret && virgin_map == virgin_bits) bitmap_changed = 1;virgin_bits代表所有用例 virgin_bits保存还没有被Fuzz覆盖到的byte,其初始值每位全被置位1,然后每次按字节置位

  return ret;

}

28、u32 count_bytes(u8 *mem)计算位图中设置的字节数


/* 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. */
   /*计算位图中设置的字节数。偶尔调用,主要用于更新状态屏幕或校准和检查已确认的新路径*/
#define FF(_b)  (0xff << ((_b) << 3))
static u32 count_bytes(u8* mem) {

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

  while (i--) {

    u32 v = *(ptr++);

    if (!v) continue;如果v为0,则代表4个字节都是0
    if (v & FF(0)) ret++; 如果v不为0,且FF(0)不为0,则ret加1,以下同理
    if (v & FF(1)) ret++;
    if (v & FF(2)) ret++;
    if (v & FF(3)) ret++;

  }

  return ret;

}

29、u8 run_target(char **argv, u32 timeout)监测目标程序超时

/* Execute target application, monitoring for timeouts. Return status
   information. The called program will update trace_bits[]. */
   /*执行目标应用程序,监视超时。返回状态信息。被调用的程序将更新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[](共享内存)实际上是不稳定的,因此我们必须防止任何早期操作进入该领域*/

  memset(trace_bits, 0, MAP_SIZE);将trace_bits全部清为0,就是将共享内存清零
  MEM_BARRIER();能够让 CPU 或编译器在内存访问上有序

  /* 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) {如果dumb_mode(dumb_mode 就是和固定变异)1且no_forkserver不为0

    child_pid = fork();fork出子进程

    if (child_pid < 0) PFATAL("fork() failed");如果小于0则显示fork错误 

    if (!child_pid) {如果成功fork出子进程

      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);子进程execv去执行target

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

      *(u32*)trace_bits = EXEC_FAIL_SIG;如果execv执行失败,则向trace_bits写入EXEC_FAIL_SIG
      exit(0);

    }

  } else {dumb_mode(dumb_mode 就是和固定变异)且no_forkserver不等于1

    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) {向控制管道写入prev_timed_out的值

      if (stop_soon) return 0;
      RPFATAL(res, "Unable to request new process from fork server (OOM?)");

    }

    if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) {从状态管道读取fork server返回的fork出的子进程的ID到child_pid

      if (stop_soon) return 0;
      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) {

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

  } else {

    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;

  getitimer(ITIMER_REAL, &it);
  exec_ms = (u64) timeout - (it.it_value.tv_sec * 1000 +
                             it.it_value.tv_usec / 1000);计算target的执行时间

  it.it_value.tv_sec = 0;
  it.it_value.tv_usec = 0;

  setitimer(ITIMER_REAL, &it, NULL);

  total_execs++;执行次数计数器加一

  /* 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
  classify_counts((u64*)trace_bits);下面第三十个函数会介绍
  classify_counts函数的功能是把实际的执行次数进行重新计数,分到8个计数桶中,即0, 1, 2, 4, 8, 16, 32, 64, 1280~255之间的实际执行次数最终都会被分到这8个计数桶中,从而对位图重新赋值
#else
  classify_counts((u32*)trace_bits);
#endif /* ^WORD_SIZE_64 */

  prev_timed_out = child_timed_out;

  /* Report outcome to caller. */

  if (WIFSIGNALED(status) && !stop_soon) {WIFSIGNALED (status)若为异常结束子进程返回的状态,则为真
  所以是异常结束子进程才能向下运行
    kill_signal = WTERMSIG(status);取得子进程因信号而中止的信号代码

    if (child_timed_out && kill_signal == SIGKILL) return FAULT_TMOUT;如果child_timed_out为1(则代表执行target超时),且状态码为SIGKILL,则返回FAULT_TMOUT

    return FAULT_CRASH;否则返回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;

}

30、classify_counts(u64 *mem) 将执行次数重新归类计数

classify_counts函数的功能是把实际的执行次数进行重新计数,分到8个计数桶中,即0, 1, 2, 4, 8, 16, 32, 64, 128,0~255之间的实际执行次数最终都会被分到这8个计数桶中,从而对位图重新赋值

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

static inline void classify_counts(u64* mem) {

  u32 i = MAP_SIZE >> 3;

  while (i--) {

    /* Optimize for sparse bitmaps. */

    if (unlikely(*mem)) {

      u16* mem16 = (u16*)mem;每次取两个字节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]];
i从03,计算mem16[i]的值,在count_class_lookup16[mem16[i]]里找到对应的取值,并赋值给mem16[i]
    }

    mem++;

  }

}

31、update_bitmap_score(struct queue_entry *q)(是否有更好的路径来遍历bitmap中的位)

/* 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. */
/*当我们遇到一条新路径时,我们调用它来查看该路径是否出现
比现有的任何一种都更“有利”。“favorables”的目的是拥有一组最小的路径,触发到目前为止在位图中看到的所有位,并以牺牲其他位为代价将其模糊化。
该过程的第一步是维护位图中每个字节的top_rated[]条目列表。如果之前没有竞争者,或者竞争者的速度x尺寸系数更有利,我们将赢得该位置*/
static void update_bitmap_score(struct queue_entry* q) {

  u32 i;
  u64 fav_factor = q->exec_us * q->len;计算出这个案例的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++)

    if (trace_bits[i]) {遍历共享内存数组,如果不为0,则代表这是已经覆盖到的path

       if (top_rated[i]) {然后检查对应这个路径的top_rated是否存在

         /* Faster-executing or smaller test cases are favored. */
         /*更快的执行或更小的测试用例是有利的*/

         if (fav_factor > top_rated[i]->exec_us * top_rated[i]->len) continue;如果存在即比较执行时间和样例大小的乘积,哪个更小
			如果top_rated[i]更小,则代表top_rated[i]更优,不做任何处理,继续遍历下一个path
         /* Looks like we're going to win. Decrease ref count for the
            previous winner, discard its trace_bits[] if necessary. */

         if (!--top_rated[i]->tc_ref) {如果q更小
           ck_free(top_rated[i]->trace_mini);就将top_rated[i]原先对应的queue entry的tc_ref字段减一
           top_rated[i]->trace_mini = 0;并将其trace_mini字段置为空
         }
		
       }

       /* Insert ourselves as the new winner. */

       top_rated[i] = q;然后设置top_rated[i]为q,即当前case(测试用例)
       q->tc_ref++;将tc_ref 的值加一

       if (!q->trace_mini) {如果q->trace_mini为空
         q->trace_mini = ck_alloc(MAP_SIZE >> 3);
         minimize_bits(q->trace_mini, trace_bits);
         则将trace_bits经过minimize_bits压缩,然后存到trace_mini字段里
       }

       score_changed = 1;

     }

}

32、void minimize_bits(u8 *dst, u8 *src)将共享内存压缩为较小的位图

/* Compact trace bytes into a smaller bitmap. We effectively just drop the
   count information here. This is called only sporadically, for some
   new paths. */
   简单的理解就是把原本是包括了是否覆盖到和覆盖了多少次的byte,压缩成是否覆盖到的bit。

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

  u32 i = 0;

  while (i < MAP_SIZE) {

    if (*(src++)) dst[i >> 3] |= 1 << (i & 7);
    i++;

  }

}

33、cull_queue(精简队列)

/* 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];创建u8 temp_v数组,大小为MAP_SIZE除8
  并将其初始值设置为0xff,其每位如果为1就代表还没有被覆盖到,如果为0就代表以及被覆盖到了。
  u32 i;

  if (dumb_mode || !score_changed) return;如果score_changed为0即top_rated没有变化,或者没有发生变异,就直接返回

  score_changed = 0;设置其为0

  memset(temp_v, 255, MAP_SIZE >> 3);

  queued_favored  = 0;设置这些参数为0
  pending_favored = 0;

  q = queue;

  while (q) {遍历队列q
    q->favored = 0;设置其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. */

  for (i = 0; i < MAP_SIZE; i++)将i从0到MAP_SIZE迭代,运用贪婪算法筛选出一组queue entry,他能覆盖现在所有已经覆盖到的路径
    if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) {为了检查该位是不是0,即判断该path对应的bit有没有被置位。
for (i = 0; i < MAP_SIZE; i++)
    if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) {如果top_rated[i]有值,且该path在temp_v里被置位
...


      u32 j = MAP_SIZE >> 3;

      /* Remove all bits belonging to the current entry from temp_v. */
		就从temp_v中清除掉所有top_rated[i]覆盖到的path,将对应的bit置为0
      while (j--) 
        if (top_rated[i]->trace_mini[j])
          temp_v[j] &= ~top_rated[i]->trace_mini[j];
	
      top_rated[i]->favored = 1;
      queued_favored++;计数器加一

      if (!top_rated[i]->was_fuzzed) pending_favored++;

    }

  q = queue;

  while (q) {
    mark_as_redundant(q, !q->favored); 将所有不是favored的test_case标记成redundant_edges
    q = q->next;
  }

}

34、mark_as_redundant(struct queue_entry *q, u8 state)标记作为变量

35、show_init_stats(在处理输入目录的末尾显示统计信息,以及一堆警告,以及几个硬编码的常量。)

/* 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) 根据第25函数里得到的total_cal_us和total__cal__cycles
  avg_us = total_cal_us / total_cal_cycles;计算出单论执行时间avg_us

  while (q) {

    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)) 
  如果单轮执行时间大于10000,就警告"The target binary is pretty slow! See %s/perf_tips.txt."
    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 session

    if (max_len > 50 * 1024)
      WARNF(cLRD "Some test cases are huge (%s) - see %s/perf_tips.txt!",
            DMS(max_len), doc_path);“一些测试用例很大(%s)-请参阅%s/perf_tips.txt!”
    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)且如果useless_at_start不为0,就警告有可以精简的样本。
      WARNF(cLRD "Some test cases look useless. Consider using a smaller set.");

    if (queued_paths > 100)根据queue的大小和个数超限提出警告
      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为0

    /* 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.
       /*找出适当的超时时间。基本思想是:平均5倍或最大1倍,四舍五入为EXEC_TM_ROUND ms,上限为1秒。

       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 =) */
       如果程序运行缓慢,乘数将降低到2倍或3倍,因为随机调度器抖动不太可能产生任何影响,并且因为我们的耐心越来越弱=*/
	注意这里avg_us的单位是微秒,而exec_tmout单位是毫秒,所以需要除以1000
    if (avg_us > 50000) exec_tmout = avg_us * 2 / 1000;根据单轮执行时间计算出exec_tmout。
    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_tmout = (exec_tmout + EXEC_TM_ROUND) / EXEC_TM_ROUND * EXEC_TM_ROUND;

    if (exec_tmout > EXEC_TIMEOUT)如果exec_tmout大于EXEC_TIMEOUT
     exec_tmout = EXEC_TIMEOUT;最大超时时间为1s

    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,代表这是resuming session

    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"))如果是dumb_mode且没有设置环境变量AFL_HANG_TMOUT
    hang_tmout = MIN(EXEC_TIMEOUT, exec_tmout * 2 + 100);设置hang_tmout为EXEC_TIMEOUT和exec_tmout * 2 + 100中的最小值

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

}

36、find_start_position(resume时,查找要开始的队列位置)

/* 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. */
   /*恢复时,请尝试查找要开始的队列位置。这只有在恢复时才有意义,并且我们可以找到原始的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)如果是in_place_resume
   fn = alloc_printf("%s/fuzzer_stats", out_dir);打开out_dir/fuzzer_stats文件
  else
   fn = alloc_printf("%s/../fuzzer_stats", in_dir);否则打开in_dir/../fuzzer_stats文件

  fd = open(fn, O_RDONLY);
  ck_free(fn);

  if (fd < 0) return 0;

  i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */
  close(fd);

  off = strstr(tmp, "cur_path          : ");找到cur_path的地址
  if (!off) return 0;

  ret = atoi(off + 20);设置ret的值
  if (ret >= queued_paths)如果ret大于queued_paths
   ret = 0;设置ret为0
  return ret;返回ret
 

}

37、void write_stats_file(double bitmap_cvg, double stability, double eps)更新统计信息文件以进行无人值守的监视

/* Update stats file for unattended monitoring. */
/*更新无人值守监控的统计文件*/
static void write_stats_file(double bitmap_cvg, double stability, double eps) {

  static double last_bcvg, last_stab, last_eps;
  static struct rusage usage;

  u8* fn = alloc_printf("%s/fuzzer_stats", out_dir);创建文件
  s32 fd;
  FILE* f;

  fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600);
  O_CREAT 如果指定文件不存在,则创建这个文件
  O_WRONLY 只写模式
  O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)

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

  ck_free(fn);

  f = fdopen(fd, "w");

  if (!f) PFATAL("fdopen() failed");

  /* Keep last values in case we're called from another context
     where exec/sec stats and such are not readily available. */
/*保留最后一个值,以防从另一个上下文调用我们,其中exec/sec stats等不可用*/
  if (!bitmap_cvg && !stability && !eps) {
    bitmap_cvg = last_bcvg;
    stability  = last_stab;
    eps        = last_eps;
  } else {
    last_bcvg = bitmap_cvg;
    last_stab = stability;
    last_eps  = eps;
  }

  fprintf(f, "start_time        : %llu\n"fuzz开始运行时间
             "last_update       : %llu\n"当前时间
             "fuzzer_pid        : %u\n"获取当前pid
             "cycles_done       : %llu\n"代表queue队列被完全变异一次的次数。
             "execs_done        : %llu\n"total_execs,target的总的执行次数,每次run_target的时候会增加1
             "execs_per_sec     : %0.02f\n"每秒执行次数
             "paths_total       : %u\n"queued_paths在每次add_to_queue的时候会增加1,代表queue里的样例总数
             "paths_favored     : %u\n"有价值路径总数
             "paths_found       : %u\n"发现新的interesting case的时候就加一,代表在fuzz运行期间发现新的queue entry
             "paths_imported    : %u\n"queued_imported是master-slave模式下,如果sync过来的case是interesting的,就增加1
             "max_depth         : %u\n"最大路径深度
             "cur_path          : %u\n" /* Must match find_start_position() */current_entry一般情况下代表的是正在执行的queue entry的整数ID,queue首节点的ID是0
             "pending_favs      : %u\n"等待fuzz的favored paths数
             "pending_total     : %u\n"pending_not_fuzzed 在queue中等待fuzz的case"variable_paths    : %u\n"queued_variable在calibrate_case去评估一个新的test case的时候,如果发现这个case的路径是可变的,则将这个计数器加一,代表发现了一个可变case
             "stability         : %0.02f%%\n"
             "bitmap_cvg        : %0.02f%%\n"
             "unique_crashes    : %llu\n"unique_crashes这是在save_if_interesting时,如果fault是FAULT_CRASH,就将unique_crashes计数器加一
             "unique_hangs      : %llu\n"unique_hangs这是在save_if_interesting时,如果fault是FAULT_TMOUT,且exec_tmout小于hang_tmout,就以hang_tmout为超时时间再执行一次,如果还超时,就让hang计数器加一。
             "last_path         : %llu\n"在add_to_queue里将一个新case加入queue时,就设置一次last_path_time为当前时间,last_path_time / 1000
             "last_crash        : %llu\n"同上,在unique_crashes加一的时候,last_crash也更新时间,last_crash_time / 1000
             "last_hang         : %llu\n"同上,在unique_hangs加一的时候,last_hang也更新时间,last_hang_time / 1000
             "execs_since_crash : %llu\n"total_execs - last_crash_execs,这里last_crash_execs是在上一次crash的时候的总计执行了多少次
             "exec_timeout      : %u\n" /* Must match find_timeout() */配置好的超时时间
             "afl_banner        : %s\n"
             "afl_version       : " VERSION "\n"
             "target_mode       : %s%s%s%s%s%s%s\n"
             "command_line      : %s\n"
             "slowest_exec_ms   : %llu\n",
             start_time / 1000, get_cur_time() / 1000, getpid(),
             queue_cycle ? (queue_cycle - 1) : 0, total_execs, eps,
             queued_paths, queued_favored, queued_discovered, queued_imported,
             max_depth, current_entry, pending_favored, pending_not_fuzzed,
             queued_variable, stability, bitmap_cvg, unique_crashes,
             unique_hangs, last_path_time / 1000, last_crash_time / 1000,
             last_hang_time / 1000, total_execs - last_crash_execs,
             exec_tmout, use_banner,
             qemu_mode ? "qemu " : "", dumb_mode ? " dumb " : "",
             no_forkserver ? "no_forksrv " : "", crash_mode ? "crash " : "",
             persistent_mode ? "persistent " : "", deferred_mode ? "deferred " : "",
             (qemu_mode || dumb_mode || no_forkserver || crash_mode ||
              persistent_mode || deferred_mode) ? "" : "default",
             orig_cmdline, slowest_exec_ms);
             /* ignore errors */

  /* Get rss value from the children
     We must have killed the forkserver process and called waitpid
     before calling getrusage */
  if (getrusage(RUSAGE_CHILDREN, &usage)) {
      WARNF("getrusage failed");
  } else if (usage.ru_maxrss == 0) {
    fprintf(f, "peak_rss_mb       : not available while afl is running\n");
  } else {
#ifdef __APPLE__
    fprintf(f, "peak_rss_mb       : %zu\n", usage.ru_maxrss >> 20);
#else
    fprintf(f, "peak_rss_mb       : %zu\n", usage.ru_maxrss >> 10);
#endif /* ^__APPLE__ */
  }

  fclose(f);

}

38、save_auto(保存自动生成的extras)

/* Save automatically generated extras. */
/*保存自动生成的附加项*/

static void save_auto(void) {

  u32 i;

  if (!auto_changed) return;如果auto_changed为0,则直接返回
  auto_changed = 0;
	如果不为0,就设置为0,然后创建名为alloc_printf("%s/queue/.state/auto_extras/auto_%06u", out_dir, 			 i);的文件,并写入a_extras的内容。
  for (i = 0; i < MIN(USE_AUTO_EXTRAS, a_extras_cnt); i++) {

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

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

    ck_write(fd, a_extras[i].data, a_extras[i].len, fn);

    close(fd);
    ck_free(fn);

  }

}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值