<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    AFL源碼淺析

    VSole2022-10-26 09:54:13

    前言AFL是一款著名的模糊測試的工具,最近在閱讀AFL源碼,記錄一下,方便以后查閱。

    • 環境? 項目:AFL
    • ? 編譯項目:將編譯的優化選項關閉,即改寫成-O0

    1

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

    2

    • 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;
        }
    ...
    
    • edit_params函數edit_params函數實際就是準備需要傳入編譯器的參數,如編譯器的類型gccclang
    • ? 其次就是是否需要開啟保護如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函數后

    4

    可以傳遞給編譯器的參數增加了-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 <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]); //用于尋找as所在路徑
    
      edit_params(argc, argv);//用于獲取編譯參數
    
      execvp(cc_params[0], (char**)cc_params);//啟動gcc或其他編譯器
    

    大致流程圖

    3

    afl-gcc可以看作是劫持了gcc的一個程序,從而修改as的路徑(為了后續的插樁做準備),并且添加所有fuzzing所需要的參數再傳入實際的編譯器中去(這里以gcc作為例子)

    afl-as.cedit_params函數afl-as.cedit_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_64trampoline_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函數

    函數標簽處的插樁如下圖所示,插樁的位置是函數第一條指令的上方進行插樁

    5

    • ? 情況二:分支跳轉,例如jle指令

    掃描到分支跳轉指令,則直接在跳轉指令下方進行插樁處理,如下圖所示

    6

    • ? 情況三:.L<num>標簽

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

    7

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

    傳入匯編器的參數情況

    8

    大致流程圖

    9

    afl-as相當于劫持了as從而修改匯編的文件名以及對相應的匯編文件進行插樁處理

    afl-as.h該文件放置了插樁需要的代碼如trampoline_fmt_64trampoline_fmt_32main_payload_64以及main_payload_32,這些代碼結合fuzzing過程有關。

    總結afl-gccafl-as可以看作是劫持了編譯器,將fuzzing相關的參數設置好并對編譯文件進行相應的插樁后再調用實際的編譯器

    源碼gcc
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    不編譯的話,就無法編譯生成Pass,你只能利用clang去編譯生成exe,比起VS2019 自帶的cl并沒有明顯優勢,作為一個小白,并不清楚支持llvm的原因。而Pass相當于一個插件,起到中間層實際執行混淆的作用,相當于我們這個功能的核心,所以必須要它。
    對堆題的總體思路
    2023-03-22 09:45:21
    結構體數組在BSS段上,其內容就是堆的地址,也就是堆的指針。說一下堆的理解堆有很多題型 什么堆溢出,off by null , uaf 等。核心的話主要是學思想,所有人都知道我要得到shell,cat flag。但是要怎么去干得有個過程,比如我們做棧題,很容易知道我要劫持棧的返回去執行任意地址,填入shellcode什么的。就是用system去執行/bin/sh。越復雜的問題往往只需要很簡單的道理。這里我用一個很簡單的例子去一步一步簡單剖析。先給出源碼gcc編譯,使用的是Ubuntu18gcc?
    XLL為UDA提供了一個非常有吸引力的選項,因為它們由Microsoft Excel執行,這是客戶端網絡中非常常見的軟件。當然,其缺點是XLL的合法用途很少,因此檢查組織是否通過電子郵件和web下載以阻止該文件擴展名的下載應該是一個非常簡單的實現方式。但不幸的是,許多組織已經落后多年,因此XLL在一段時間內將一直是一種可行的網絡釣魚方法。
    SOCIALNETWORK打靶記錄
    2023-05-29 09:14:26
    對隱藏路徑進行爬取和發現:打開后發現是一個代碼執行頁面 如果過濾不嚴格可能可以通過該功能直接執行代碼操作。通過查看是否存在dockerenv這個文件,如果存在則大概率就是一個docker容器系統。16個網段,存在65535個ip。在kali系統上啟動http程序,在目標靶機上運行wget,將kali上的a程序下載過去。使用賬密進行ssh登錄。
    AFL源碼淺析
    2022-10-26 09:54:13
    前言AFL是一款著名的模糊測試的工具,最近在閱讀AFL源碼,記錄一下,方便以后查閱。編譯項目:將編譯的優化選項關閉,即改寫成-O01afl-gcc.c使用gdb加載afl-gcc,并使用set arg -o test test.c設置參數2find_as函數?find_as函數首先會通過AFL_PATH環境變量的值從而獲得AFL對應的路徑?若上述環境變量不存在則獲取當前afl-gcc所在的文件路徑?判斷該路徑下的as文件是否具有可執行權限u8?//函數用來判斷指定的文件或目錄是否有可執行權限,若指定方式有效則返回0,否則返回-1
    假如想在x86平臺運行arm程序,稱arm為source ISA, 而x86為target ISA, 在虛擬化的角度來說arm就是Guest, x86為Host。這種問題被稱為Code-Discovery Problem。每個體系結構對應的helper函數在target/xxx/helper.h頭文件中定義。
    需要llvm 11+,這是當前afl支持的效率最高的選擇,也意味著編譯要花更長時間。實現了編譯級插樁,效果比匯編級插樁更好。從編譯的實現流程上理解插樁模式差異:afl-gcc插樁分析考慮到afl的插樁方式隨編譯器的選擇而變化,從最簡單的afl-gcc開始入手。
    本題來源于DefCon Quals 2021的mooosl,考察點是最新版本musl libc 1.2.2利用。
    須安裝AFL++,可以使用官方docker鏡像;如果已經在本地安裝,也可直接使用。測試的場景為,非特權用戶輸入惡意構造程序執行參數,引起sudo程序崩潰。sudo程序由root用戶和其他用戶啟動的表現是不同的。sudo的所有權是root,但卻是由普通用戶調用的。argv作為一個指針數組的指針,該指針數組中最后一個指針應為0,其余的每一項為一個字符串指針。注意到afl_init_argv函數中,存在對0x02的判斷,編寫這個文件的作者解釋到,以單獨一個0x02作為參數表
    前言本文通過多個 poc ,結合ftp協議底層和php源碼,分析了在 php 中利用 ftp 偽協議攻擊 php-fpm ,從而繞過 disable_functions 的攻擊方法,并在文末復現了 [藍帽杯 2021]One Pointer PHP 和 [WMCTF2021] Make PHP Great Again And Again。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类