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

文章目录

  • 前言
  • 1.shmget(key_t key, size_t size, int shmflg)函数
  • 2.shmat(int shm_id, const void *shm_addr, int shmflg)函数
  • 一、源码
  • 11.setup_shm(配置共享内存和virgin_bits)
  • 12.init_count_class16(记录是否到达这个路径,和这个路径被命中了多少次)
  • 13.setup_dirs_fds(准备输出文件夹和fd)
  • 14.read_testcases(从输入文件夹中读取所有文件,然后将它们排队进行测试)
  • 15、add_to_queue(将新测试用例附加到队列中)
  • 16、load_auto(加载自动生成的附加内容)
  • 17、maybe_add_auto(//maybe_add_auto的辅助函数)
  • 18、pivot_inputs(在输出目录中为输入测试用例创建硬链接,选择好名字和相应的旋转)
  • 19、load_extras()
  • 24、perform_dry_run(对所有测试用例进行试运行,以确认应用程序按照预期。这仅对初始输入执行,并且仅执行一次)

前言

1.shmget(key_t key, size_t size, int shmflg)函数

第一个参数,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.
这里shm_id取值是IPC_PRIVATE,所以函数shmget()将创建一块新的共享内存
第二个参数,size以字节为单位指定需要共享的内存容量
这里取值为MAP_SIZE
第三个参数,shmflg是权限标志
IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则打开操作。
IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。
421分别表示,读写执行3种权限。 比如,上面的6=4+2,表示读+写。
0600 每一位表示一种类型的权限,比如,第一位是表示八进制,第二位表示拥有者的权限为读写,第三位表示同组无权限,第四位表示他人无权限。

2.shmat(int shm_id, const void *shm_addr, int shmflg)函数

第一个参数,shm_id是由shmget()函数返回的共享内存标识。
第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
第三个参数,shm_flg是一组标志位,通常为0。
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1

一、源码

11.setup_shm(配置共享内存和virgin_bits)

/* Configure shared memory and virgin_bits. This is called at startup. */
/*配置共享内存和原始字节。这叫做启动*/

EXP_ST void setup_shm(void) {

  u8* shm_str;

  if (!in_bitmap) //如果in_bitmap为空
  memset(virgin_bits, 255, MAP_SIZE);//通过memset初始化数组virgin_bits[MAP_SIZE]的每个元素的值为'255'

  memset(virgin_tmout, 255, MAP_SIZE);//通过memset初始化数组virgin_tmout[MAP_SIZE]的每个元素的值为'255'
  memset(virgin_crash, 255, MAP_SIZE);//通过memset初始化数组virgin_crash[MAP_SIZE]的每个元素的值为'255'

  shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);//调用shmget分配一块共享内存,将返回的共享内存标识符保存到shm_id里。

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

  atexit(remove_shm);//注册atexit handler为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? */
     /*如果有人要求我们在哑模式下模糊检测的二进制文件,
		我们不希望他们检测仪器,因为我们不会发送
		fork服务器命令。这也许以后应该被更好的自动检测所取代
		吧*/

  if (!dumb_mode) //如果不是dumb_mode模式
  setenv(SHM_ENV_VAR, shm_str, 1);//设置环境变量SHM_ENV_VAR的值为shm_str

  ck_free(shm_str);//释放shm_str内存

  trace_bits = shmat(shm_id, NULL, 0);//第一次创建完共享内存时,它还不能被任何进程访问,所以通过shmat来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间
  
  if (trace_bits == (void *)-1)//如果shmat()函数启动访问失败则返回“-1”
   PFATAL("shmat() failed");

}

12.init_count_class16(记录是否到达这个路径,和这个路径被命中了多少次)

/* Destructively classify execution counts in a trace. This is used as a
   preprocessing step for any newly acquired traces. Called on every exec,
   must be fast. */
   /*对跟踪中的执行计数进行破坏性分类。这是用来作为一个
	任何新获取的记录道的预处理步骤。拜访了每一位高管,
	一定很快*/

static const u8 count_class_lookup8[256] = {//来记录是否到达这个路径,和这个路径被命中了多少次

  [0]           = 0,
  [1]           = 1,
  [2]           = 2,
  [3]           = 4,
  [4 ... 7]     = 8,//命中4到7次都统一认为命中了8次
  [8 ... 15]    = 16,//同理
  [16 ... 31]   = 32,//同理
  [32 ... 127]  = 64,//同理
  [128 ... 255] = 128//同理

};//而为什么又需要用一个count_class_lookup16呢,是因为AFL在后面实际进行规整的时候,是一次读两个字节去处理的,为了提高效率,这只是出于效率的考量,实际效果还是上面这种效果。
static u16 count_class_lookup16[65536];


EXP_ST void init_count_class16(void) {

  u32 b1, b2;

  for (b1 = 0; b1 < 256; b1++) 
    for (b2 = 0; b2 < 256; b2++)//将count_class_lookup16[65536]分成两个256的嵌套循环
      count_class_lookup16[(b1 << 8) + b2] =    
        (count_class_lookup8[b1] << 8) |
        count_class_lookup8[b2];//count_class_lookup8[b1]左移8的结果同count_class_lookup8[b2]进行按位或操作,结果放在b1左移8加b2下标的count_class_lookup16数组成员中

}

13.setup_dirs_fds(准备输出文件夹和fd)

/* 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文件夹,设置权限为0700(读写执行)
      PFATAL("Unable to create '%s'", sync_dir);//否则报错不能创建

  if (mkdir(out_dir, 0700)) {//创建out_dir文件夹,设置权限为读写执行

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

    maybe_delete_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_fd

#ifndef __sun	//如果没有定义宏__sun

    if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB))//如果打开out_dir失败,或者为out_dir通过flock建立互斥锁定失败
      PFATAL("Unable to flock() output directory.");

#endif /* !__sun */

  }

  /* Queue directory for any starting & discovered paths. */ //任何起始路径和发现路径的队列目录。

  tmp = alloc_printf("%s/queue", out_dir);//创建一个tmp字符串
  if (mkdir(tmp, 0700))//创建out_dir/queue文件夹,设置权限为读写
  PFATAL("Unable to create '%s'", tmp);//否则报错不能创建
  ck_free(tmp);//释放tmp内存

  /* Top-level directory for queue metadata used for session
     resume and related tasks. */
     /*用于会话的队列元数据的顶级目录简历和相关任务*/

  tmp = alloc_printf("%s/queue/.state/", out_dir);
  if (mkdir(tmp, 0700))// 建立out_dir/queue/.state/文件夹,设置为读写权限 主要保存用于session resume和related tasks的queue metadata
  PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);//释放tmp内存

  /* Directory for flagging queue entries that went through
     deterministic fuzzing in the past. */
     /*用于标记过去经过确定性模糊处理的队列条目的目录*/

  tmp = alloc_printf("%s/queue/.state/deterministic_done/", out_dir);
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);//创建out_dir/queue/.state/deterministic_done/,设置为读写权限,该文件夹标记过去经历过deterministic fuzzing的queue entries。
  ck_free(tmp);

  /* Directory with the auto-selected dictionary entries. */
  /*包含自动选择的词典条目的目录*/

  tmp = alloc_printf("%s/queue/.state/auto_extras/", out_dir);
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);//创建out_dir/queue/.state/auto_extras/,设置为读写权限
  ck_free(tmp);

  /* The set of paths currently deemed redundant. */
  /*当前被认为是冗余的路径集*/
  tmp = alloc_printf("%s/queue/.state/redundant_edges/", out_dir);
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);//创建out_dir/queue/.state/redundant_edges/,设置为读写权限
  ck_free(tmp);

  /* The set of paths showing variable behavior. */
  /*显示可变行为的路径集*/

  tmp = alloc_printf("%s/queue/.state/variable_behavior/", out_dir);
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);//创建out_dir/queue/.state/variable_behavior/,设置为读写权限
  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/,设置为读写权限,同步文件夹,用于跟踪cooperating fuzzers.
      PFATAL("Unable to create '%s'", tmp);

    ck_free(tmp);

  }

  /* All recorded crashes. */
  /*记录所有的cashes*/
  tmp = alloc_printf("%s/crashes", out_dir);
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);//创建out_dir/crashes文件夹,设置为读写权限,用于记录crashes
  ck_free(tmp);

  /* All recorded hangs. */
 /*记录所有的hangs*/
  tmp = alloc_printf("%s/hangs", out_dir);
  if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp);//创建out_dir/hangs文件夹,设置为读写权限
  ck_free(tmp);

  /* Generally useful file descriptors. */
	/*通常有用的文件描述符*/
  dev_null_fd = open("/dev/null", O_RDWR);//以读写模式打开
  if (dev_null_fd < 0) 
  PFATAL("Unable to open /dev/null");

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

  /* Gnuplot output file. */
  /*建立Gnuplot输出文件夹*/
  tmp = alloc_printf("%s/plot_data", out_dir);
  fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600);//以只写方式打开 如果文件不存在,就创建,并获取句柄
  if (fd < 0)
  PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);

  plot_file = fdopen(fd, "w");//根据句柄得到FILE* plot_file
  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");
                     /* ignore errors */
//向其中写入# unix_time, cycles_done, cur_path, paths_total, pending_total, pending_favs, map_size, unique_crashes, unique_hangs, max_depth, execs_per_sec\n
}

14.read_testcases(从输入文件夹中读取所有文件,然后将它们排队进行测试)

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

  ACTF("Scanning '%s'...", 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);

  if (nl_cnt < 0) {

    if (errno == ENOENT || errno == 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) {

    ACTF("Shuffling queue...");
    shuffle_ptrs((void**)nl, nl_cnt);

  }

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

    struct stat st;

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

    u8  passed_det = 0;

    free(nl[i]); /* not tracked */
 
    if (lstat(fn, &st) || access(fn, R_OK))
      PFATAL("Unable to access '%s'", fn);

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

    if (!S_ISREG(st.st_mode) || !st.st_size || strstr(fn, "/README.testcases")) {

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

    }

    if (st.st_size > MAX_FILE) 
      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;
    ck_free(dfn);

    add_to_queue(fn, st.st_size, passed_det);

  }

  free(nl); /* not tracked */

  if (!queued_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;
  queued_at_start = queued_paths;

}

15、add_to_queue(将新测试用例附加到队列中)


/* 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->len          = len;
  q->depth        = cur_depth + 1;
  q->passed_det   = passed_det;

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

  if (queue_top) {

    queue_top->next = q;
    queue_top = q;

  } else 
  q_prev100 = queue = queue_top = q;

  queued_paths++;
  pending_not_fuzzed++;//queue计数器queued_paths和待fuzz的样例计数器pending_not_fuzzed加一

  cycles_wo_finds = 0;

  /* Set next_100 pointer for every 100th element (index 0, 100, etc) to allow faster iteration. *///为每100个元素(索引0、100等)设置next_100指针,以允许更快的迭代。
  if ((queued_paths - 1) % 100 == 0 && queued_paths > 1) {

    q_prev100->next_100 = q;
    q_prev100 = q;

  }

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

}

16、load_auto(加载自动生成的附加内容)

load自动生成的提取出来的词典token

/* Load automatically generated extras. */
//加载自动生成的附加内容

static void load_auto(void) {

  u32 i;

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

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

    fd = open(fn, O_RDONLY, 0600);

    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);//从fd中读取最多MAX_AUTO_EXTRA+1个字节放到tmp数组中

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

    if (len >= MIN_AUTO_EXTRA && len <= MAX_AUTO_EXTRA)
      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.");

}

17、maybe_add_auto(//maybe_add_auto的辅助函数)


/* Helper function for maybe_add_auto() */
//maybe_add_auto的辅助函数
static inline u8 memcmp_nocase(u8* m1, u8* m2, u32 len) {

  while (len--) 
  if (tolower(*(m1++)) ^ tolower(*(m2++))) return 1;
  return 0;

}


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

  /* Skip runs of identical bytes. */
	跳过相同字节的运行。
  for (i = 1; i < len; i++)
    if (mem[0] ^ mem[i]) 异或,若相同则跳过循环
     break;

  if (i == len)
   return;

  /* Reject builtin interesting values. */
	拒绝内置interesting的值
  if (len == 2) {

    i = sizeof(interesting_16) >> 1;

    while (i--) 
      if (*((u16*)mem) == interesting_16[i] ||
          *((u16*)mem) == SWAP16(interesting_16[i])) return;

  }

  if (len == 4) {

    i = sizeof(interesting_32) >> 2;

    while (i--) 
      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++)
    if (extras[i].len >= len) break;

  for (; i < extras_cnt && extras[i].len == len; i++)
    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. */
      /*最后但并非最不重要的一点是,检查a_extras[]是否匹配。没有特定排序顺序的保证*/
  auto_changed = 1;

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

    if (a_extras[i].len == len && !memcmp_nocase(a_extras[i].data, mem, len)) {

      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. */
     /*现在看来,我们正在处理一个新条目。那么,让我们添加它如果我们有空位的话,请附上。否则,让我们随机驱逐其他人列表下半部分的条目*/

  if (a_extras_cnt < MAX_AUTO_EXTRAS) {

    a_extras = ck_realloc_block(a_extras, (a_extras_cnt + 1) *
                                sizeof(struct extra_data));

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

  } else {

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

}

18、pivot_inputs(在输出目录中为输入测试用例创建硬链接,选择好名字和相应的旋转)

逻辑上说这个函数就是为input里的test case,在output 里创建hard link

/* 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...");
为所有输入文件创造 hard links
  while (q) {

    u8  *nfn, *rsl = strrchr(q->fname, '/');
    u32 orig_id;

    if (!rsl) rsl = q->fname; else rsl++;

    /* 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. */
/*如果原始文件名符合语法和记录的ID与我们要分配的ID匹配,只需使用原始文件名即可。这对于恢复模糊运行很有价值*/
#ifndef SIMPLE_FILES
#  define CASE_PREFIX "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) {

      u8* src_str;
      u32 src_id;

      resuming_fuzz = 1;
      nfn = alloc_printf("%s/queue/%s", out_dir, rsl);

      /* Since we're at it, let's also try to find parent and figure out the
         appropriate depth for this entry. */
         既然我们已经做到了,让我们尝试发现parent并且弄清楚这个条例的适应深度

      src_str = strchr(rsl + 3, ':');

      if (src_str && sscanf(src_str + 1, "%06u", &src_id) == 1) {

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

      if (use_name) use_name += 6; else use_name = rsl;
      nfn = alloc_printf("%s/queue/id:%06u,orig:%s", out_dir, id, 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);
    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 = q->next;
    id++;

  }

  if (in_place_resume) nuke_resume_dir();

}

19、load_extras()

24、perform_dry_run(对所有测试用例进行试运行,以确认应用程序按照预期。这仅对初始输入执行,并且仅执行一次)

/* 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;设置cal_failures为0
  u8* skip_crashes = getenv("AFL_SKIP_CRASHES");//读取环境变量AFL_SKIP_CRASHES到skip_crashes

  while (q) {//遍历queue

    u8* use_mem;
    u8  res;
    s32 fd;

    u8* fn = strrchr(q->fname, '/') + 1;

    ACTF("Attempting dry run with '%s'...", fn);

    fd = open(q->fname, O_RDONLY);
    if (fd < 0) PFATAL("Unable to open '%s'", q->fname);

    use_mem = ck_alloc_nozero(q->len);

    if (read(fd, use_mem, q->len) != q->len)
      FATAL("Short read from '%s'", q->fname);

    close(fd);

    res = calibrate_case(argv, q, use_mem, 0, 1);//校准测试用例,见下文
    ck_free(use_mem);

    if (stop_soon) return;

    if (res == crash_mode || res == FAULT_NOBITS)//如果res的结果为crash_mode或者FAULT_NOBITS
      SAYF(cGRA "    len = %u, map size = %u, exec speed = %llu us\n" cRST, 
           q->len, q->bitmap_size, q->exec_us);//打印SAYF("len = %u, map size = %u, exec speed = %llu us\n", q->len, q->bitmap_size, q->exec_us);

    switch (res) {//依据res的结果查看是哪种错误并进行判断。一共有以下几种错误类型

      case FAULT_NONE:

        if (q == queue) check_map_coverage();//如果q是头结点,即第一个测试用例,则check_map_coverage,用以评估map coverage
        计数trace_bits发现的路径数,如果小于100,就直接返回
		在trace_bits的数组后半段,如果有值就直接返回。
		抛出警告WARNF("Recompile binary with newer version of afl to improve coverage!")

        if (crash_mode) FATAL("Test case '%s' does *NOT* crash", fn);//如果是crash_mode,则抛出异常,FATAL("Test case '%s' does *NOT* crash", fn);,该文件不崩溃

        break;

      case FAULT_TMOUT:

        if (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. */
           //如果指定了-t参数,则timeout_given值为2

          if (timeout_given > 1) {//如果timeout_given>1
            WARNF("Test case results in a timeout (skipping)");//则警告“测试用例导致超时(跳过)”
            q->cal_failed = CAL_CHANCES;q的cal_failed 为CAL_CHANCES
            cal_failures++;计数器加1
            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;

        if (skip_crashes) {
          WARNF("Test case results in a crash (skipping)");
          q->cal_failed = CAL_CHANCES;
          cal_failures++;
          break;
        }

        if (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 [...] 
#else
               "      ( ulimit -Sd $[%llu << 10]; /path/to/binary [...] 
#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  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  for troubleshooting tips.\n");

        }

        FATAL("Test case '%s' results in a crash", fn);//不管有没有设置mem_limit都会有异常

      case FAULT_ERROR:

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

      case FAULT_NOINST:
			如果没有出现任何路径信息
        FATAL("No instrumentation detected");//报错未检测到仪器

      case FAULT_NOBITS: 
		有路径信息但是没有任何新路径
        useless_at_start++;计数器加一

        if (!in_bitmap && !shuffle_queue)
          WARNF("No new instrumentation output, test case may be useless.");//认为是无用路径

        break;

    }

    if (q->var_behavior) WARNF("Instrumentation output varies across runs.");代表路径的输出可变
如果这个样例q的var_behavior为真,则代表它多次运行,同样的输入条件下,却出现不同的覆盖信息。
    q = q->next;

  }

  if (cal_failures) {

    if (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.");

}

你可能感兴趣的:(学习)