# honggfuzz笔记记录 ## 2020-05-18-honggfuzz const_feedback特性分析 ### 前言 本次调研的honggfuzz版本为2.2(74e7bc161662269b6ff4cdb3e2fdd2ad0bebd69b)。 ### 使用介绍 ### 安装 ```shell $ git clone https://github.com/google/honggfuzz.git $ apt-get update -y && apt-get install -y libbfd-dev libunwind-dev libblocksruntime-dev liblzma-dev $ CFLAGS="-O3" make ``` ### 运行 以fuzz libpng 为例 编译 libpng ```shell $ sudo apt-get install -y libghc-zlib-dev $ wget https://downloads.sourceforge.net/project/libpng/libpng12/older-releases/1.2.56/libpng-1.2.56.tar.gz $ tar xf libpng-1.2.56.tar.gz $ cd libpng-1.2.56 && ./configure && make ``` 编译 fuzz-target , 这里的 target.cc 使用的是 [fuzzbench 中的实现](https://github.com/google/fuzzbench/blob/master/benchmarks/libpng-1.2.56/target.cc) ```shell $ ../../../hfuzz_cc/hfuzz-clang++ -stdlib=libc++ -pthread -Wl,--no-as-needed -Wl,-ldl -Wl,-lm -Wno-unused-command-line-argument -O3 -std=c++11 target.cc build/.libs/libpng12.a -I build/ -lz -o ./build/fuzz-target ``` 开始fuzz ```shell $ ../../honggfuzz -P --rlimit_rss 2048 --sanitizers_del_report=true -i input/ -o output/ --dict libpng.dict -- ./target ``` ### 反馈驱动的fuzz 使用 hfuzz-clang 编译目标文件代码时,`-fsanitize-coverage = trace-pc-guard,indirect-calls,trace-cmp` 标志集将自动添加到 clang 的命令行中。新版本中引入了与 trace-cmp 相关的新变异策略,本次重点对其介绍。 #### trace-cmp 包含此标志时,编译器在比较指令和switch指令之前插入如下代码: ```c /* Standard __sanitizer_cov_trace_cmp wrappers */ void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2) void __sanitizer_cov_trace_cmp2(uint16_t Arg1, uint16_t Arg2) void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2) void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) /* Standard __sanitizer_cov_trace_const_cmp wrappers */ void __sanitizer_cov_trace_const_cmp1(uint8_t Arg1, uint8_t Arg2) void __sanitizer_cov_trace_const_cmp2(uint16_t Arg1, uint16_t Arg2) void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2) void __sanitizer_cov_trace_const_cmp8(uint64_t Arg1, uint64_t Arg2) ``` 之后会调用`instrumentAddConstMem()`或 `instrumentAddConstMemInternal()`函数将比较的常量值存入`globalCmpFeedback`中: ```c static inline void instrumentAddConstMemInternal(const void* mem, size_t len) { if (len == 0) { return; } if (len > sizeof(globalCmpFeedback->valArr[0].val)) { len = sizeof(globalCmpFeedback->valArr[0].val); } uint32_t curroff = ATOMIC_GET(globalCmpFeedback->cnt); if (curroff >= ARRAYSIZE(globalCmpFeedback->valArr)) { return; } for (uint32_t i = 0; i < curroff; i++) {//若该常量已存在在列表中,跳过 if ((len == ATOMIC_GET(globalCmpFeedback->valArr[i].len)) && libc_memcmp(globalCmpFeedback->valArr[i].val, mem, len) == 0) { return; } } uint32_t newoff = ATOMIC_POST_INC(globalCmpFeedback->cnt); if (newoff >= ARRAYSIZE(globalCmpFeedback->valArr)) { ATOMIC_SET(globalCmpFeedback->cnt, ARRAYSIZE(globalCmpFeedback->valArr)); return; } memcpy(globalCmpFeedback->valArr[newoff].val, mem, len); ATOMIC_SET(globalCmpFeedback->valArr[newoff].len, len); wmb(); } ``` `globalCmpFeedback`结构体定义如下: ```c typedef struct { uint32_t cnt;//存储常量值个数 struct { uint8_t val[32];//常量值 uint32_t len; //常量值长度 } valArr[1024 * 16]; } cmpfeedback_t; ``` 最后调用 `hfuzz_trace_cmpx_internal`更新 ```c HF_REQUIRE_SSE42_POPCNT static inline void hfuzz_trace_cmp1_internal( uintptr_t pc, uint8_t Arg1, uint8_t Arg2) { uintptr_t pos = pc % _HF_PERF_BITMAP_SIZE_16M; register uint8_t v = ((sizeof(Arg1) * 8) - __builtin_popcount(Arg1 ^ Arg2)); uint8_t prev = ATOMIC_GET(globalCovFeedback->bbMapCmp[pos]); if (prev < v) { ATOMIC_SET(globalCovFeedback->bbMapCmp[pos], v); ATOMIC_POST_ADD(globalCovFeedback->pidNewCmp[my_thread_no], v - prev); wmb(); } } ``` ### 启动流程 从honggfuzz.c的main函数开始分析。经过一系列初始化之后调用了`fuzz_threadsStart`函数。 ```c //honggfuzz.c#L398 setupRLimits(); setupSignalsPreThreads(); fuzz_threadsStart(&hfuzz); ``` `fuzz_threadsStart`函数中设置当前state为`_HF_STATE_DYNAMIC_DRY_RUN`,进入第一阶段Dry Run。 ```c //fuzz.c#L560 void fuzz_threadsStart(honggfuzz_t* hfuzz) { if (!arch_archInit(hfuzz)) { LOG_F("Couldn't prepare arch for fuzzing"); } if (!sanitizers_Init(hfuzz)) { LOG_F("Couldn't prepare sanitizer options"); } if (hfuzz->socketFuzzer.enabled) { /* Don't do dry run with socketFuzzer */ LOG_I("Entering phase - Feedback Driven Mode (SocketFuzzer)"); hfuzz->feedback.state = _HF_STATE_DYNAMIC_MAIN; } else if (hfuzz->feedback.dynFileMethod != _HF_DYNFILE_NONE) { LOG_I("Entering phase 1/3: Dry Run"); hfuzz->feedback.state = _HF_STATE_DYNAMIC_DRY_RUN; } else { LOG_I("Entering phase: Static"); hfuzz->feedback.state = _HF_STATE_STATIC; } for (size_t i = 0; i < hfuzz->threads.threadsMax; i++) { if (!subproc_runThread( hfuzz, &hfuzz->threads.threads[i], fuzz_threadNew, /* joinable= */ true)) { PLOG_F("Couldn't run a thread #%zu", i); } } } ``` 接下来调用了`subproc_runThread`->`pthread_create`->`fuzz_threadNew`->`fuzz_fuzzLoop`函数。`fuzz_threadNew`函数中会对每个线程中结构体、变量等在进入`fuzz_fuzzLoop`函数前进行最后初始化操作,其中若加入了 `--export_feedback`参数则会在此处为每个线程创建覆盖率反馈的bitmap。 ```c snprintf(mapname, sizeof(mapname), "hf-%u-perthreadmap", fuzzNo); if ((run.perThreadCovFeedbackFd = files_createSharedMem(sizeof(feedback_t), mapname, /* exportmap= */ run.global->io.exportFeedback)) == -1) { LOG_F("files_createSharedMem(name='%s', sz=%zu, dir='%s') failed", mapname, sizeof(feedback_t), run.global->io.workDir); } defer { if (run.perThreadCovFeedbackFd != -1) { close(run.perThreadCovFeedbackFd); } }; ``` 接下来进入一个无限循环,调用 `fuzz_fuzzLoop`函数: ```c static void fuzz_fuzzLoop(run_t* run) { ... if (!fuzz_fetchInput(run)) { if (run->global->cfg.minimize && fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MINIMIZE) { fuzz_setTerminating(); return; } LOG_F("Cound't prepare input for fuzzing"); } if (!subproc_Run(run)) { LOG_F("Couldn't run fuzzed command"); } if (run->global->feedback.dynFileMethod != _HF_DYNFILE_NONE) { fuzz_perfFeedback(run); } if (run->global->cfg.useVerifier && !fuzz_runVerifier(run)) { return; } report_saveReport(run); } ``` `fuzz_fuzzLoop`函数首先调用了`fuzz_fetchInput`函数,根据不同的state对输入文件进行相应处理,若state为`_HF_STATE_DYNAMIC_MAIN`则会应用变异策略对文件进行变异。因为当前的state是`_HF_STATE_DYNAMIC_DRY_RUN`,所以接着调用了`input_prepareStaticFile`函数取得一个文件并返回。 ```c static bool fuzz_fetchInput(run_t* run) { { fuzzState_t st = fuzz_getState(run->global); if (st == _HF_STATE_DYNAMIC_DRY_RUN) { run->mutationsPerRun = 0U; if (input_prepareStaticFile(run, /* rewind= */ false, true)) { return true; } fuzz_setDynamicMainState(run); run->mutationsPerRun = run->global->mutate.mutationsPerRun; } } if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MINIMIZE) { ... } if (fuzz_getState(run->global) == _HF_STATE_DYNAMIC_MAIN) { if (run->global->exe.externalCommand) { if (!input_prepareExternalFile(run)) { LOG_E("input_prepareExternalFile() failed"); return false; } } else if (run->global->exe.feedbackMutateCommand) { if (!input_prepareDynamicInput(run, false)) { LOG_E("input_prepareDynamicInput(() failed"); return false; } } else if (!input_prepareDynamicInput(run, true)) { LOG_E("input_prepareDynamicInput() failed"); return false; } } if (fuzz_getState(run->global) == _HF_STATE_STATIC) { ... } ... return true; } ``` 回到`fuzz_fuzzLoop`函数,接下来调用 `subproc_Run`->`subproc_New`。`subproc_New`中 fork 出子进程,先调用`subproc_PrepareExecv`来准备运行前所需的覆盖率的 bitmap及相关环境变量。例如,用来记录比较指令常量的 `cmpFeedbackMap`就是在此处进行赋值,从而用于之后的变异策略。 ```c /* The const comparison bitmap/feedback structure */ if (run->global->feedback.cmpFeedback && TEMP_FAILURE_RETRY(dup2(run->global->feedback.cmpFeedbackFd, _HF_CMP_BITMAP_FD)) == -1) { PLOG_E("dup2(%d, _HF_CMP_BITMAP_FD=%d)", run->global->feedback.cmpFeedbackFd, _HF_CMP_BITMAP_FD); return false; } ``` 之后调用`arch_launchChild`函数运行 `fuzz_target` : ``` \_ ../../honggfuzz -P --rlimit_rss 2048 --sanitizers_del_report=true -i input/ -o output/ -d --dict libpng.dict -- ./target \_ ./target \_ ./target \_ ./target \_ ./target ``` ### 变异策略 fuzz策略的实现主要集中在mangle.c中,在循环的`fuzzloop`函数中,会根据用户的选择的 fuzz 方式来调用动态fuzz 或者静态 fuzz 的方法,但最后都是调用`mangle_mangleContent`来变异文件数据: ``` +-----------------------+ +---------------> |input_prepareStaticFile| ------+ | +-----------------------+ | | | | v +-------------+ +---------------+ +--------------------+ |fuzz_fuzzLoop| --> |fuzz_fetchInput| |mangle_mangleContent| +-------------+ +---------------+ +--------------------+ | ^ | | | +-------------------------+ | +---------------> |input_prepareDynamicInput| ----+ +-------------------------+ ``` `mangle_mangleContent`函数部分实现如下: ```c //mangle.c#L840 if (run->mutationsPerRun == 0U) {//设置变异率为0,仅作打开处理,通常用于验证崩溃 return; } if (run->dynfile->size == 0U) { //对空文件赋予随机size mangle_Resize(run, /* printable= */ run->global->cfg.only_printable); } uint64_t changesCnt = run->global->mutate.mutationsPerRun; //根据speed_factor大小设置changesCnt值,该值为之后变异的轮数 if (speed_factor < 5) { changesCnt = util_rndGet(1, run->global->mutate.mutationsPerRun); } else if (speed_factor < 10) { changesCnt = run->global->mutate.mutationsPerRun; } else { changesCnt = HF_MIN(speed_factor, 12); changesCnt = HF_MAX(changesCnt, run->global->mutate.mutationsPerRun); } //如果最后一次获取覆盖率时间超过5秒,则提高拼接变异的使用概率 if ((time(NULL) - ATOMIC_GET(run->global->timing.lastCovUpdate)) > 5) { if (util_rnd64() % 2) { mangle_Splice(run, run->global->cfg.only_printable); } } //随机选择变异函数对输入文件内容进行变异 for (uint64_t x = 0; x < changesCnt; x++) { uint64_t choice = util_rndGet(0, ARRAYSIZE(mangleFuncs) - 1); mangleFuncs[choice](run, /* printable= */ run->global->cfg.only_printable); } ``` 变异函数列表如下: 这里添加多个 `mangle_Shrink` 的原因是为了减少其他操作中插入或扩展文件带来的文件大小增大。 ```c //mangle.c#L812 static void (*const mangleFuncs[])(run_t * run, bool printable) = { /* Every *Insert or Expand expands file, so add more Shrink's */ mangle_Shrink, mangle_Shrink, mangle_Shrink, mangle_Shrink, mangle_Expand, mangle_Bit, mangle_IncByte, mangle_DecByte, mangle_NegByte, mangle_AddSub, mangle_MemSet, mangle_MemSwap, mangle_MemCopy, mangle_Bytes, mangle_ASCIINum, mangle_ASCIINumChange, mangle_ByteRepeatOverwrite, mangle_ByteRepeatInsert, mangle_Magic, mangle_StaticDict, mangle_ConstFeedbackDict, mangle_RandomOverwrite, mangle_RandomInsert, mangle_Splice, }; ``` #### mangle_Shrink 删除随机长度的文件内容。 ```c static void mangle_Shrink(run_t* run, bool printable HF_ATTR_UNUSED) { if (run->dynfile->size <= 2U) { return; } size_t off_start = mangle_getOffSet(run); size_t len = mangle_LenLeft(run, off_start); if (len == 0) { return; } if (util_rnd64() % 16) { len = mangle_getLen(HF_MIN(16, len)); } else { len = mangle_getLen(len); } size_t off_end = off_start + len; size_t len_to_move = run->dynfile->size - off_end; mangle_Move(run, off_end, off_start, len_to_move); input_setSize(run, run->dynfile->size - len); } ``` #### mangle_Expand 文件末尾扩展随机长度的空间,用空格填充,然后在随机位置,取前面的随机长度作数据拷贝。 ```c static void mangle_Expand(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); size_t len; if (util_rnd64() % 16) { len = mangle_getLen(HF_MIN(16, run->global->mutate.maxInputSz - off)); } else { len = mangle_getLen(run->global->mutate.maxInputSz - off); } mangle_Inflate(run, off, len, printable); } ``` ```c static inline size_t mangle_Inflate(run_t* run, size_t off, size_t len, bool printable) { if (run->dynfile->size >= run->global->mutate.maxInputSz) { return 0; } if (len > (run->global->mutate.maxInputSz - run->dynfile->size)) { len = run->global->mutate.maxInputSz - run->dynfile->size; } input_setSize(run, run->dynfile->size + len); mangle_Move(run, off, off + len, run->dynfile->size); if (printable) { memset(&run->dynfile->data[off], ' ', len); } return len; } ``` #### mangle_Bit 取随机位置的数值做位翻转。 ```c static void mangle_Bit(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); run->dynfile->data[off] ^= (uint8_t)(1U << util_rndGet(0, 7)); if (printable) { util_turnToPrintable(&(run->dynfile->data[off]), 1); } } ``` #### mangle_IncByte/DecByte/NegByte 随机位置的数据加1/减1/取反。 ```c static void mangle_IncByte(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); if (printable) { run->dynfile->data[off] = (run->dynfile->data[off] - 32 + 1) % 95 + 32; } else { run->dynfile->data[off] += (uint8_t)1UL; } } static void mangle_DecByte(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); if (printable) { run->dynfile->data[off] = (run->dynfile->data[off] - 32 + 94) % 95 + 32; } else { run->dynfile->data[off] -= (uint8_t)1UL; } } static void mangle_NegByte(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); if (printable) { run->dynfile->data[off] = 94 - (run->dynfile->data[off] - 32) + 32; } else { run->dynfile->data[off] = ~(run->dynfile->data[off]); } } ``` #### mangle_AddSub 取随机位置的1、2、4或8字节的数据长度作加减操作,2.2版本中对操作数范围进行划分,缩小了选择的范围。 ```c static void mangle_AddSub(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); /* 1,2,4,8 */ size_t varLen = 1U << util_rndGet(0, 3); if ((run->dynfile->size - off) < varLen) { varLen = 1; } uint64_t range; switch (varLen) { case 1: range = 16;//1<<4 break; case 2: range = 4096;//1<<12 break; case 4: range = 1048576;//1<<20 break; case 8: range = 268435456;//1<<28 break; default: LOG_F("Invalid operand size: %zu", varLen); } mangle_AddSubWithRange(run, off, varLen, range, printable); } ``` ```c static inline void mangle_AddSubWithRange( run_t* run, size_t off, size_t varLen, uint64_t range, bool printable) { int64_t delta = (int64_t)util_rndGet(0, range * 2) - (int64_t)range; switch (varLen) { case 1: { run->dynfile->data[off] += delta; break; } case 2: { int16_t val; memcpy(&val, &run->dynfile->data[off], sizeof(val)); if (util_rnd64() & 0x1) { val += delta; } else { /* Foreign endianess */ val = __builtin_bswap16(val); val += delta; val = __builtin_bswap16(val); } mangle_Overwrite(run, off, (uint8_t*)&val, varLen, printable); break; } case 4: { int32_t val; memcpy(&val, &run->dynfile->data[off], sizeof(val)); if (util_rnd64() & 0x1) { val += delta; } else { /* Foreign endianess */ val = __builtin_bswap32(val); val += delta; val = __builtin_bswap32(val); } mangle_Overwrite(run, off, (uint8_t*)&val, varLen, printable); break; } case 8: { int64_t val; memcpy(&val, &run->dynfile->data[off], sizeof(val)); if (util_rnd64() & 0x1) { val += delta; } else { /* Foreign endianess */ val = __builtin_bswap64(val); val += delta; val = __builtin_bswap64(val); } mangle_Overwrite(run, off, (uint8_t*)&val, varLen, printable); break; } default: { LOG_F("Unknown variable length size: %zu", varLen); } } } ``` #### mangle_MemSet 取随机位置、随机大小,可打印字符用随机生成的可打印字符填充,否则用`UINT8_MAX` 填充。 ```c static void mangle_MemSet(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); size_t len = mangle_getLen(run->dynfile->size - off); int val = printable ? (int)util_rndPrintable() : (int)util_rndGet(0, UINT8_MAX); memset(&run->dynfile->data[off], val, len); } ``` #### mangle_MemSwap 新策略,从文件随机两处取随机大小按这两块长度的最小值进行交换。 ```c static void mangle_MemSwap(run_t* run, bool printable HF_ATTR_UNUSED) { size_t off1 = mangle_getOffSet(run); size_t maxlen1 = run->dynfile->size - off1; size_t off2 = mangle_getOffSet(run); size_t maxlen2 = run->dynfile->size - off2; size_t len = mangle_getLen(HF_MIN(maxlen1, maxlen2)); uint8_t* tmpbuf = (uint8_t*)util_Malloc(len); defer { free(tmpbuf); }; memcpy(tmpbuf, &run->dynfile->data[off1], len); memmove(&run->dynfile->data[off1], &run->dynfile->data[off2], len); memcpy(&run->dynfile->data[off2], tmpbuf, len); } ``` #### mangle_MemCopy 新策略,随机位置取随机大小内容插入/覆盖随机位置。 ```c static void mangle_MemCopy(run_t* run, bool printable HF_ATTR_UNUSED) { size_t off = mangle_getOffSet(run); size_t len = mangle_getLen(run->dynfile->size - off); /* Use a temp buf, as Insert/Inflate can change source bytes */ uint8_t* tmpbuf = (uint8_t*)util_Malloc(len); defer { free(tmpbuf); }; memcpy(tmpbuf, &run->dynfile->data[off], len); mangle_UseValue(run, tmpbuf, len, printable); } ``` #### mangle_Bytes 随机位置插入/覆盖1~2字节数据。 ```c static void mangle_Bytes(run_t* run, bool printable) { uint16_t buf; if (printable) { util_rndBufPrintable((uint8_t*)&buf, sizeof(buf)); } else { buf = util_rnd64(); } /* Overwrite with random 1-2-byte values */ size_t toCopy = util_rndGet(1, 2); mangle_UseValue(run, (const uint8_t*)&buf, toCopy, printable); } ``` #### mangle_ASCIINum 随机位置插入/覆盖 2~8 字节数据。 ```c static void mangle_ASCIINum(run_t* run, bool printable) { size_t len = util_rndGet(2, 8); char buf[20]; snprintf(buf, sizeof(buf), "%-19" PRId64, (int64_t)util_rnd64()); mangle_UseValue(run, (const uint8_t*)buf, len, printable); } ``` #### mangle_ASCIINumChange 新策略,从随机位置起寻找数字,若未找到则执行`mangle_Bytes`操作,找到则随机对该数字进行加/减/乘/除/取反/替换随机数字。 ```c static void mangle_ASCIINumChange(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); /* Find a digit */ for (; off < run->dynfile->size; off++) { if (isdigit(run->dynfile->data[off])) { break; } } if (off == run->dynfile->size) { mangle_Bytes(run, printable); return; } size_t len = HF_MIN(20, run->dynfile->size - off); char numbuf[21] = {}; strncpy(numbuf, (const char*)&run->dynfile->data[off], len); uint64_t val = (uint64_t)strtoull(numbuf, NULL, 10); switch (util_rndGet(0, 5)) { case 0: val += util_rndGet(1, 256); break; case 1: val -= util_rndGet(1, 256); break; case 2: val *= util_rndGet(1, 256); break; case 3: val /= util_rndGet(1, 256); break; case 4: val = ~(val); break; case 5: val = util_rnd64(); break; default: LOG_F("Invalid choice"); }; len = HF_MIN((size_t)snprintf(numbuf, sizeof(numbuf), "%" PRIu64, val), len); mangle_Overwrite(run, off, (const uint8_t*)numbuf, len, printable); } ``` #### mangle_ByteRepeatOverwrite 新策略,在随机位置选取随机长度覆盖为该随机位置的值。 ```c static void mangle_ByteRepeatOverwrite(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); size_t destOff = off + 1; size_t maxSz = run->dynfile->size - destOff; /* No space to repeat */ if (!maxSz) { mangle_Bytes(run, printable); return; } size_t len = mangle_getLen(maxSz); memset(&run->dynfile->data[destOff], run->dynfile->data[off], len); } ``` #### mangle_ByteRepeatInsert 新策略,在随机位置选取随机长度插入该随机位置的值。 ```c static void mangle_ByteRepeatInsert(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); size_t destOff = off + 1; size_t maxSz = run->dynfile->size - destOff; /* No space to repeat */ if (!maxSz) { mangle_Bytes(run, printable); return; } size_t len = mangle_getLen(maxSz); len = mangle_Inflate(run, destOff, len, printable); memset(&run->dynfile->data[destOff], run->dynfile->data[off], len); } ``` #### mangle_Magic 取各种边界值进行覆写。 ```c static void mangle_Magic(run_t* run, bool printable) { uint64_t choice = util_rndGet(0, ARRAYSIZE(mangleMagicVals) - 1); mangle_UseValue(run, mangleMagicVals[choice].val, mangleMagicVals[choice].size, printable); } ``` #### mangle_StaticDict 新策略,随机从读入的字典中选择一个magic,插入或替换。 ```c static void mangle_StaticDict(run_t* run, bool printable) { if (run->global->mutate.dictionaryCnt == 0) { mangle_Bytes(run, printable); return; } uint64_t choice = util_rndGet(0, run->global->mutate.dictionaryCnt - 1); mangle_UseValue(run, run->global->mutate.dictionary[choice].val, run->global->mutate.dictionary[choice].len, printable); } ``` #### mangle_ConstFeedbackDict 新策略,从cmpFeedbackMap中随机选取常量值,插入或覆盖随机位置。 ```c static void mangle_ConstFeedbackDict(run_t* run, bool printable) { size_t len; const uint8_t* val = mangle_FeedbackDict(run, &len); if (val == NULL) { mangle_Bytes(run, printable); return; } mangle_UseValue(run, val, len, printable); } static inline const uint8_t* mangle_FeedbackDict(run_t* run, size_t* len) { if (!run->global->feedback.cmpFeedback) { return NULL; } cmpfeedback_t* cmpf = run->global->feedback.cmpFeedbackMap; uint32_t cnt = ATOMIC_GET(cmpf->cnt); if (cnt == 0) { return NULL; } if (cnt > ARRAYSIZE(cmpf->valArr)) { cnt = ARRAYSIZE(cmpf->valArr); } uint32_t choice = util_rndGet(0, cnt - 1); *len = (size_t)ATOMIC_GET(cmpf->valArr[choice].len); if (*len == 0) { return NULL; } return cmpf->valArr[choice].val; } ``` #### mangle_RandomOverwrite 新策略,随机位置选取随机长度进行覆盖。 ```c static void mangle_RandomOverwrite(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); size_t len = mangle_getLen(run->dynfile->size - off); if (printable) { util_rndBufPrintable(&run->dynfile->data[off], len); } else { util_rndBuf(&run->dynfile->data[off], len); } } ``` #### mangle_RandomInsert 新策略,随机位置选取随机长度进行插入。 ```c static void mangle_RandomInsert(run_t* run, bool printable) { size_t off = mangle_getOffSet(run); size_t len = mangle_getLen(run->dynfile->size - off); len = mangle_Inflate(run, off, len, printable); if (printable) { util_rndBufPrintable(&run->dynfile->data[off], len); } else { util_rndBuf(&run->dynfile->data[off], len); } } ``` #### mangle_Splice 获取另一文件输入截取随机大小,插入/覆盖到原文件。 ```c static void mangle_Splice(run_t* run, bool printable) { const uint8_t* buf; size_t sz = input_getRandomInputAsBuf(run, &buf); if (!sz) { mangle_Bytes(run, printable); return; } size_t remoteOff = mangle_getLen(sz) - 1; size_t len = mangle_getLen(sz - remoteOff); mangle_UseValue(run, &buf[remoteOff], len, printable); } ``` ## 2024-02-04-基于硬件的覆盖率反馈 ### 使用介绍与前置条件 Perf exclude_callchain_kernel requires kernel >= 3.7 Intel's PT and new Intel BTS format require kernel >= 4.1 本节来自振哥笔记 Honggfuzz 的优势是它提供了一些基于硬件反馈的 fuzz 能力,应该比 blind fuzz 要强一些,参考 https://github.com/google/honggfuzz/blob/master/docs/FeedbackDrivenFuzzing.md ``` --linux_perf_bts_edge This mode will take into consideration pairs (tuples) of jumps, recording unique from-to jump pairs. The data is taken from the Intel BTS CPU registers. --linux_perf_ipt_block This mode will utilize Interl's PT (Process Trace) subsystem, which should be way faster than BTS (Branch Trace Store), but will currently produce less precise results. --linux_perf_instr This mode tries to maximize the number of instructions taken during each process iteration. The counters will be taken from the Linux perf subsystems. Intel, AMD and even other CPU architectures are supported for this mode. --linux_perf_branch it will try to maximize the number of branches taken by CPU on behalf of the fuzzed process (here: djpeg.static) while performing each fuzzing iteration. Intel, AMD and even other CPU architectures are supported for this mode. ``` 如果在虚拟机中使用 Honggfuzz 基于硬件反馈的 fuzz ,需要打开 CPU 设置中的「虚拟化 CPU 性能计数器」 选项,不然 honggfuzz 没有办法正常工作。执行下面的命令可以检查 honggfuzz 是否支持硬件反馈的功能 `sudo perf list hw` , 在 Ubuntu linux 下的输出为: ```shell # perf list hw List of pre-defined events (to be used in -e): branch-instructions OR branches [Hardware event] branch-misses [Hardware event] bus-cycles [Hardware event] cache-misses [Hardware event] cache-references [Hardware event] cpu-cycles OR cycles [Hardware event] instructions [Hardware event] ref-cycles [Hardware event] ``` Linux perf command, also called perf_events. perf is powerful: it can instrument CPU performance counters, tracepoints, kprobes, and uprobes (dynamic tracing). perf 工具需要自己安装,下面是几个发行版的安装方法。 - Ubuntu/Debian ```sh sudo apt install linux-tools-$(uname -r) linux-tools-generic ``` 在默认配置的情况下使用 perf 需要 root 权限,允许普通用户可以使用 perf 需要执行下面的命令。 ```sh sudo su - echo 0 > /proc/sys/kernel/perf_event_paranoid exit ``` 上面是临时起作用的方法,如果要使普通用户永久获得使用 perf 的权限,可以使用下面的命令。 ```sh sudo nano /etc/sysctl.conf # add the following line kernel.perf_event_paranoid = 0 sudo sysctl -p ``` ### 源码分析 源码版本(348a47213919f14b9453e89a663b1515369bd9a2)。以linux_perf_branch为例 honggfuzz会在`arch_perfInit`获取Intel's PT 和 Intel BTS值,函数调用路径:`fuzz_threadsStart->arch_archInit->arch_perfInit` 在`arch_perfOpen`对perf进行初始化,函数调用路径:`subproc_Run->subproc_New->arch_prepareParentAfterFork->arch_perfOpen` ```c //linux/perf.c:217 bool arch_perfOpen(run_t* run) { ... if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) { if (!arch_perfCreate( run, run->pid, _HF_DYNFILE_BRANCH_COUNT, &run->arch_linux.cpuBranchFd)) { LOG_E("Cannot set up perf for pid=%d (_HF_DYNFILE_BRANCH_COUNT)", (int)run->pid); goto out; } } ... ``` `arch_perfOpen`中通过调用`arch_perfCreate`针对不同选项进行初始化,最终调用`perf_event_open`后获取perf文件描述符 ```c //linux/perf.c:117 static bool arch_perfCreate(run_t* run, pid_t pid, dynFileMethod_t method, int* perfFd) { ... struct perf_event_attr pe = {}; pe.size = sizeof(struct perf_event_attr); if (run->global->arch_linux.kernelOnly) { pe.exclude_user = 1; } else { pe.exclude_kernel = 1; } pe.disabled = 1; if (!run->global->exe.persistent) { pe.enable_on_exec = 1; } pe.exclude_hv = 1; pe.type = PERF_TYPE_HARDWARE; switch (method) { ... case _HF_DYNFILE_BRANCH_COUNT: LOG_D("Using: PERF_COUNT_HW_BRANCH_INSTRUCTIONS for pid=%d", (int)pid); pe.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; pe.inherit = 1; break; ... } ... *perfFd = perf_event_open(&pe, pid, -1, -1, PERF_FLAG_FD_CLOEXEC); if (*perfFd == -1) { PLOG_E("perf_event_open() failed"); return false; } ... return true; } static long perf_event_open( struct perf_event_attr* hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags) { return syscall(__NR_perf_event_open, hw_event, (uintptr_t)pid, (uintptr_t)cpu, (uintptr_t)group_fd, (uintptr_t)flags); } ``` 在`arch_perfEnable`函数中启用对应选项的覆盖率追踪,该函数调用路径:`subproc_Run->arch_prepareParent->arch_perfEnable` ```c //linux/perf.c:293 bool arch_perfEnable(run_t* run) { ... if (run->global->feedback.dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) { ioctl(run->arch_linux.cpuBranchFd, PERF_EVENT_IOC_ENABLE, 0); } ... return true; } ``` 在`arch_perfAnalyze`,记录perf的覆盖率反馈,该函数调用路径:`subproc_Run->arch_reapChild->arch_perfAnalyze` ```c //linux/perf.c:331 void arch_perfAnalyze(run_t* run) { ... uint64_t branchCount = 0; if ((run->global->feedback.dynFileMethod & _HF_DYNFILE_BRANCH_COUNT) && run->arch_linux.cpuBranchFd != -1) { ioctl(run->arch_linux.cpuBranchFd, PERF_EVENT_IOC_DISABLE, 0); if (files_readFromFd(run->arch_linux.cpuBranchFd, (uint8_t*)&branchCount, sizeof(branchCount)) != sizeof(branchCount)) { PLOG_E("read(perfFd='%d') failed", run->arch_linux.cpuBranchFd); } ioctl(run->arch_linux.cpuBranchFd, PERF_EVENT_IOC_RESET, 0); } ... run->hwCnts.cpuBranchCnt = branchCount; } ``` 最后在 `fuzz_perfFeedback`中处理获得的`cpuBranchCnt`计数与上一轮的`run->global->feedback.hwCnts.cpuBranchCnt`进行比较若计数增长则更新全局计数并将当前输入加入到corpus中。 ## 参考 [1] https://github.com/google/honggfuzz [2] https://bbs.pediy.com/thread-247954.htm [3] https://www.anquanke.com/post/id/181936