前言
AFL是一款著名的模糊測驗的工具,最近在閱讀AFL原始碼,記錄一下,方便以后查閱,
環境
-
專案:AFL
-
編譯專案:將編譯的優化選項關閉,即改寫成
-O0

afl-gcc.c
使用gdb加載afl-gcc,并使用set arg -o test test.c設定引數

find_as函式
-
find_as函式首先會通過AFL_PATH環境變數的值從而獲得AFL對應的路徑 -
若上述環境變數不存在則獲取當前
afl-gcc所在的檔案路徑 -
判斷該路徑下的
as檔案是否具有可執行權限
u8 *afl_path = getenv("AFL_PATH"); ... if (afl_path) { ? tmp = alloc_printf("%s/as", afl_path); //將AFL所在路徑與字符as進行拼接 ? if (!access(tmp, X_OK)) { //函式用來判斷指定的檔案或目錄是否有可執行權限,若指定方式有效則回傳0,否則回傳-1 as_path = afl_path; ck_free(tmp); return; } ? ck_free(tmp); ? } ? slash = strrchr(argv0, '/'); //在引數argv0所指向的字串中搜索最后一次出現字符'/' ? if (slash) { ? u8 *dir; ? *slash = 0; dir = ck_strdup(argv0); *slash = '/'; ? tmp = alloc_printf("%s/afl-as", dir); //將當前AFL所在的路徑跟afl-as進行拼接 ? if (!access(tmp, X_OK)) { as_path = dir; ck_free(tmp); return; } ...
【----幫助網安學習,以下所有學習資料免費領!加vx:yj009991,備注 “博客園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
edit_params函式
-
edit_params函式實際就是準備需要傳入編譯器的引數,如編譯器的型別gcc或clang -
其次就是是否需要開啟保護如
canary等 -
最后就是判斷是否開啟記憶體泄漏探測的工具,如
ASAN,該工具是針對C/C++ 的快速記憶體錯誤檢測工具
... cc_params = ck_alloc((argc + 128) * sizeof(u8*)); ? name = strrchr(argv[0], '/'); //獲取可執行檔案名稱 if (!name) name = argv[0]; else name++; /*跳過路徑符'/' */ ? if (!strncmp(name, "afl-clang", 9)) { //判斷編譯器是否為clang ... } else { if (!strcmp(name, "afl-g++")) { u8* alt_cxx = getenv("AFL_CXX"); cc_params[0] = alt_cxx ? alt_cxx : (u8*)"g++"; } else if (!strcmp(name, "afl-gcj")) { u8* alt_cc = getenv("AFL_GCJ"); cc_params[0] = alt_cc ? alt_cc : (u8*)"gcj"; } else { u8* alt_cc = getenv("AFL_CC"); cc_params[0] = alt_cc ? alt_cc : (u8*)"gcc"; //如環境變數沒寫入AFL_CC則默認使用gcc } } while (--argc) { u8* cur = *(++argv); //讀取下一個引數 ? if (!strncmp(cur, "-B", 2)) { //若引數是-B ? if (!be_quiet) WARNF("-B is already set, overriding"); //用于設定編譯器的搜索路徑 ? if (!cur[2] && argc > 1) { argc--; argv++; }//繼續讀取下一個引數 continue; ? } ? if (!strcmp(cur, "-integrated-as")) continue; ? if (!strcmp(cur, "-pipe")) continue; ? #if defined(__FreeBSD__) && defined(__x86_64__) if (!strcmp(cur, "-m32")) m32_set = 1; #endif ? if (!strcmp(cur, "-fsanitize=address") || !strcmp(cur, "-fsanitize=memory")) asan_set = 1; //記憶體訪問的錯誤 ? if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;//緩沖區溢位問題的檢查 ? cc_params[cc_par_cnt++] = cur; //cc_params用于存放的引數 ? } ? cc_params[cc_par_cnt++] = "-B"; //引數-B cc_params[cc_par_cnt++] = as_path; //afl-as的路徑 ? if (clang_mode) cc_params[cc_par_cnt++] = "-no-integrated-as"; ? if (getenv("AFL_HARDEN")) { ? cc_params[cc_par_cnt++] = "-fstack-protector-all"; //canary保護 ? if (!fortify_set) cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2"; ? } ? if (asan_set) { ? /* Pass this on to afl-as to adjust map density. */ ? setenv("AFL_USE_ASAN", "1", 1); ? } else if (getenv("AFL_USE_ASAN")) { ? if (getenv("AFL_USE_MSAN")) FATAL("ASAN and MSAN are mutually exclusive"); ? if (getenv("AFL_HARDEN")) FATAL("ASAN and AFL_HARDEN are mutually exclusive"); ? cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; cc_params[cc_par_cnt++] = "-fsanitize=address"; ? } else if (getenv("AFL_USE_MSAN")) { ? if (getenv("AFL_USE_ASAN")) FATAL("ASAN and MSAN are mutually exclusive"); ? if (getenv("AFL_HARDEN")) FATAL("MSAN and AFL_HARDEN are mutually exclusive"); ? cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; cc_params[cc_par_cnt++] = "-fsanitize=memory"; } ... cc_params[cc_par_cnt++] = "-g"; ... 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")) { 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; }
通過edit_params函式后

可以傳遞給編譯器的引數增加了-B . -g -O3 -funroll-loops -D__AFL_COMPILER=1 -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1這幾項
main函式
-
首先呼叫
isatty函式判斷描述符是否為終端機以及是否為靜默模式,即不列印任何資訊,SAYF即輸出函式用于輸出提示字符 -
接著通過
find_as函式搜索as檔案所在的路徑 -
接著通過
edit_params函式編輯獲取需要傳入編譯器的引數 -
最后通過
execvp函式啟動gcc或其他編譯器
/* isatty函式用于判斷檔案描述詞是否是為終端機 獲取AFL_QUIET的環境變數 */ if (isatty(2) && !getenv("AFL_QUIET")) { //判斷是否靜默模式 /* #ifdef MESSAGES_TO_STDOUT # define SAYF(x...) printf(x) #else # define SAYF(x...) fprintf(stderr, x) #endif */ SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <[email protected]>\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]); //用于尋找as所在路徑 ? edit_params(argc, argv);//用于獲取編譯引數 ? execvp(cc_params[0], (char**)cc_params);//啟動gcc或其他編譯器
大致流程圖

afl-gcc可以看作是劫持了gcc的一個程式,從而修改as的路徑(為了后續的插樁做準備),并且添加所有fuzzing所需要的引數再傳入實際的編譯器中去(這里以gcc作為例子)
afl-as.c
edit_params函式
afl-as.c的edit_params函式比較簡單
-
首先是確定
as檔案所在的路徑,若沒有設定環境變數則直接使用as作為匯編器所在路徑的引數 -
其次是檢測
.s檔案是否在臨時目錄下,這里我做了測驗如果.s不在臨時目錄則無法插樁成功 -
最后隨機生成檔案名,將該檔案作為插樁后的檔案并作為傳輸傳入匯編器
u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); //afl-as的地址 ... as_params = ck_alloc((argc + 32) * sizeof(u8*)); //給引數分配空間 ? as_params[0] = afl_as ? afl_as : (u8*)"as"; ? as_params[argc] = 0; //截斷符 ... //用于記錄檔案是64位還是32位 for (i = 1; i < argc - 1; i++) { if (!strcmp(argv[i], "--64")) use_64bit = 1; else if (!strcmp(argv[i], "--32")) use_64bit = 0; ... if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) && strncmp(input_file, "/var/tmp/", 9) && strncmp(input_file, "/tmp/", 5)) pass_thru = 1; //匯編檔案需要放在臨時目錄下,否則后續無法對檔案進行插樁 ? } modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(), (u32)time(NULL)); //隨機生成檔案名,作為插樁的目標檔案 ... as_params[as_par_cnt++] = modified_file; //將待修改的檔案名作為匯編器的引數 as_params[as_par_cnt] = NULL;
add_instrumentation函式
add_instrumentation函式是插樁的關鍵函式
-
首先是分別打開需要編譯的檔案以及存放插樁后的檔案,并且對需要編譯的檔案逐行逐行進行掃描
-
其次對于以下情況的代碼塊不進行插樁處理
-
pass_thru = 1,這里經除錯發現只要.s檔案存在于臨時目錄下pass_thru的值就會為0,pass_thru = 1的意思是只傳遞資料不進行插樁 -
skip_intel = 1即為跳過intel的匯編語法的代碼 -
不在
.text段內 -
在
.text段但是不處于函式標簽或者分支標簽
-
-
trampoline_fmt_64與trampoline_fmt_32即為需要插樁的代碼,并會記錄總共插樁了幾處 -
若進行了插樁處理,那么則需要在檔案末尾插入
main_payload_64,是與afl進行fuzzing相關的函式
...
if (input_file) { //需要編譯的檔案
?
inf = fopen(input_file, "r");
if (!inf) PFATAL("Unable to read '%s'", input_file);
?
} else inf = stdin;
?
outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600); //打開存放插樁后的檔案
?
if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file);
?
outf = fdopen(outfd, "w");
?
if (!outf) PFATAL("fdopen() failed");
while (fgets(line, MAX_LINE, inf)) { //對需要匯編的檔案進行一行一行的掃描
?
/* In some cases, we want to defer writing the instrumentation trampoline
until after all the labels, macros, comments, etc. If we're in this
mode, and if the line starts with a tab followed by a character, dump
the trampoline now. */
?
//isalpha是一種函式:判斷字符ch是否為英文字母
//# define R(x) (random() % (x))
if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&
instrument_next && line[0] == '\t' && isalpha(line[1])) {
?
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
R(MAP_SIZE)); //將插樁代碼寫入改寫檔案中,trampoline_fmt_64為64位程式的插樁代碼,trampoline_fmt_32為32位程式的插樁代碼
?
instrument_next = 0;
ins_lines++; //總共插樁了多少處地方
}
...
if (line[0] == '\t' && line[1] == '.') {
?
/* OpenBSD puts jump tables directly inline with the code, which is
a bit annoying. They use a specific format of p2align directives
around them, so we use that as a signal.
OpenBSD為一個類unix的作業系統
*/
?
if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&
isdigit(line[10]) && line[11] == '\n') skip_next_label = 1; //跳轉到下一個標簽
if (!strncmp(line + 2, "text\n", 5) ||
!strncmp(line + 2, "section\t.text", 13) ||
!strncmp(line + 2, "section\t__TEXT,__text", 21) ||
!strncmp(line + 2, "section __TEXT,__text", 21)) {
instr_ok = 1; //只要是text段就是我們應該插樁的段
continue;
}
?
if (!strncmp(line + 2, "section\t", 8) ||
!strncmp(line + 2, "section ", 8) ||
!strncmp(line + 2, "bss\n", 4) ||
!strncmp(line + 2, "data\n", 5)) {
instr_ok = 0; //不需要插樁的段
continue;
}
?
}
...
if (line[0] == '\t') {//檢測jnz等分支指令
?
if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) { //絕對跳轉jmp不進行插樁處理
?
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
R(MAP_SIZE)); //給分支跳轉指令進行插樁
?
ins_lines++; //插樁的指令數
?
}
continue; //插樁完直接跳過
}
...
if (strstr(line, ":")) { //檢測標簽
?
if (line[0] == '.') {
?
/* Apple: .L<num> / .LBB<num> */
if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) //分支標簽
&& R(100) < inst_ratio) {
...
if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;//若該標簽不需要跳轉則記錄下來,該標簽需要插樁
?
}
?
} else { //函式標簽
?
/* Function label (always instrumented, deferred mode). */
?
instrument_next = 1;//函式標簽都需要進行插樁
}
?
}
?
}
if (ins_lines)
fputs(use_64bit ? main_payload_64 : main_payload_32, outf); //若進行插樁處理則需要插入main_payload_64
這里重點關注一下插樁的位置
-
情況一:函式入口,例如
main函式
函式標簽處的插樁如下圖所示,插樁的位置是函式第一條指令的上方進行插樁

-
情況二:分支跳轉,例如
jle指令
掃描到分支跳轉指令,則直接在跳轉指令下方進行插樁處理,如下圖所示

-
情況三:
.L<num>標簽
.L為本地標簽,afl-as.c也會掃描該標簽并進行插樁處理,可以看到跳轉指令的目的地地址就是以.L<num>,因此.L<num>可以認為分支的起始位置,與函式標簽一樣,會在第一條指令上方進行插樁處理

main函式
main函式主要經過edit_params函式修改了傳入匯編器的引數,并且對匯編檔案進行插樁處理,最后使用execvp函式啟動匯編器進行匯編處理
... gettimeofday(&tv, &tz); ? rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid();//隨機種子 ? srandom(rand_seed);//通過種子生成亂數 ? edit_params(argc, argv); //加載引數,并在/tmp/目錄下生成臨時的匯編檔案 ? if (inst_ratio_str) { ? if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100) FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)"); ? } ? if (getenv(AS_LOOP_ENV_VAR)) FATAL("Endless loop when calling 'as' (remove '.' from your PATH)"); ? setenv(AS_LOOP_ENV_VAR, "1", 1); ? /* When compiling with ASAN, we don't have a particularly elegant way to skip ASAN-specific branches. But we can probabilistically compensate for that... */ ? if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) { sanitizer = 1; inst_ratio /= 3; } ? if (!just_version) add_instrumentation();//對檔案進行插樁處理 ? if (!(pid = fork())) { ? execvp(as_params[0], (char**)as_params);//將插樁后的檔案傳入匯編器中 FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]); ? ...
傳入匯編器的引數情況

大致流程圖

afl-as相當于劫持了as從而修改匯編的檔案名以及對相應的匯編檔案進行插樁處理
afl-as.h
該檔案放置了插樁需要的代碼如trampoline_fmt_64、trampoline_fmt_32、main_payload_64以及main_payload_32,這些代碼結合fuzzing程序有關,
總結
afl-gcc與afl-as可以看作是劫持了編譯器,將fuzzing相關的引數設定好并對編譯檔案進行相應的插樁后再呼叫實際的編譯器,
更多靶場實驗練習、網安學習資料,請點擊這里>>
合天智匯:合天網路靶場、網安實戰虛擬環境
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/519299.html
標籤:其他
