<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速通——流程及afl-fuzz.c源碼簡析

    VSole2022-07-28 16:17:40

    一、source code fuzzing的基本流程

    主要內容是Instrument target和Fuzz本體

    二、Instrument

    根據compiler的選擇不同會影響后續fuzzing效率。

    1、LTO mode (afl-clang-lto/afl-clang-lto++)

    LTO(Link Time Optimization)鏈接時優化是鏈接期間的程序優化,多個中間文件通過鏈接器合并在一起,并將它們組合為一個程序,縮減代碼體積,因此鏈接時優化是對整個程序的分析和跨模塊的優化。

    需要llvm 11+,這是當前afl支持的效率最高的選擇(理論上,實際情況會受未知因素影響,比如fuzzing libxml2的時候),也意味著編譯要花更長時間。

    2、LLVM mode (afl-clang-fast/afl-clang-fast++)

    依賴LLVM的optimizer,穩定性較高的編譯器,用的比較多,可以跨平臺(non-x86)編譯。

    實現了編譯級插樁,效果比匯編級插樁更好。

    3、GCC_PLUGIN mode (afl-gcc-fast/afl-g++-fast)

    效果和LLVM mode差不多,不過依賴的是GCC_plugin,也比較推薦。

    4、GCC mode (afl-gcc/afl-g++) (or afl-clang/afl-clang++ for clang)

    相較其他編譯器,沒別的特色,基本用不到。

    從編譯的實現流程上理解插樁模式差異:

    afl-gcc插樁分析

    考慮到afl的插樁方式隨編譯器的選擇而變化,從最簡單的afl-gcc開始入手。

    先把一個簡單程序用afl-gcc編譯,代碼來源(https://github.com/mykter/afl-training)

    #include #include #include #include  #define INPUTSIZE 100 int process(char *input){    char *out;    char *rest;    int len;    if (strncmp(input, "u ", 2) == 0)    { // upper case command        char *rest;        len = strtol(input + 2, &rest, 10); // how many characters of the string to upper-case        rest += 1;                            // skip the first char (should be a space)        out = malloc(len + strlen(input));    // could be shorter, but play it safe        if (len > (int)strlen(input))        {            printf("Specified length %d was larger than the input!", len);            return 1;        }        else if (out == NULL)        {            printf("Failed to allocate memory");            return 1;        }        for (int i = 0; i != len; i++)        {            char c = rest[i];            if (c > 96 && c < 123) // ascii a-z            {                c -= 32;            }            out[i] = c;        }        out[len] = 0;        strcat(out, rest + len); // append the remaining text        printf("%s", out);        free(out);    }    else if (strncmp(input, "head ", 5) == 0)    { // head command        if (strlen(input) > 6)        {            len = strtol(input + 4, &rest, 10);            rest += 1;          // skip the first char (should be a space)            rest[len] = '\0'; // truncate string at specified offset            printf("%s", rest);        }        else        {            fprintf(stderr, "head input was too small");        }    }    else if (strcmp(input, "surprise!") == 0)    {        // easter egg!        *(char *)1 = 2;    }    else    {        return 1;    }    return 0;} int main(int argc, char *argv[]){    char *usage = "Usage: %s"                  "Text utility - accepts commands and data on stdin and prints results to stdout."                  "\tInput             | Output"                  "\t------------------+-----------------------"                  "\tu      | Uppercased version of the first  bytes of ."                  "\thead   | The first  bytes of .";    char input[INPUTSIZE] = {0};     // Slurp input    if (read(STDIN_FILENO, input, INPUTSIZE) < 0)    {        fprintf(stderr, "Couldn't read stdin.");    }     int ret = process(input);    if (ret)    {        fprintf(stderr, usage, argv[0]);    };    return ret;}
    

    很顯然,只要輸出指定字符串,程序就會訪問到非法內存,同時程序根據輸入頭部的不同產生多個分支,從而測試AFL輸入樣本的變異過程。

    編譯中程序顯示對52處位置進行了插樁。

    把編譯得到的文件丟進IDA,可以發現編譯生成的函數中有多個__afl_maybe_log,顯然他們由afl-gcc的插樁產生。

    當執行到這段代碼,fuzzer知道這段代碼被觸發,從而統計每次輸入樣本的邊緣覆蓋率。

    正常生成可執行文件過程為

    • 預處理:.c生成.i
    • 編譯:.i生成.s,到匯編語言
    • 匯編:.s生成.o,匯編語言到機器語言
    • 鏈接:由.o生成可執行文件

    IR:高級語言到匯編的中間語言,可以解決平臺間的差異。

    llvm負責IR到匯編語言的轉化,并在此過程中進行插樁。

    插樁的代碼執行時與更新共享內存中的執行信息,從而對代碼覆蓋率進行統計。

    使用afl-clang-fast編譯,產生的函數__sanitizer_cov_trace_pc_guard,就是llvm插樁的經典例子。

    ASAN(Address Sanitizer):在數據前后添加禁止訪問區域,訪問到后報錯。

    • 存在一個判斷標準,判斷合法地址和非法地址
    • 每個數據只能寫在給他分配的位置上
    • 會把原本相鄰的變量隔離
    • 需要更長的編譯時間

    三、Fuzz target

    源代碼比較長,我就挑了幾個重要函數的源碼進行分析。

    初始化

    進入main函數,首先獲取時間,循環讀取參數。

    gettimeofday(&tv, &tz);  srandom(tv.tv_sec ^ tv.tv_usec ^ getpid());   while ((opt = getopt(argc, argv, "+i:o:f:m:t:T:dnCB:S:M:x:Q")) > 0)     switch (opt) {       case 'i': /* input dir */         if (in_dir) FATAL("Multiple -i options not supported");        in_dir = optarg;        …………
    

    下面接了一大堆目錄處理和前期檢查的函數。

    setup_shm()

    forkserver、主進程、fork出的子進程間存在共享內存,這段共享內存由內核管理,其中存儲數組,記錄每次樣本執行訪問到的代碼路徑。

    • 如果是forkserver,通過管道通信
    • fork出的子進程通過共享內存傳輸結果

    該函數用于配置共享內存和virgin_bits。

    /s數組定義EXP_ST u8  virgin_bits[MAP_SIZE],     /* Regions yet untouched by fuzzing */           virgin_tmout[MAP_SIZE],    /* Bits we haven't seen in tmouts   */           virgin_crash[MAP_SIZE];    /* Bits we haven't seen in crashes  */EXP_ST void setup_shm(void) {…………  if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE);  memset(virgin_tmout, 255, MAP_SIZE);  memset(virgin_crash, 255, MAP_SIZE);
    

    將三個狀態數組全部初始化為255(0~65535)

    • virgin_bits記錄尚未覆蓋的區域
    • virgin_tmout記錄timeout時的tuple信息
    • virgin_crash記錄crash時的tuple信息
      shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);…………  trace_bits = shmat(shm_id, NULL, 0);
    

    int shmget(key_t key, size_t size, int shmflg)申請共享大小為65536的共享內存。

    第一參數為 IPC_PRIVATE,使用IPC_PRIVATE創建的IPC對象, key值屬性為0,和IPC對象的編號就沒有了對應關系。這樣毫無關系的進程,就不能通過key值來得到IPC對象的編號(因為這種方式創建的IPC對象的key值都是0)。因此,這種方式產生的IPC對象,和無名管道類似,不能用于毫無關系的進程間通信。但也不是一點用處都沒有,仍然可以用于有親緣關系的進程間通信。

    第二參數 MAP_SIZE 為65536,是這一段內存的大小。

    第三參數 IPC_CREAT | IPC_EXCL | 0600,代表這段內存的權限。

    0600權限代表,只有創建者可以進行讀寫

    IPC_CREAT 如果共享內存不存在,則創建一個共享內存,否則打開操作。

    IPC_EXCL 只有在共享內存不存在的時候,新的共享內存才建立,否則就產生錯誤。

    void shmat(int shm_id, const void shm_addr, int shmflg) 訪問共享內存。

    第一參數指定這一段共享內存的id。

    第二參數為NULL一般,shm_addr指定共享內存連接到當前進程中的地址位置,通常為空,表示讓系統來選擇共享內存的地址。

    第三參數shm_flg是一組標志位,通常為0。

    返回一個指向共享內存起始位置的指針,存入trace_bits。

    Fork Server

    forkserver功能

    由主進程創建的子進程,負責fork出fuzz對象,使用pipe和主進程通信。

    管道只能在父子進程間通信,如果要實現祖孫進程通信,需要設置環境變量,孫進程通過環境變量獲取文件描述符。

    調用鏈perform_dry_run(use_argv) -> calibrate_case(argv, q, use_mem, 0, 1) -> init_forkserver(argv)

    perform_dry_run():每個測試用例都執行一次,僅對初始輸入執行一次測試,以確保程序按預期運行。

    calibrate_case():校準一個新的測試用例,只在處理輸入目錄和發現新路徑是執行。

    init_forkserver():用于初始化forkserver。

    1、初始參數中st_pipe[2], ctl_pipe[2]分別為狀態管道和控制管道。

    EXP_ST void init_forkserver(char** argv) {  static struct itimerval it; int st_pipe[2], ctl_pipe[2]; int status; s32 rlen;
    

    2、接著fork出子進程forkserver并使其脫離主進程。

    forksrv_pid = fork();//子進程為forkserver if (forksrv_pid < 0) PFATAL("fork() failed");  //fork失敗 if (!forksrv_pid) {   //forkserver執行 …………  setsid();  //讓子進程完全獨立運行
    

    3、重定向forkserver的stdout、stderr到dev_null_fd。

    視情況重定向stdin

    若定義了out_file,則把stdin重定向到dev_null_fd

    否則關閉out_fd(間接關閉了stdin)

    完成后對FORKSRV_FD和FORKSRV_FD + 1進行重定向。

    linux之dup和dup2函數解析

    https://blog.csdn.net/weixin_30764045/article/details/116936359

    dup2(dev_null_fd, 1);dup2(dev_null_fd, 2);if (out_file) {  dup2(dev_null_fd, 0);} else {  dup2(out_fd, 0);  close(out_fd);}if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed");if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed");
    

    4、執行execv之前還有一系列參數設置,這里先略過,如果execv執行失敗,那么主進程將通過trace_bits = EXEC_FAIL_SIG(位于bitmap)獲得信息。

    execv(target_path, argv); /* Use a distinctive bitmap signature to tell the parent about execv()   falling through. */ *(u32*)trace_bits = EXEC_FAIL_SIG;exit(0);
    

    5、主進程的pipe為fsrv_ctl_fd = ctl_pipe[1]用于寫;fsrv_st_fd = st_pipe[0]用于讀; 設置完成后等待forkserver的返回狀態信號。

    如果長度正好為4(exit),一切正常,直接返回

    否則分類處理異常信號,打印消息并退出

    crash

    timeout

    /* Close the unneeded endpoints. */close(ctl_pipe[0]);close(st_pipe[1]); fsrv_ctl_fd = ctl_pipe[1];fsrv_st_fd  = st_pipe[0];//等待返回消息it.it_value.tv_sec = ((exec_tmout * FORK_WAIT_MULT) / 1000);it.it_value.tv_usec = ((exec_tmout * FORK_WAIT_MULT) % 1000) * 1000; setitimer(ITIMER_REAL, &it, NULL);rlen = read(fsrv_st_fd, &status, 4); it.it_value.tv_sec = 0;it.it_value.tv_usec = 0;setitimer(ITIMER_REAL, &it, NULL);if (rlen == 4) {  OKF("All right - fork server is up.");  return;}
    

    fuzzing策略

    各種初始設置完成后進入while循環,執行fuzzing主程序。

    先來看一個比較重要的數據結構queue_entry的特點。

    • 存儲輸入樣本
    • 存儲每次執行樣本后的基本信息
    • 鏈表連接
    struct queue_entry {   u8* fname;                          /* File name for the test case      */  u32 len;                            /* Input length                     */   u8  cal_failed,                     /* Calibration failed?              */      trim_done,                      /* Trimmed?                         */      was_fuzzed,                     /* Had any fuzzing done yet?        */      passed_det,                     /* Deterministic stages passed?     */      has_new_cov,                    /* Triggers new coverage?           */      var_behavior,                   /* Variable behavior?               */      favored,                        /* Currently favored?               */      fs_redundant;                   /* Marked as redundant in the fs?   */   u32 bitmap_size,                    /* Number of bits set in bitmap     */      exec_cksum;                     /* Checksum of the execution trace  */   u64 exec_us,                        /* Execution time (us)              */      handicap,                       /* Number of queue cycles behind    */      depth;                          /* Path depth                       */   u8* trace_mini;                     /* Trace bytes, if kept             */  u32 tc_ref;                         /* Trace bytes ref count            */   struct queue_entry *next,           /* Next element, if any             */                     *next_100;       /* 100 elements ahead               */ }; static struct queue_entry *queue,     /* Fuzzing queue (linked list)      */                          *queue_cur, /* Current offset within the queue  */                          *queue_top, /* Top of the list                  */                          *q_prev100; /* Previous 100 marker              */static struct queue_entry*  top_rated[MAP_SIZE];                /* Top entries for bitmap bytes     */
    

    其中top_rated里面存放的是bitmap中每個位置當前最短路徑。

    cull_queue()

    功能:每次執行fuzz_one之前,簡化隊列。

    1、如果是dumb_mode或者score_changed為0(即上一次fuzz沒有產生更好的路徑),直接返回。

    if (dumb_mode || !score_changed) return;
    

    2、遍歷隊列,還原favored設置。

    q = queue;while (q) {  q->favored = 0;  q = q->next;}
    

    3、循環取出處于top_rate中并且被temp_v標記的用例,每取出一個,清除temp_v中所有屬于這個entry的bit,并設置它的favored位,令queued_favored,如果這個用例還沒被fuzz過,令pending_favored++,標記優先執行。

    for (i = 0; i < MAP_SIZE; i++)  if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) {    u32 j = MAP_SIZE >> 3;    while (j--)      if (top_rated[i]->trace_mini[j])        temp_v[j] &= ~top_rated[i]->trace_mini[j];     top_rated[i]->favored = 1;    queued_favored++;    if (!top_rated[i]->was_fuzzed) pending_favored++;  }
    

    4、簡化隊列,標記冗余項。

    q = queue;  while (q) {    mark_as_redundant(q, !q->favored);    q = q->next;  }
    

    if (!queue_cur)

    功能:判斷一次循環是否結束,是則初始化隊列。

    queue_cur指向當前隊列中元素,為空說明遍歷到結尾。

    不為空則直接下一步。

    1、記錄輪數、重置狀態。

    queue_cycle++;   current_entry     = 0;  cur_skipped_paths = 0;   queue_cur         = queue;
    

    2、seek_to的值來源于find_start_position(),找到fuzzer重啟后的開始位置。

    (只在fuzzer重啟的第一個循環里用到)這里把queue_cur抬高到seek_to位置,恢復重啟前的狀態。

    while (seek_to) {  current_entry++;  seek_to--;  queue_cur = queue_cur->next;}
    

    3、展示狀態,就是命令行面板,每次狀態更新或在其他狀況下就會調用一次。

    show_stats();
    

    4、非終端模式下輸出循環數。

    if (not_on_tty) {        ACTF("Entering queue cycle %llu.", queue_cycle);        fflush(stdout);      }
    

    5、queue_path不變,說明一整個循環未發現新路徑,設置cycles_wo_finds+1或者use_splicing=1,他注釋說會更換策略,但如果設置了-d參數,其實本來用的就是splicing,直接計數就行,cycles_wo_finds只是根據它的數量判斷現在是否可以結束fuzzing,沒別的影響。

    if (queued_paths == prev_queued) {         if (use_splicing) cycles_wo_finds++; else use_splicing = 1;       } else cycles_wo_finds = 0;      prev_queued = queued_paths;
    

    6、設置prev_queued為上一次的結果。

    prev_queued = queued_paths;
    

    7、如果設置了相關參數,sync_fuzzers()可以從其他fuzzer獲取測試用例。

    if (sync_id && queue_cycle == 1 && getenv("AFL_IMPORT_FIRST"))        sync_fuzzers(use_argv);
    

    關鍵執行函數fuzz_one()

    skipped_fuzz = fuzz_one(use_argv);
    

    終于到了最關鍵的地方。

    fuzz_one從當前隊列中取一個用例執行。

    fuzz成功返回0,跳過或bailed out返回1。

    1、進來先判斷是否有favored, non-fuzzed用例需要執行。

    如果有,則有99%的概率跳過在它之前的用例。

    if (pending_favored){   if ((queue_cur->was_fuzzed || !queue_cur->favored) &&        UR(100) < SKIP_TO_NEW_PROB) return 1;}
    

    2、即使沒有需要優先fuzz的用例,非dumb_mode下,當前用例不是favored,隊列中超過10個元素的情況下:

    當前已運行超過2輪,未被fuzz過的,跳過概率75%(就是說第一二次循環就會跳過很大一部分,這是由于perform_dry_run里已經跑過一輪測試了)。

    否則,跳過概率95%。

    else if (!dumb_mode && !queue_cur->favored && queued_paths > 10) {    if (queue_cycle > 1 && !queue_cur->was_fuzzed) {      if (UR(100) < SKIP_NFAV_NEW_PROB) return 1;    } else {      if (UR(100) < SKIP_NFAV_OLD_PROB) return 1;    }  }
    

    3、直接把當前測試用例映射到內存,提高效率。

    orig_in = in_buf = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
    

    4、out_buf不是從文件讀,這里相當于直接用了malloc(len+1),即使mmap也不能提高效率。

    out_buf = ck_alloc_nozero(len);
    

    fuzz_one CALIBRATION

    if (queue_cur->cal_failed)
    

    只有存在cal_failed被標記才會執行。

    cal_failed在calibrate_case()中,發生以下情況會+1:

    若測試時發生crash_mode(-C設置crash_mode為2,否則為0?)以外的fault。

    非dumb_mode,且第一次測試運行后trace_bits為空。

    同時afl允許我們通過設置,即使發生上述情況,也不在此階段執行CALIBRATION(通過令cal_failed=3)。

    該判定位于perform_dry_run()。

    設置timeout_given =2,則忽略FAULT_TMOUT。

    未設置crash_mode時,設置環境變量AFL_SKIP_CRASHES為1,忽略FAULT_CRASH。

    if (cal_failures == queued_paths)if (cal_failures * 5 > queued_paths)
    

    然而,出現上述問題會使cal_failures++,若報錯比例過高,就會要求你檢查設置。

    回到fuzz_one,若校準錯誤小于3。

    res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0);
    

    讓存在校準問題的用例再次校準。

    • 出現FAULT_ERROR,說明無法運行,直接放棄搶救,報錯。
    • 出現crash_mode,接著往下運行。
    • 其他任何情況都跳過,cur_skipped_paths++。

    fuzz_one TRIMMING

    if (!dumb_mode && !queue_cur->trim_done){ u8 res = trim_case(argv, queue_cur, in_buf); ………… queue_cur->trim_done = 1; if (len != queue_cur->len) len = queue_cur->len;}memcpy(out_buf, in_buf, len);
    

    非dumb_mode且該case尚未trim時執行。

    最后結果存儲在out_buf。

    trim_case(char** argv, struct queue_entry* q, u8* in_buf)

    1、長度小于5直接返回。

    2、len_p2=2^x > q->len

    remove_len取len_p2/16與4的最大值。

    len_p2 = next_p2(q->len);remove_len = MAX(len_p2 / TRIM_START_STEPS, TRIM_MIN_BYTES);
    

    3、循環判斷remove_len是否大于最小步長max(len_p2 /1024,4),滿足則繼續。

    否則跳轉到7。

    while (remove_len >= MAX(len_p2 / TRIM_END_STEPS, TRIM_MIN_BYTES))
    

    4、格式化remove_len到tmp。

    sprintf(tmp, "trim %s/%s", DI(remove_len), DI(remove_len));
    

    5、內部循環,根據 remove_pos, trim_avail生成新case。

    while (remove_pos < q->len){write_with_gap(in_buf, q->len, remove_pos, trim_avail);fault = run_target(argv, exec_tmout);cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);if (cksum == q->exec_cksum){……}else remove_pos += remove_len}
    

    static void write_with_gap(void* mem, u32 len, u32 skip_at, u32 skip_len)

    功能:刪除skip_at開始skip_len長度的內容,新內容存儲于mem(此處為in_buf)。

    運行一次新case,確認當前刪除是否影響bitmap。

    如果不影響,保存這次縮減。

    否則remove_pos后移步長remove_len。

    6、remove_len/2,回到3進行判斷。

    remove_len >>= 1;
    

    7、needs_write為1(在5的if中設置)說明case需要更新,把in_buf內容寫入文件,并更新bitmap信息。

    if (needs_write){ck_write(fd, in_buf, q->len, q->fname);memcpy(trace_bits, clean_trace, MAP_SIZE);update_bitmap_score(q);}
    

    fuzz_one PERFORMANCE SCORE

    1、調用calculate_score(queue_cur)計算當前queue_cur的score。

    2、如果設置了skip_deterministic或者queue_cur->was_fuzzed或者queue_cur->passed_det=1

    如果當前的queue_cur->exec_cksum % master_max不等于master_id - 1

    跳轉havoc_stage

    fuzz_one變異

    考慮到這部分代碼比較長,我主要從功能上入手,結合部分代碼分析。

    變異分為6個階段:

    • SIMPLE BITFLIP (+dictionary construction)階段
    • ARITHMETIC INC/DEC 階段
    • INTERESTING VALUES階段
    • DICTIONARY STUFF階段
    • RANDOM HAVOC階段
    • SPLICING階段

    SIMPLE BITFLIP (+dictionary construction)階段

    按位翻轉,每次都是比特位級別的操作,從 1bit 到 32bit。

    #define FLIP_BIT(_ar, _b) do { \    u8* _arf = (u8*)(_ar); \    u32 _bf = (_b); \    _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \  } while (0)
    

    _ar是操作對象,_br指明操作第幾個字節(_bf) >> 3中的第幾個bit(128 >> ((_bf) & 7))(從高位到低位)。

    一個異或相當于實現了對一個指定bit位的翻轉。

    bitflip 1/1

    for (stage_cur = 0; stage_cur < stage_max; stage_cur++) {stage_cur_byte = stage_cur >> 3;FLIP_BIT(out_buf, stage_cur);if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;FLIP_BIT(out_buf, stage_cur);……
    

    1、第一個翻轉會遍歷case中的每一位,每次翻轉1個bit。

    2、如果翻轉后,common_fuzz_stuff()返回1,就直接跳過整個case,否則把這個bit再翻轉回來。

    3、檢測token并添加。

    maybe_add_auto(a_collect, a_len);
    

    從注釋上理解token得概念:如果在某一段連續bit上進行連續翻轉后,都能讓程序產生新的路徑,就稱連續翻轉的這些bit為一個token。

    common_fuzz_stuff(char** argv, u8* out_buf, u32 len)

    1、用新的case運行程序,獲取fault。

    write_to_testcase(out_buf, len);fault = run_target(argv, exec_tmout);
    

    2、檢測fault值

    if (fault == FAULT_TMOUT) {    if (subseq_tmouts++ > TMOUT_LIMIT) {      cur_skipped_paths++;      return 1;    }  } else subseq_tmouts = 0;
    

    如果FAULT_TMOUT并且subseq_tmouts(fuzz每個case時置零)未超出限制,返回1。

    若不是FAULT_TMOUT,subseq_tmouts=0。

    3、用戶要求進程終止,返回1。

    4、save_if_interesting()。

    5、返回0。

    bitflip 2/1

    每次連續反轉2個bit,步長為1bit。

    bitflip 4/1

    每次連續反轉2個bit,步長為1bit。

    bitflip 8/8

    增加了effector map,每次連續反轉8個bit,步長為8bit。

    與之前找token的方式相似,如果byte翻轉生成了新路徑,就讓這個byte在effector map中位置為1,否則為0。目的也是讓后續變異參考,確認哪些位置是關鍵的參數,繞過無用的數據。

    eff_map[0] = 1;if (EFF_APOS(len - 1) != 0) {  eff_map[EFF_APOS(len - 1)] = 1;  eff_cnt++;}
    

    初始只有第一個、最后一個位置為1。

    if (cksum != queue_cur->exec_cksum) {  eff_map[EFF_APOS(stage_cur)] = 1;  eff_cnt++;}
    

    每次發現新路徑設置1。

    if (eff_cnt != EFF_ALEN(len) &&    eff_cnt * 100 / EFF_ALEN(len) > EFF_MAX_PERC) {  memset(eff_map, 1, EFF_ALEN(len));  blocks_eff_select += EFF_ALEN(len);}
    

    發現有效位超過90%直接全為1。

    注意,如果采用dumb mode或從fuzzer后續不會用到effector map的結果。

    bitflip 16/8

    每次連續反轉16個bit,步長為8bit。

    bitflip 32/8

    每次連續反轉32個bit,步長為8bit。

    ARITHMETIC INC/DEC 階段

    目的是測試易于整數溢出的數據。

    與位翻轉不同,從 8bit 級別開始,而且每次進行的是加減運算操作。

    arith 8/8

    每次對8bit進行加減運算,步長8bit。

    1、case遍歷。

    for (i = 0; i < len; i++){u8 orig = out_buf[i];}
    

    orig為每次操作的位置。

    2、effector map為0直接跳過。

    if (!eff_map[EFF_APOS(i)])
    

    3、循環進行前后異或,一共ARITH_MAX=35輪。

    for (j = 1; j <= ARITH_MAX; j++)
    

    4、org與orig+j進行異或。

    u8 r = orig ^ (orig + j);
    

    5、要求每次產生的case不能與bitflip產生的相同,否則直接跳過。

    通過orig+j的方式生成新的case進行測試。

    if (!could_be_bitflip(r)) {   stage_cur_val = j;  out_buf[i] = orig + j;   if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;  stage_cur++; } else stage_max--;
    

    6、與上一步相似,使用org-j生成新的case進行測試。

    r =  orig ^ (orig - j);if (!could_be_bitflip(r)) {  stage_cur_val = -j;  out_buf[i] = orig - j;  ……} else stage_max--;
    

    7、恢復原case。

    out_buf[i] = orig;
    

    arith 16/8

    每次對16bit進行加減運算,步長8bit,對小端、大端加減法都進行測試。

    arith 32/8

    每次對32bit進行加減運算,步長8bit,對小端、大端加減法都進行測試。

    INTERESTING VALUES階段

    使用“interesting values”對文件內容進行替換,替換內容為一系列確定的值。

    static s8  interesting_8[]  = { INTERESTING_8 };static s16 interesting_16[] = { INTERESTING_8, INTERESTING_16 };static s32 interesting_32[] = { INTERESTING_8, INTERESTING_16, INTERESTING_32 };
    

    interest 8/8

    每次對8bit進行替換變異,步長8bit。

    1、case遍歷。

    for (i = 0; i < len; i++)
    

    2、eff_map檢驗不為0。

    if (!eff_map[EFF_APOS(i)])
    

    3、替換內容遍歷。

    for (j = 0; j < sizeof(interesting_8); j++)
    

    4、要求新case不能被bitfilp和arith生成過。

    if (could_be_bitflip(orig ^ (u8)interesting_8[j]) ||          could_be_arith(orig, (u8)interesting_8[j], 1))
    

    5、樸實無華的執行并恢復原case。

    stage_cur_val = interesting_8[j];out_buf[i] = interesting_8[j];if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry;out_buf[i] = orig;
    

    interest 16/8

    每次對16bit進行替換變異,步長8bit。

    interest 32/8

    每次對32bit進行替換變異,步長8bit。

    DICTIONARY STUFF階段

    用戶提供的字典里有token,用來替換要進行變異的文件內容,如果用戶沒提供就使用 bitflip 自動生成的 token。

    user extras (over)

    以8bit為步長,標記起始位置開始,替換為token。

    1、每個字節都替換一遍。

    for (i = 0; i < len; i++)
    

    2、遍歷用戶字典。

    for (j = 0; j < extras_cnt; j++)
    

    3、出現以下情況,直接下一條token。

    if ((extras_cnt > MAX_DET_EXTRAS && UR(extras_cnt) >= MAX_DET_EXTRAS) ||          extras[j].len > len - i ||          !memcmp(extras[j].data, out_buf + i, extras[j].len) ||          !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, extras[j].len)))
    

    字典token數>200,隨機生成一個小于字典token數,仍>=200。

    替換token后長度超過case原大小。

    case中數據與token一致。

    eff_map為0。

    4、替換,執行。

    last_len = extras[j].len;memcpy(out_buf + i, extras[j].data, last_len); if (common_fuzz_stuff(argv, out_buf, len))
    

    5、所有token結束后恢復,跳回步驟1。

    memcpy(out_buf + i, in_buf + i, last_len);
    

    user extras (insert)

    以8bit為步長,標記起始位置插入token。

    auto extras (over)

    以8bit為步長,標記起始位置開始,替換為在bitflip階段生成的token。

    這是deterministic steps的最后一步。

    if (!queue_cur->passed_det) mark_as_det_done(queue_cur);
    

    我們可以在這里設置完成狀態。

    RANDOM HAVOC階段

    進行很大程度的雜亂破壞,隨機組合,規則比較雜,但目的一致。

    SPLICING階段

    通過將兩個case按一定規則進行拼接,得到一個新case。

    HAVOC和SPLICING是相結合的,拼接case后會回到havoc進行隨機變異。

    char函數進程間通信
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Frida工作原理學習
    2022-07-12 16:28:29
    frida是一款便攜的、自由的、支持全平臺的hook框架,可以通過編寫JavaScript、Python代碼來和frida_server端進行交互,還記得當年用xposed時那種寫了一大堆代碼每次修改都要重新打包安裝重啟手機、那種調試調到頭皮發麻的痛苦,百分之30的時間都是在那里安裝重啟安裝重啟。
    能運行的環境包括I/O,權限控制,系統調用,進程管理,內存管理等多項功能都可以歸結到上邊兩點中。需要注意的是,kernel 的crash 通常會引起重啟。注意大多數的現代操作系統只使用了 Ring 0 和 Ring 3。
    特權模式逃逸和掛載目錄逃逸是最常見的逃逸手法。 特權模式逃逸,也就是熟知的--privileged選項啟動后容器不受seccomp等機制的的限制,常見利用就是掛載根目錄或利用docker.sock創建惡意容器。 而基于容器特權模式逃逸也分不同特權情況,本文總結常見特權模式下不同Capabilities常見對應的攻擊手法。
    也防止有人通過inlinehook 直接hook recv ,recvform,recvmsg 直接在收到數據包的時候被攔截和替換掉。
    根據廠商的要求,在修補后的固件未發布前,我對該漏洞細節進行了保密。若讀者將本文內容用作其他用途,由讀者承擔全部法律及連帶責任,文章作者不承擔任何法律及連帶責任。此時,我們驚喜地發現xxx系列產品的xxx型號固件并沒有被加密,可以成功解開。漏洞分析此部分以xxx固件為例進行分析,該固件是aarch64架構的。其他固件也許架構或部分字段的偏移不同,但均存在該漏洞。找到無鑒權的API接口顯然,此類固件的cgi部分是用Lua所寫的。
    需要llvm 11+,這是當前afl支持的效率最高的選擇,也意味著編譯要花更長時間。實現了編譯級插樁,效果比匯編級插樁更好。從編譯的實現流程上理解插樁模式差異:afl-gcc插樁分析考慮到afl的插樁方式隨編譯器的選擇而變化,從最簡單的afl-gcc開始入手。
    Internet Explorer 11遠程執行代碼漏洞 在野外發現的Internet Explorer的最新零日攻擊利用了舊版JavaScript引擎中的漏洞CVE-2020-0674,CVE-2019-1429,CVE-2019-0676和CVE-2018-8653。相比之下,CVE-2020-1380是中...
    行業湖倉一體的建設方案2020年下半年,我們開始探索解決方案,數據湖進入了我們的視線。不難看出,數據湖與數據倉庫兩者雖然能力互補但卻很難直接合并成一套系統。通過采用基于湖倉一體的冷熱數據分層存儲方案,可以有效降低數據的單位存儲成本。2.異構數據統一元數據管理數據湖通過開放底層文件存儲,給數據入湖帶來了極致的靈活性。進入數據湖的數據可以是結構化的文本,也可以是半結構化的網頁,甚至是完全非結構化的圖片。
    《中華人民共和國數據安全法》于2021年9月1日正式實施,在國家強化數據安全監管的大形勢下,所有企事業單位將面臨著數據安全如何合規合法這一問題。本文試圖站在操作系統角度初步提出數據安全中基于元數據重構的數據標簽解決途徑。
    控制流劫持攻擊是當前較為主流的攻擊方式之一,包括ROP、JOP等等。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类