fuzz原始碼閱讀

e*16 a發表於2021-11-29

這個月的目標就是把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了,加深理解
未完待續!!!

相關文章