fuzz源码阅读

发布者:e*16 a
发布于:2021-11-29 22:59

这个月的目标就是把afl代码读完,不着急慢慢看(最快估计一周),大致分为3个模块afl-gcc,afl-as,afl-fuzz
至于为什么要阅读这些源码,大概是为了今后更好的利用和理解fuzz和对以后魔改源码做准备~~~

1.调试afl-gcc.c源码

1.find_as

这里解释一下,as是什么,as是linux下常用的一种汇编器,负责把生成的汇编代码生成二进制代码

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
#define AFL_MAIN
 
#include "config.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
 
static u8*  as_path;                /* Path to the AFL 'as' wrapper      */
static u8** cc_params;              /* Parameters passed to the real CC  */
static u32  cc_par_cnt = 1;         /* Param count, including argv0      */
static u8   be_quiet,               /* Quiet mode                        */
            clang_mode;             /* Invoked as afl-clang*?            */
 
 
/* Try to find our "fake" GNU assembler in AFL_PATH or at the location derived
   from argv[0]. If that fails, abort. */
 
static void find_as(u8* argv0) {  //通过argv[0](当前文件的路径)来寻找对应的汇编器as(linux上as是常用的一个汇编器,负责把生成的汇编代码翻译到二进制)
  u8 *afl_path = getenv("AFL_PATH");  //getenv是搜索其参数所指向的环境字符串
  u8 *slash, *tmp;
 
  if (afl_path) {  //如果获取成功
 
    tmp = alloc_printf("%s/as", afl_path);  //动态分配空间来存储afl_path的路径
 
    if (!access(tmp, X_OK)) {  //如果此路径有可执行权限,就将路径赋值为as_path
      as_path = afl_path;  
      ck_free(tmp);
      return;
    }
 
    ck_free(tmp);   //若不可访问,直接free掉
 
  } ////获取AFL_PATH路径,检验路径是否可以访问
 
  slash = strrchr(argv0, '/');  //strrchr是从argv0的右侧开始查询"/"的出现并向右获取,也就是说如果afl_path没有获取成功的话,就获取当前路径dir
 
  if (slash) {   //若获取到当前路径
    u8 *dir;
    *slash = 0;
    dir = ck_strdup(argv0);  //将argv0所代表的路径复制给dir 
    *slash = '/';
 
    tmp = alloc_printf("%s/afl-as", dir); 
 
    if (!access(tmp, X_OK)) {
      as_path = dir;
      ck_free(tmp);
      return;
    }
 
    ck_free(tmp);
    ck_free(dir);
 
  }   //和上面的函数差不多,如果上面的没获取成功,则读取当前路径,并赋值给as_path,然后free,return,不可访问的话就直接free掉
 
  if (!access(AFL_PATH "/as", X_OK)) {  //若上述两种都没有成功,就找"/as",若可访问,就赋值给as_path,再返回
    as_path = AFL_PATH;
    return;
  }
 
  FATAL("Unable to find AFL wrapper binary for 'as'. Please set AFL_PATH");  //若都没找到,则输出错误信息
 
}

2.edit_params

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
/* Copy argv to cc_params, making the necessary edits. */
 
static void edit_params(u32 argc, char** argv) { 
 
  u8 fortify_set = 0, asan_set = 0
  u8 *name;
 
#if defined(__FreeBSD__) && defined(__x86_64__)
  u8 m32_set = 0;
#endif
 
  cc_params = ck_alloc((argc + 128) * sizeof(u8*));   //为cc_params开辟空间
 
  name = strrchr(argv[0], '/');  //获取右数第一个"/"后的编译器名称赋给name
  if (!name) name = argv[0]; else name++;
 
  if (!strncmp(name, "afl-clang", 9)) {   //如果name是以"afl-clang"开头,进入循环
 
    clang_mode = 1;  
    setenv(CLANG_ENV_VAR, "1", 1);  //设置环境变量  
 
    if (!strcmp(name, "afl-clang++")) {  //如果name是以"afl-clang++"开头,进入循环
      u8* alt_cxx = getenv("AFL_CXX");
      cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++"; //若获取到环境变量则直接将环境变量赋值给cc_params,若没有,将"clang++"赋值给cc_params
    }
    else {                          //如果name不是以"afl-clang++"开头
      u8* alt_cc = getenv("AFL_CC");  //获得环境变量"AFL_CC"
      cc_params[0] = alt_cc ? alt_cc : (u8*)"clang";   //若获取到环境变量则直接将环境变量赋值给cc_params,若没有,将"clang"赋值给cc_params
    }   //cc_params[]是保存编译参数的数组
 
  } else {    //如果name不是以"afl-clang"开头,则进入如下循环
 
#ifdef __APPLE__   //参考漫牛师傅的资料说如果是苹果平台则进入如下分支
 
    if (!strcmp(name, "afl-g++")) cc_params[0] = getenv("AFL_CXX");  //如果name是以"afl-g++"开头,将cc_params赋值为"AFL_CXX"的环境变量
    else if (!strcmp(name, "afl-gcj")) cc_params[0] = getenv("AFL_GCJ");  //如果name是以"afl-gcj"开头,将cc_params赋值为"AFL_GCJ"的环境变量
    else cc_params[0] = getenv("AFL_CC"); //如果name的值不是如上两个,就赋值为"AFL_CC"的环境变量
 
    if (!cc_params[0]) {  //若cc_params值为0,则提示Mac下要有限使用afl-clang,如果要使用aflgcc需要配置路径
 
      SAYF("\n" cLRD "[-] " cRST
           "On Apple systems, 'gcc' is usually just a wrapper for clang. Please use the\n"
           "    'afl-clang' utility instead of 'afl-gcc'. If you really have GCC installed,\n"
           "    set AFL_CC or AFL_CXX to specify the correct path to that compiler.\n");
 
      FATAL("AFL_CC or AFL_CXX required on MacOS X");
 
    }
 
#else   //若不是Apple平台
 
    if (!strcmp(name, "afl-g++")) {  //如果name是"afl-g++",则将alt_cxx赋值为"AFL_CXX"的环境变量
      u8* alt_cxx = getenv("AFL_CXX");
      cc_params[0] = alt_cxx ? alt_cxx : (u8*)"g++"; //如果获取到值则直接将环境变量值付给cc_params[0],如果没有获取到则直接将字符串“g++”付给cc_params[0]
    } else if (!strcmp(name, "afl-gcj")) {  //如果name是"afl-gcj",则将alt_cxx赋值为"AFL-GCJ"的环境变量
      u8* alt_cc = getenv("AFL_GCJ");
      cc_params[0] = alt_cc ? alt_cc : (u8*)"gcj"; //如果获取到值则直接将环境变量值付给cc_params[0],如果没有获取到则直接将字符串“gcj”付给cc_params[0]
    } else {
      u8* alt_cc = getenv("AFL_CC");  //获取AFL_CC环境变量
      cc_params[0] = alt_cc ? alt_cc : (u8*)"gcc";   //如果获取到值则直接将环境变量值付给cc_params[0],如果没有获取到则直接将字符串“gcc”付给cc_params[0]
    }
 
#endif /* __APPLE__ */
 
  }
 
  while (--argc) {   //参数--
    u8* cur = *(++argv);
 
    if (!strncmp(cur, "-B", 2)) {   //如果cur为"-B"
 
      if (!be_quiet) WARNF("-B is already set, overriding");  //如果静默模式已经关闭,则输出信息
 
      if (!cur[2] && argc > 1) { argc--; argv++; } 
      continue;
 
    }
 
    if (!strcmp(cur, "-integrated-as")) continue//当前参数为"-integrated-as"时跳过本次循环
 
    if (!strcmp(cur, "-pipe")) continue//当前参数为"-pipe"时跳过本次循环
 
#if defined(__FreeBSD__) && defined(__x86_64__) 
    if (!strcmp(cur, "-m32")) m32_set = 1;   //判断当前参数为“-m32”时,设置m32_set标志参数为1
#endif
 
    if (!strcmp(cur, "-fsanitize=address") ||               
        !strcmp(cur, "-fsanitize=memory")) asan_set = 1;     /*判断当前参数为"-fsanitize=address"或"-
fsanitize=memory"时,并设置asan_set标志参数为1(这两个参数为了告诉gcc要检查内存访问错误)  */
 
    if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1//判断当前参数为“FORTIFY_SOURCE”时,设置fortify_set标志参数为1(此参数为fortify保护是否开启)
 
    cc_params[cc_par_cnt++] = cur;
 
  }
 
  cc_params[cc_par_cnt++] = "-B";     
  cc_params[cc_par_cnt++] = as_path;  //取出find_as()函数中找到的as_path,组成"-B as_path"
 
  if (clang_mode)  //若clang_mode为1
    cc_params[cc_par_cnt++] = "-no-integrated-as";      //赋值cc_params追加参数"-no-integrated-as"
 
  if (getenv("AFL_HARDEN")) {  //如果可以获取到该环境变量,进入分支
 
    cc_params[cc_par_cnt++] = "-fstack-protector-all"
 
    if (!fortify_set)   //检查是否设置fortify参数,如果没有,进入分支
      cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
 
  }
 
  if (asan_set) {   //判断是否检查内存,如果已经设置为1,进入循环
 
    setenv("AFL_USE_ASAN", "1", 1);  //设置环境变量为1
 
  } else if (getenv("AFL_USE_ASAN")) {  //如果"AFL_USE_ASAN"被设为1,进入循环
 
    if (getenv("AFL_USE_MSAN"))  //判断"AFL_USE_MSAN"是否是1,若是则进入循环
      FATAL("ASAN and MSAN are mutually exclusive");  //会提示ASAN和MSAN存在互斥
 
    if (getenv("AFL_HARDEN"))   //判断"AFL_HARDEN"是否为1,若是则进入循环
      FATAL("ASAN and AFL_HARDEN are mutually exclusive");   //会提示ASAN和AFL_HARDEN存在互斥
 
    cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"
    cc_params[cc_par_cnt++] = "-fsanitize=address"//若上述两个环境变量都没有设置,则追加这两个参数
 
  } else if (getenv("AFL_USE_MSAN")) {    //如果"AFL_USE_MSAN"1,进入循环
 
    if (getenv("AFL_USE_ASAN"))   //判断"AFL_USE_ASAN"是否为1,若是则进入循环
      FATAL("ASAN and MSAN are mutually exclusive");  //会提示ASAN和MSAN是互斥的
 
    if (getenv("AFL_HARDEN"))   //判断"AFL_HARDEN"是否为1,若是则进入循环
      FATAL("MSAN and AFL_HARDEN are mutually exclusive");  //会提示MASN和AFL_HARDEN存在互斥
 
    cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
    cc_params[cc_par_cnt++] = "-fsanitize=memory"//若上述两个环境变量都没有设置,则追加这两个参数
 
 
  }
 
  if (!getenv("AFL_DONT_OPTIMIZE")) {  //如果没有成功获取"AFL_DONT_OPTIMIZE"环境变量,就进入分支
 
#if defined(__FreeBSD__) && defined(__x86_64__)
 
    /* On 64-bit FreeBSD systems, clang -g -m32 is broken, but -m32 itself
       works OK. This has nothing to do with us, but let's avoid triggering
       that bug. */
 
    if (!clang_mode || !m32_set)  //若没有设置clang_mode模式或者没有加m32,则进入分支
      cc_params[cc_par_cnt++] = "-g";   //设置-g参数
 
#else  //若不是_FreeBSD和__x86_64__系统,则进入分支
 
      cc_params[cc_par_cnt++] = "-g"//追加参数-g
 
#endif  
 
    cc_params[cc_par_cnt++] = "-O3";
    cc_params[cc_par_cnt++] = "-funroll-loops";
 
    /* Two indicators that you're building for fuzzing; one of them is
       AFL-specific, the other is shared with libfuzzer. */
 
    cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
    cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";
 
  }
 
  if (getenv("AFL_NO_BUILTIN")) {    //如果能获取到"AFL_NO_BUILTIN"环境变量
 
    cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
    cc_params[cc_par_cnt++] = "-fno-builtin-strstr";
    cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr";
 
  //追加参数
 
  cc_params[cc_par_cnt] = NULL;  //表示数组结束
 
}

3.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
int main(int argc, char** argv) { 
 
  if (isatty(2) && !getenv("AFL_QUIET")) {
 
    SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
 
  } else be_quiet = 1;
 
  if (argc < 2) {
 
    SAYF("\n"
         "This is a helper application for afl-fuzz. It serves as a drop-in replacement\n"
         "for gcc or clang, letting you recompile third-party code with the required\n"
         "runtime instrumentation. A common use pattern would be one of the following:\n\n"
 
         "  CC=%s/afl-gcc ./configure\n"
         "  CXX=%s/afl-g++ ./configure\n\n"
 
         "You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.\n"
         "Setting AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
         BIN_PATH, BIN_PATH);
 
    exit(1);
 
  }
 
  find_as(argv[0]);  //上面已经分析
 
  edit_params(argc, argv);  //上面也已经分析
 
  execvp(cc_params[0], (char**)cc_params);  //execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名, 找到后便执行该文件, 然后将第二个参数argv 传给该欲执行的文件。
//调用该函数执行afl-gcc( cc_params[0]为编译器,(char**)cc_params为编译器参数 )
  FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
 
  return 0;
 
}

2.调试afl-fuzz

(⊙o⊙)… 大概8000多行代码,看到这么多代码头都大了,也是第一次分析
我是用的gdb+source insight4.0+大佬分析的文章来进行一个大致浏览

1.setup_signal_handlers

该函数是注册四个信号处理函数handle_stop_sig,handle_timeout,handle_resize,handle_skipreq

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
EXP_ST void setup_signal_handlers(void)
{
 
  struct sigaction sa;
 
  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);   //SIGHUP信号是终端结束时发出
  sigaction(SIGINT, &sa, NULL);   //SIGINT信号是ctrl+c
  sigaction(SIGTERM, &sa, NULL);  //SIGTERM信号是通过kill产生,比较友好,不像SIGKILL直接关闭进程
 
  /* Exec timeout notifications. */
 
  sa.sa_handler = handle_timeout;  //注册超时处理函数
  sigaction(SIGALRM, &sa, NULL);  //SIGALRM信号是定时器终止时发送给进程的信号
 
  /* Window resize */
 
  sa.sa_handler = handle_resize;  //注册窗口变化处理函数
  sigaction(SIGWINCH, &sa, NULL); //SIGWINCH信号是窗口大小改变时发出
 
  /* SIGUSR1: skip entry */
 
  sa.sa_handler = handle_skipreq;  //注册跳过用户请求函数
  sigaction(SIGUSR1, &sa, NULL);   //SIGUSR1信号是留给用户使用的信号
 
  /* Things we don't care about. */
 
  sa.sa_handler = SIG_IGN;     //用SIG_IGN表示忽略SIGTSTP和SIGPIPE信号
  sigaction(SIGTSTP, &sa, NULL);
  sigaction(SIGPIPE, &sa, NULL);
}

2.check_asan_opts

该函数的主要作用是读取环境变量"ASAN_OPTIONS"和"MSAN_OPTIONS"

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
static void check_asan_opts(void)
{
  u8 *x = getenv("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");
 
  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!");
  }
}

3.fix_up_sync

该函数是定义了一些参数冲突和一些参数的要求啥的

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
static void fix_up_sync(void)
{
 
  u8 *x = sync_id;
 
  if (dumb_mode)
    FATAL("-S / -M and -n are mutually exclusive");
 
  if (skip_deterministic)
  {
 
    if (force_deterministic)
      FATAL("use -S instead of -M -d");
    else
      FATAL("-S already implies -d");
  }
 
  while (*x)
  {
 
    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");
 
  x = alloc_printf("%s/%s", out_dir, sync_id);
 
  sync_dir = out_dir;
  out_dir = x;
 
  if (!force_deterministic)
  {
    skip_deterministic = 1;
    use_splicing = 1;
  }
}

4.save_cmdline

该函数的作用是将命令行参数拷贝到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
25
static void save_cmdline(u32 argc, char **argv)
{
 
  u32 len = 1, i;
  u8 *buf;
 
  for (i = 0; i < argc; i++)
    len += strlen(argv[i]) + 1;
 
  buf = orig_cmdline = ck_alloc(len);
 
  for (i = 0; i < argc; i++)
  {
 
    u32 l = strlen(argv[i]);
 
    memcpy(buf, argv[i], l);
    buf += l;
 
    if (i != argc - 1)
      *(buf++) = ' ';
  }
 
  *buf = 0;
}

5.fix_up_banner

创建一个运行中的banner

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 void fix_up_banner(u8 *name)
{
 
  if (!use_banner)
  {
 
    if (sync_id)
    {
      use_banner = sync_id;
    }
    else
    {
 
      u8 *trim = strrchr(name, '/');  
      if (!trim)
        use_banner = name;
      else
        use_banner = trim + 1;
    }
  }
 
  if (strlen(use_banner) > 40)
  {
 
    u8 *tmp = ck_alloc(44);
    sprintf(tmp, "%.40s...", use_banner);
    use_banner = tmp;
  }
}

6.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
24
25
/* Check if we're on TTY. */
static void check_if_tty(void)
{
 
  struct winsize ws;
 
  if (getenv("AFL_NO_UI"))
  {
    OKF("Disabling the UI because AFL_NO_UI is set.");
    not_on_tty = 1;
    return;
  }
 
  if (ioctl(1, TIOCGWINSZ, &ws))
  {
 
    if (errno == ENOTTY)
    {
      OKF("Looks like we're not running on a tty, so I'll be a bit less verbose.");
      not_on_tty = 1;
    }
 
    return;
  }
}

7.get_core_count

计算cpu核心的数量

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
static void get_core_count(void)
{
 
  u32 cur_runnable = 0;
 
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
 
  size_t s = sizeof(cpu_core_count);
 
  /* On *BSD systems, we can just use a sysctl to get the number of CPUs. */
 
#ifdef __APPLE__
 
  if (sysctlbyname("hw.logicalcpu", &cpu_core_count, &s, NULL, 0) < 0)
    return;
 
#else
 
  int s_name[2] = {CTL_HW, HW_NCPU};
 
  if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0)
    return;
 
#endif /* ^__APPLE__ */
 
#else
 
#ifdef HAVE_AFFINITY
 
  cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN);
 
#else
 
  FILE *f = fopen("/proc/stat", "r");
  u8 tmp[1024];
 
  if (!f)
    return;
 
  while (fgets(tmp, sizeof(tmp), f))
    if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3]))
      cpu_core_count++;
 
  fclose(f);
 
#endif /* ^HAVE_AFFINITY */
 
#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */
 
  if (cpu_core_count > 0)
  {
 
    cur_runnable = (u32)get_runnable_processes();
 
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
 
    /* Add ourselves, since the 1-minute average doesn't include that yet. */
 
    cur_runnable++;
 
#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */
 
    OKF("You have %u CPU core%s and %u runnable tasks (utilization: %0.0f%%).",
        cpu_core_count, cpu_core_count > 1 ? "s" : "",
        cur_runnable, cur_runnable * 100.0 / cpu_core_count);
 
    if (cpu_core_count > 1)
    {
 
      if (cur_runnable > cpu_core_count * 1.5)
      {
 
        WARNF("System under apparent load, performance may be spotty.");
      }
      else if (cur_runnable + 1 <= cpu_core_count)
      {
 
        OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path);
      }
    }
  }
  else
  {
 
    cpu_core_count = 0;
    WARNF("Unable to figure out the number of CPU cores.");
  }
}

8.check_crash_handling

确保核心转储不会进入另外一个程序运行

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
static void check_crash_handling(void)
{
 
#ifdef __APPLE__
 
  /* Yuck! There appears to be no simple C API to query for the state of
     loaded daemons on MacOS X, and I'm a bit hesitant to do something
     more sophisticated, such as disabling crash reporting via Mach ports,
     until I get a box to test the code. So, for now, we check for crash
     reporting the awful way. */
 
  if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash$'"))
    return;
 
  SAYF("\n" cLRD "[-] " cRST
       "Whoops, your system is configured to forward crash notifications to an\n"
       "    external crash reporting utility. This will cause issues due to the\n"
       "    extended delay between the fuzzed binary malfunctioning and this fact\n"
       "    being relayed to the fuzzer via the standard waitpid() API.\n\n"
       "    To avoid having crashes misinterpreted as timeouts, please run the\n"
       "    following commands:\n\n"
 
       "    SL=/System/Library; PL=com.apple.ReportCrash\n"
       "    launchctl unload -w ${SL}/LaunchAgents/${PL}.plist\n"
       "    sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist\n");
 
  if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES"))
    FATAL("Crash reporter detected");
 
#else
 
  /* This is Linux specific, but I don't think there's anything equivalent on
     *BSD, so we can just let it slide for now. */
 
  s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY);
  u8 fchar;
 
  if (fd < 0)
    return;
 
  ACTF("Checking core_pattern...");
 
  if (read(fd, &fchar, 1) == 1 && fchar == '|')
  {
 
    SAYF("\n" cLRD "[-] " cRST
         "Hmm, your system is configured to send core dump notifications to an\n"
         "    external utility. This will cause issues: there will be an extended delay\n"
         "    between stumbling upon a crash and having this information relayed to the\n"
         "    fuzzer via the standard waitpid() API.\n\n"
 
         "    To avoid having crashes misinterpreted as timeouts, please log in as root\n"
         "    and temporarily modify /proc/sys/kernel/core_pattern, like so:\n\n"
 
         "    echo core >/proc/sys/kernel/core_pattern\n");
 
    if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES"))
      FATAL("Pipe at the beginning of 'core_pattern'");
  }
 
  close(fd);
 
#endif /* ^__APPLE__ */
}

9.check_cpu_governor

检查cpu管理者,我也不太理解呜呜呜

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
static void check_cpu_governor(void)
{
 
  FILE *f;
  u8 tmp[128];
  u64 min = 0, max = 0;
 
  if (getenv("AFL_SKIP_CPUFREQ"))
    return;
 
  f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r");
  if (!f)
    return;
 
  ACTF("Checking CPU scaling governor...");
 
  if (!fgets(tmp, 128, f))
    PFATAL("fgets() failed");
 
  fclose(f);
 
  if (!strncmp(tmp, "perf", 4))
    return;
 
  f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r");
 
  if (f)
  {
    if (fscanf(f, "%llu", &min) != 1)
      min = 0;
    fclose(f);
  }
 
  f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r");
 
  if (f)
  {
    if (fscanf(f, "%llu", &max) != 1)
      max = 0;
    fclose(f);
  }
 
  if (min == max)
    return;
 
  SAYF("\n" cLRD "[-] " cRST
       "Whoops, your system uses on-demand CPU frequency scaling, adjusted\n"
       "    between %llu and %llu MHz. Unfortunately, the scaling algorithm in the\n"
       "    kernel is imperfect and can miss the short-lived processes spawned by\n"
       "    afl-fuzz. To keep things moving, run these commands as root:\n\n"
 
       "    cd /sys/devices/system/cpu\n"
       "    echo performance | tee cpu*/cpufreq/scaling_governor\n\n"
 
       "    You can later go back to the original state by replacing 'performance' with\n"
       "    'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ\n"
       "    to make afl-fuzz skip this check - but expect some performance drop.\n",
       min / 1024, max / 1024);
 
  FATAL("Suboptimal CPU scaling governor");
}

10.setup_shm

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
EXP_ST void setup_shm(void)
{
 
  u8 *shm_str;
 
  if (!in_bitmap) 
    memset(virgin_bits, 255, MAP_SIZE); 
 
  memset(virgin_tmout, 255, MAP_SIZE);
  memset(virgin_crash, 255, MAP_SIZE);
  //上面都是初始化变量
  shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600); //创建共享内存,大小为MAP_SIZE
    //IPC_EXCL:只有共享内存不存在时,才会创建新的,否则发生错误
 
  if (shm_id < 0)
    PFATAL("shmget() failed");
 
  atexit(remove_shm); 
 
  shm_str = alloc_printf("%d", shm_id);
 
  /* 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)  //非dump_mode模式下
    setenv(SHM_ENV_VAR, shm_str, 1);
 
  ck_free(shm_str);
 
  trace_bits = shmat(shm_id, NULL, 0);  //启动对该共享内存的访问,并将其第一字节的指针返回给trace_bits
 
  if (trace_bits == (void *)-1)
    PFATAL("shmat() failed");
}

11.init_count_class16

初始化数组

1
2
3
4
5
6
7
8
9
10
11
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[(b1 << 8) + b2] =
          (count_class_lookup8[b1] << 8) |
          count_class_lookup8[b2];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
static const u8 count_class_lookup8[256] = {
 
    [0] = 0,
    [1] = 1,
    [2] = 2,
    [3] = 4,
    [4 ... 7] = 8,
    [8 ... 15] = 16,
    [16 ... 31] = 32,
    [32 ... 127] = 64,
    [128 ... 255] = 128
 
};

trace_bits是用于记录是否到达该路径以及到达的次数,到达的次数用count_class_lookup8/count_class_lookup16进行等级划分,并通过等级来体现是否存在路径更新

12.setup_dirs_fds

设置输出目录和文件描述符

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
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不为空,就建立输出文件夹
    PFATAL("Unable to create '%s'", sync_dir);
 
  if (mkdir(out_dir, 0700))  //sync不为空的情况下,创建文件夹,创建失败返回-1,进入失败分支
  {
 
    if (errno != EEXIST)
      PFATAL("Unable to create '%s'", out_dir);
 
    maybe_delete_out_dir();
  }
  else                    //创建成功返回0,进入成功分支
  {
 
    if (in_place_resume)
      FATAL("Resume attempted but old output directory not found");
 
    out_dir_fd = open(out_dir, O_RDONLY);   //打开输出文件夹目录
 
#ifndef __sun
 
    if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB))   //
      PFATAL("Unable to flock() output directory.");
 
#endif /* !__sun */
  }
 
  /* Queue directory for any starting & discovered paths. */
 
  tmp = alloc_printf("%s/queue", out_dir); 
  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);  //保存queue数据并用于恢复会话和相关任务
  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); //用于标记过去经过确定性模糊处理的队列条目的目录
  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);  //自动选择的字典目录
  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);  //当前被视为冗杂的路径集
  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);  //显示可变行为的路径集
  if (mkdir(tmp, 0700))
    PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);
 
  /* Sync directory for keeping track of cooperating fuzzers. */
 
  if (sync_id)   //创建目录,用于跟踪cooperating fuzzers
  {
 
    tmp = alloc_printf("%s/.synced/", out_dir);
 
    if (mkdir(tmp, 0700) && (!in_place_resume || errno != EEXIST))
      PFATAL("Unable to create '%s'", tmp);
 
    ck_free(tmp);
  }
 
  /* All recorded crashes. */
 
  tmp = alloc_printf("%s/crashes", out_dir);  //创建crashes崩溃目录
  if (mkdir(tmp, 0700))
    PFATAL("Unable to create '%s'", tmp);
  ck_free(tmp);
 
  /* All recorded hangs. */
 
  tmp = alloc_printf("%s/hangs", 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);
  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. */
 
  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");
  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 */
}

13.read_testcases

从输入文件中读取testcases,排成队列用于测试

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
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.txt"))
    {
 
      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;
}

14.add_to_queue

把新的testcase添加到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
static void add_to_queue(u8 *fname, u32 len, u8 passed_det)
{
 
  struct queue_entry *q = ck_alloc(sizeof(struct 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++;
 
  cycles_wo_finds = 0;
 
  /* Set next_100 pointer for every 100th element (index 0, 100, etc) to allow faster iteration. */
  if ((queued_paths - 1) % 100 == 0 && queued_paths > 1)
  {
 
    q_prev100->next_100 = q;
    q_prev100 = q;
  }
 
  last_path_time = get_cur_time();
}

15.load_auto

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

今天就分析到这里,由于源码太多太长,加上自己没有对fuzz有更深的理解,所以写出来的注释可能会有错误,接下来几天应该是会一边阅读源码,一边进行fuzzing了,加深理解
未完待续!!!


声明:该文观点仅代表作者本人,转载请注明来自看雪