iT邦幫忙

2022 iThome 鐵人賽

DAY 4
0
Security

模糊測試從入門到放棄系列 第 4

[Day 4] 近代 fuzzer 始祖 - AFL - 總覽 & 編譯

  • 分享至 

  • xImage
  •  

Fuzzing 的概念也許已經存在一陣子,不過近期最有影響性的 fuzzer 應該公認為 AFL,實作直觀並且具有完整的框架,code base 不大之外程式碼也很好看懂,因此接下來的幾天會介紹 AFL 的實作,讓讀者對完整的 fuzzer 實作有更深入的了解。

整個過程可以拆分成兩個部分: 1. 對於 target source code 的處理、2. 執行過程中 fuzzer 與 target 的互動。首先預計會花兩天的時間介紹第一個部分,而在之後花三、四天介紹第二部分。

AFL 對於 target source code 的處理又可以拆成: 1. compile、2. assemble 兩個部分,今天會著重在 compile 的處理。

建立執行環境

首先需要 clone 並且編譯 AFL 相關檔案:

cd ~
git clone https://github.com/google/AFL
cd ~/AFL
make

編譯後會在 ~/AFL 路徑底下產生多個執行檔:

|-- afl-as
|-- afl-clang -> afl-gcc
|-- afl-clang++ -> afl-gcc
|-- afl-fuzz
|-- afl-g++ -> afl-gcc
|-- afl-gcc

之後要跑 AFL 之前需要先編譯你的 target,在此以 test.c 為例,參考註解說明執行以下命令:

# 編譯原始碼
~/AFL/afl-gcc -o test test.c

# 產生 seed
mkdir in && echo "seed" > in/seed

# 建置 linux 環境
echo core | sudo tee /proc/sys/kernel/core_pattern
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# 執行 fuzzer
~/AFL/afl-fuzz -i in -o out ./test

test.c:

#include <stdio.h>
#include <unistd.h>

int main()
{
    int a;
    read(0, &a, 0x2);
    if (a == 0xdead)
        *(int *)0 = 0xdeadbeef;
    return 0;
}

執行 ~/AFL/afl-fuzz -i in -o out ./test 後如果有看到以下畫面,就代表 AFL 有成功跑起來:

https://ithelp.ithome.com.tw/upload/images/20220906/20151153Il8Lnt8OYX.png

Compile

建置環境時我們有使用到 ~/AFL/afl-gcc -o test test.c 來編譯 test.c,實際上程式 afl-gcc 只是一般使用的 C compile gcc 的包裝,目的是要方便使用者使用,也就是最終指令 ~/AFL/afl-gcc XXX YYY 會轉成 gcc XXX YYY ... 去執行,接下來從原始碼 afl-gcc.c 來了解這個機制。

在 afl-gcc 的原始碼當中,最重要的是 function edit_params(),就如同他的名字,是用來更新使用者傳進來的參數,下方為已經加上註解的程式碼,運作原理應該還算是淺顯易懂,一些不重要的程式碼片段已經刪剪或修改,如果有興趣可以找完整版來看:

static void edit_params(u32 argc, char** argv)
{
    u8 asan_set = 0;
    u8 *name;
    
    // cc_params array 會儲存要被執行指令的參數
    cc_params = ck_alloc((argc + 128) * sizeof(u8*));

    // 取得執行檔名稱,最後會得到 "afl-gcc"
    name = strrchr(argv[0], '/');
    if (!name) name = argv[0]; else name++;

    if (!strncmp(name, "afl-clang", 9)) {
        // clang 為另一個 C compiler,在此不討論
    } else {
        // ...
        else {
            // 這邊會把 gcc 加到要被執行指令的參數中
            u8* alt_cc = getenv("AFL_CC");
            cc_params[0] = alt_cc ? alt_cc : (u8*)"gcc";
        }
    }

    while (--argc) {
        // 取出每個執行參數做處理
        u8* cur = *(++argv);
        // 如果發現參數會使用 sanitizer,透過變數做紀錄
        if (!strcmp(cur, "-fsanitize=address") ||
            !strcmp(cur, "-fsanitize=memory")) asan_set = 1;
        cc_params[cc_par_cnt++] = cur;
    }
	
    // -B <path> 可以將 path 加到搜尋 compiler/assembler 的路徑當中
    cc_params[cc_par_cnt++] = "-B";
    cc_params[cc_par_cnt++] = as_path; // AFL 的路徑
	
    // 設置環境變數 AFL_USE_ASAN,代表有使用 sanitizer
    if (asan_set) {
        setenv("AFL_USE_ASAN", "1", 1);
    }

    // 加上額外的優化參數
    cc_params[cc_par_cnt++] = "-g";
    cc_params[cc_par_cnt++] = "-O3";
    cc_params[cc_par_cnt++] = "-funroll-loops";
    cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
    cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";
    cc_params[cc_par_cnt] = NULL;
}

最後變數 cc_params[] 會作為 function execvp() 的參數,執行以下指令:

gcc -o test test.c -B /home/user/AFL -g -O3 -funroll-loops -D__AFL_COMPILER=1 -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1
  • -g: 為產生的執行檔加上 debug info
  • -D: 定義 macro
  • -O3: level3 的 optimization

上一篇
[Day 3] 透過 Sanitizer 偵測程式異常
下一篇
[Day 5] 近代 fuzzer 始祖 - AFL - 插樁 & 組譯
系列文
模糊測試從入門到放棄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言