简介 前面学习了 afl-as ,但是可以发现前面的插桩技术十分原始且神奇,现在更为通用的插桩方式是通过 llvm pass 进行插桩,对应的也就是 afl-clang-fast 。
LLVM llvm 的大体框架如下:
Clang 是 LLVM 项目的一个子项目,它是 LLVM 架构下的 C/C++/Objective-C 的编译器,是 LLVM 前端的一部分。相较于GCC,具备编译速度快、占用内存少、模块化设计、诊断信息可读性强、设计清晰简单等优点。
以 Clang 为例,从源码到机器码的流程如下:
其中的 LLVM Pass 便是用户自定义的处理 IR 的过程,可以进行优化、插桩、分析等功能。
AFL中的afl-clang-fast AFL 中的 llvm mode 可以实现编译器级别的插桩,主要包含以下三个文件:
afl-clang-fast.c :本质上是 clang 的 wrapper ,和 afl-gcc 类似
afl-llvm-pass.so.cc :通过 afl-clang-fast 调用 clang 时将 pass 插入 LLVM 中
afl-llvm-rt.o.c :重写了 afl-as.h 中的插桩代码(main_payload)
总结一下就是通过 llvm pass 记录信息,将每个基本块都插入探针
afl-clang-fast.c main 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 int main (int argc, char ** argv) { if (isatty(2 ) && !getenv("AFL_QUIET" )) { #ifdef USE_TRACE_PC SAYF(cCYA "afl-clang-fast [tpcg] " cBRI VERSION cRST " by <lszekeres@google.com>\n" ); #else SAYF(cCYA "afl-clang-fast " cBRI VERSION cRST " by <lszekeres@google.com>\n" ); #endif } if (argc < 2 ) { SAYF("\n" "This is a helper application for afl-fuzz. It serves as a drop-in replacement\n" "for clang, letting you recompile third-party code with the required runtime\n" "instrumentation. A common use pattern would be one of the following:\n\n" " CC=%s/afl-clang-fast ./configure\n" " CXX=%s/afl-clang-fast++ ./configure\n\n" "In contrast to the traditional afl-clang tool, this version is implemented as\n" "an LLVM pass and tends to offer improved performance with slow programs.\n\n" "You can specify custom next-stage toolchain via AFL_CC and AFL_CXX. Setting\n" "AFL_HARDEN enables hardening optimizations in the compiled code.\n\n" , BIN_PATH, BIN_PATH); exit (1 ); } #ifndef __ANDROID__ find_obj(argv[0 ]); #endif edit_params(argc, argv); execvp(cc_params[0 ], (char **)cc_params); FATAL("Oops, failed to execute '%s' - check your PATH" , cc_params[0 ]); return 0 ; }
主要有三个函数调用:
find_obj :如果是安卓平台,则查找运行时的 lib
edit_params :修改 params
execvp :和 afl-gcc 类似
find_obj 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static void find_obj (u8* argv0) { u8 *afl_path = getenv("AFL_PATH" ); u8 *slash, *tmp; if (afl_path) { tmp = alloc_printf("%s/afl-llvm-rt.o" , afl_path); if (!access(tmp, R_OK)) { obj_path = afl_path; ck_free(tmp); return ; } ck_free(tmp); } slash = strrchr (argv0, '/' ); if (slash) { u8 *dir; *slash = 0 ; dir = ck_strdup(argv0); *slash = '/' ; tmp = alloc_printf("%s/afl-llvm-rt.o" , dir); if (!access(tmp, R_OK)) { obj_path = dir; ck_free(tmp); return ; } ck_free(tmp); ck_free(dir); } if (!access(AFL_PATH "/afl-llvm-rt.o" , R_OK)) { obj_path = AFL_PATH; return ; } FATAL("Unable to find 'afl-llvm-rt.o' or 'afl-llvm-pass.so'. Please set AFL_PATH" ); }
查看是否有环境变量 AFL_PATH 并赋值给 afl_path ,如果有则在 afl_path 下寻找 afl-llvm-rt.o 并返回
通过 argv0 的 “/” 分割出前面的路径,并在该路径下寻找 afl-llvm-rt.o 并返回
最后在 AFL_PATH 路径下寻找 afl-llvm-rt.o 并返回
都找不到则抛出异常
edit_params 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 static void edit_params (u32 argc, char ** argv) { u8 fortify_set = 0 , asan_set = 0 , x_set = 0 , bit_mode = 0 ; u8 *name; cc_params = ck_alloc((argc + 128 ) * sizeof (u8*)); name = strrchr (argv[0 ], '/' ); if (!name) name = argv[0 ]; else name++; if (!strcmp (name, "afl-clang-fast++" )) { u8* alt_cxx = getenv("AFL_CXX" ); cc_params[0 ] = alt_cxx ? alt_cxx : (u8*)"clang++" ; } else { u8* alt_cc = getenv("AFL_CC" ); cc_params[0 ] = alt_cc ? alt_cc : (u8*)"clang" ; } #ifdef USE_TRACE_PC cc_params[cc_par_cnt++] = "-fsanitize-coverage=trace-pc-guard" ; #ifndef __ANDROID__ cc_params[cc_par_cnt++] = "-mllvm" ; cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0" ; #endif #else cc_params[cc_par_cnt++] = "-Xclang" ; cc_params[cc_par_cnt++] = "-load" ; cc_params[cc_par_cnt++] = "-Xclang" ; cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so" , obj_path); #endif cc_params[cc_par_cnt++] = "-Qunused-arguments" ; while (--argc) { u8* cur = *(++argv); if (!strcmp (cur, "-m32" )) bit_mode = 32 ; if (!strcmp (cur, "armv7a-linux-androideabi" )) bit_mode = 32 ; if (!strcmp (cur, "-m64" )) bit_mode = 64 ; if (!strcmp (cur, "-x" )) x_set = 1 ; if (!strcmp (cur, "-fsanitize=address" ) || !strcmp (cur, "-fsanitize=memory" )) asan_set = 1 ; if (strstr (cur, "FORTIFY_SOURCE" )) fortify_set = 1 ; if (!strcmp (cur, "-Wl,-z,defs" ) || !strcmp (cur, "-Wl,--no-undefined" )) continue ; cc_params[cc_par_cnt++] = cur; } if (getenv("AFL_HARDEN" )) { cc_params[cc_par_cnt++] = "-fstack-protector-all" ; if (!fortify_set) cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2" ; } if (!asan_set) { 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" ; } } #ifdef USE_TRACE_PC if (getenv("AFL_INST_RATIO" )) FATAL("AFL_INST_RATIO not available at compile time with 'trace-pc'." ); #endif if (!getenv("AFL_DONT_OPTIMIZE" )) { cc_params[cc_par_cnt++] = "-g" ; cc_params[cc_par_cnt++] = "-O3" ; cc_params[cc_par_cnt++] = "-funroll-loops" ; } 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++] = "-D__AFL_HAVE_MANUAL_CONTROL=1" ; 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++] = "-D__AFL_LOOP(_A)=" "({ static volatile char *_B __attribute__((used)); " " _B = (char*)\"" PERSIST_SIG "\"; " #ifdef __APPLE__ "__attribute__((visibility(\"default\"))) " "int _L(unsigned int) __asm__(\"___afl_persistent_loop\"); " #else "__attribute__((visibility(\"default\"))) " "int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); " #endif "_L(_A); })" ; cc_params[cc_par_cnt++] = "-D__AFL_INIT()=" "do { static volatile char *_A __attribute__((used)); " " _A = (char*)\"" DEFER_SIG "\"; " #ifdef __APPLE__ "__attribute__((visibility(\"default\"))) " "void _I(void) __asm__(\"___afl_manual_init\"); " #else "__attribute__((visibility(\"default\"))) " "void _I(void) __asm__(\"__afl_manual_init\"); " #endif "_I(); } while (0)" ; if (x_set) { cc_params[cc_par_cnt++] = "-x" ; cc_params[cc_par_cnt++] = "none" ; } #ifndef __ANDROID__ switch (bit_mode) { case 0 : cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o" , obj_path); break ; case 32 : cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-32.o" , obj_path); if (access(cc_params[cc_par_cnt - 1 ], R_OK)) FATAL("-m32 is not supported by your compiler" ); break ; case 64 : cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-64.o" , obj_path); if (access(cc_params[cc_par_cnt - 1 ], R_OK)) FATAL("-m64 is not supported by your compiler" ); break ; } #endif cc_params[cc_par_cnt] = NULL ; }
为 cc_params 开辟 (argc + 128) * sizeof(u8*) 大小的空间
通过分割 argv[0] 判断传入的是啥
如果是 afl-clang-fast++ ,则将 cc_params[0] 设置为环境变量 AFL_CXX 或 clang++
否则就是 afl-clang-fast ,则将 cc_params[0] 设置为环境变量 AFL_CC 或 clang
如果定义了宏 USE_TRACE_PC ,则添加 -fsanitize-coverage=trace-pc-guard
如果不是安卓平台,则添加 -mllvm -sanitizer-coverage-block-threshold=0
否则添加 -Xclang -load -Xclang “obj_path/afl-llvm-pass.so”
最后添加 -Qunused-arguments
通过 argc 遍历 argv 并进行设置
-m32 和 armv7a-linux-androideabi 设置 bit_mode 为 32
-m64 设置 bit_mode 为 64
-x 设置 x_set 为 1
-fsanitize=address 或 -fsanitize=memory 设置 asan_set 为 1
FORTIFY_SOURCE 设置 fortify_set 为 1
忽略 -Wl,-z,defs 和 -Wl,–no-undefined
如果有环境变量 AFL_HARDEN ,添加 -fstack-protector-all
如果没有设置 fortify_set ,则添加 -D_FORTIFY_SOURCE=2
如果没有设置 asan_set
如果有环境变量 AFL_USE_ASAN
如果有环境变量 AFL_USE_MSAN 或 AFL_HARDEN ,抛出错误
添加 -U_FORTIFY_SOURCE -fsanitize=address
没有环境变量 AFL_USE_ASAN,而有环境变量 AFL_USE_MSAN
如果有环境变量 AFL_USE_ASAN 或 AFL_HARDEN ,抛出错误
添加 -U_FORTIFY_SOURCE -fsanitize=memory
如果有宏定义 USE_TRACE_PC
如果有环境变量 AFL_INST_RATIO ,抛出错误
如果没有环境变量 AFL_DONT_OPTIMIZE
如果有环境变量 AFL_NO_BUILTIN
添加 -fno-builtin-strcmp -fno-builtin-strncmp -fno-builtin-strcasecmp -fno-builtin-strncasecmp -fno-builtin-memcmp
添加 -D__AFL_HAVE_MANUAL_CONTROL=1 -D__AFL_COMPILER=1 -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1
添加宏定义
D__AFL_LOOP(_A) :对应 ___afl_persistent_loop
D__AFL_INIT() :对应 ___afl_manual_init
如果设置了 x_set ,添加 -x none
如果是安卓平台,判断 bit_mode 的值
如果为 0 ,则添加 obj_path/afl-llvm-rt.o
如果为 32 ,则添加 obj_path/afl-llvm-rt-32.o
如果为 64 ,则添加 obj_path/afl-llvm-rt-64.o
整体的流程和 afl-gcc 中的 edit_params 类似,有很多相同的地方
execvp 最后的 execvp 则是将替换过后的执行运行,和 afl-gcc 一样,这里不再赘述
afl-llvm-pass.so.cc 该文件实现了插桩的 llvm pass
AFLCoverage 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 namespace { class AFLCoverage : public ModulePass { public: static char ID; AFLCoverage() : ModulePass(ID) { } bool runOnModule (Module &M) override; }; }
实现了 runOnModule
register pass 1 2 3 4 5 6 7 8 9 10 11 12 static void registerAFLPass (const PassManagerBuilder &, legacy::PassManagerBase &PM) { PM.add(new AFLCoverage()); } static RegisterStandardPasses RegisterAFLPass ( PassManagerBuilder::EP_ModuleOptimizerEarly, registerAFLPass) ;static RegisterStandardPasses RegisterAFLPass0 ( PassManagerBuilder::EP_EnabledOnOptLevel0, registerAFLPass) ;
向 PassManager 中注册了新的 AFLPass
runOnModule 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 char AFLCoverage::ID = 0 ;bool AFLCoverage::runOnModule (Module &M) { LLVMContext &C = M.getContext(); IntegerType *Int8Ty = IntegerType::getInt8Ty(C); IntegerType *Int32Ty = IntegerType::getInt32Ty(C); char be_quiet = 0 ; if (isatty(2 ) && !getenv("AFL_QUIET" )) { SAYF(cCYA "afl-llvm-pass " cBRI VERSION cRST " by <lszekeres@google.com>\n" ); } else be_quiet = 1 ; char * inst_ratio_str = getenv("AFL_INST_RATIO" ); unsigned int inst_ratio = 100 ; if (inst_ratio_str) { if (sscanf (inst_ratio_str, "%u" , &inst_ratio) != 1 || !inst_ratio || inst_ratio > 100 ) FATAL("Bad value of AFL_INST_RATIO (must be between 1 and 100)" ); } GlobalVariable *AFLMapPtr = new GlobalVariable(M, PointerType::get(Int8Ty, 0 ), false , GlobalValue::ExternalLinkage, 0 , "__afl_area_ptr" ); GlobalVariable *AFLPrevLoc = new GlobalVariable( M, Int32Ty, false , GlobalValue::ExternalLinkage, 0 , "__afl_prev_loc" , 0 , GlobalVariable::GeneralDynamicTLSModel, 0 , false ); int inst_blocks = 0 ; for (auto &F : M) for (auto &BB : F) { BasicBlock::iterator IP = BB.getFirstInsertionPt(); IRBuilder<> IRB(&(*IP)); if (AFL_R(100 ) >= inst_ratio) continue ; unsigned int cur_loc = AFL_R(MAP_SIZE); ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc); LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc); PrevLoc->setMetadata(M.getMDKindID("nosanitize" ), MDNode::get(C, None)); Value *PrevLocCasted = IRB.CreateZExt(PrevLoc, IRB.getInt32Ty()); LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr); MapPtr->setMetadata(M.getMDKindID("nosanitize" ), MDNode::get(C, None)); Value *MapPtrIdx = IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc)); LoadInst *Counter = IRB.CreateLoad(MapPtrIdx); Counter->setMetadata(M.getMDKindID("nosanitize" ), MDNode::get(C, None)); Value *Incr = IRB.CreateAdd(Counter, ConstantInt::get(Int8Ty, 1 )); IRB.CreateStore(Incr, MapPtrIdx) ->setMetadata(M.getMDKindID("nosanitize" ), MDNode::get(C, None)); StoreInst *Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1 ), AFLPrevLoc); Store->setMetadata(M.getMDKindID("nosanitize" ), MDNode::get(C, None)); inst_blocks++; } if (!be_quiet) { if (!inst_blocks) WARNF("No instrumentation targets found." ); else OKF("Instrumented %u locations (%s mode, ratio %u%%)." , inst_blocks, getenv("AFL_HARDEN" ) ? "hardened" : ((getenv("AFL_USE_ASAN" ) || getenv("AFL_USE_MSAN" )) ? "ASAN/MSAN" : "non-hardened" ), inst_ratio); } return true ; }
获取 LLVMContext 进程上下文
检测环境变量 AFL_INST_RATIO
如果有则设置为 inst_ratio_str 并转为数字赋值给 inst_ratio 。检测 inst_ratio 是否在 0 到 100 内,如果不是则抛出异常
否则设置 inst_ratio 为默认值 100
获取共享内存 SHM 区域的指针和上一基本块的随机 ID
开始插桩
遍历每一个基本块(BB),如果 AFL_R(100) 大于等于 inst_ratio 则 continue
给当前基本块的 ID (CurLoc)赋值为随机值 AFL_R(MAP_SIZE)
插入 Load 指令,获取前一个基本块的 ID 并赋值给 PrevLocCasted
插入 Load 指令,获取共享内存区域的指针 MapPtr
使用 CreateGEP 获取 PrevLocCasted 和 CurLoc 异或的 MapPtrIdx
插入 Load 指令,获取 MapPtrIdx 并使用 Store 指令将共享内存的指定索引处内存加一
插入 Store 指令,更新上一个块的 ID (AFLPrevLoc)为 cur_loc >> 1
将插桩计数器 inst_blocks 加一
如果不是静默模式,则打印信息
整个过程和 afl-as 中的流程十分类似,不同的在于这里使用 llvm 的 pass ,使得插桩更加科学。
afl-llvm-rt.o.c 该文件实现了三个 llvm mode 中的主要功能:
deferred instrumentation
persistent mode
trace-pc-guard mode
全局定义 1 2 3 4 5 6 7 8 9 10 11 12 u8 __afl_area_initial[MAP_SIZE]; u8* __afl_area_ptr = __afl_area_initial; __thread u32 __afl_prev_loc; static u8 is_persistent;
正确的初始化程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define DEFER_ENV_VAR "__AFL_DEFER_FORKSRV" #define PERSIST_ENV_VAR "__AFL_PERSISTENT" __attribute__((constructor(CONST_PRIO))) void __afl_auto_init(void ) { is_persistent = !!getenv(PERSIST_ENV_VAR); if (getenv(DEFER_ENV_VAR)) return ; __afl_manual_init(); }
读取环境变量 __AFL_PERSISTENT 并赋值给 is_persistent
如果环境变量中没有 __AFL_DEFER_FORKSRV 则返回,否则执行 __afl_manual_init 进行初始化
deferred instrumentation AFL 会尝试通过只执行一次目标二进制文件来提升性能,在 main()之前暂停程序,然后克隆“主”进程获得一个稳定的可进行持续fuzz的目标。简言之,避免目标二进制文件的多次、重复的完整运行,而是采取了一种类似快照的机制。我们之前所查看的 afl-as 中也是这样的模式。
首先我们需要寻找稳定的克隆位置,并添加如下代码:
1 2 3 #ifdef __AFL_HAVE_MANUAL_CONTROL __AFL_INIT(); #endif
其中 __AFL_INIT() 也就是 afl-clang-fast.c 中定义的宏定义:
1 2 3 4 5 6 7 8 9 10 11 cc_params[cc_par_cnt++] = "-D__AFL_INIT()=" "do { static volatile char *_A __attribute__((used)); " " _A = (char*)\"" DEFER_SIG "\"; " #ifdef __APPLE__ "__attribute__((visibility(\"default\"))) " "void _I(void) __asm__(\"___afl_manual_init\"); " #else "__attribute__((visibility(\"default\"))) " "void _I(void) __asm__(\"__afl_manual_init\"); " #endif "_I(); } while (0)" ;
会走到 __afl_manual_init 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void __afl_manual_init(void ) { static u8 init_done; if (!init_done) { __afl_map_shm(); __afl_start_forkserver(); init_done = 1 ; } }
__afl_map_shm 函数会初始化共享内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static void __afl_map_shm(void ) { u8 *id_str = getenv(SHM_ENV_VAR); if (id_str) { u32 shm_id = atoi(id_str); __afl_area_ptr = shmat(shm_id, NULL , 0 ); if (__afl_area_ptr == (void *)-1 ) _exit(1 ); __afl_area_ptr[0 ] = 1 ; } }
获取环境变量 SHM_ENV_VAR 并赋值给 id_str ,转为数字 shm_id 并使用 (shm_id, NULL, 0) 申请共享内存,将结果赋值给 __afl_area_ptr
如果上述步骤出错则推出,否则将 __afl_area_ptr[0] 置为 1
该步骤是为了防止在低 AFL_INST_RATIO 的情况下父进程也不会停止本进程
__afl_start_forkserver 函数会启动 fork server :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 static void __afl_start_forkserver(void ) { static u8 tmp[4 ]; s32 child_pid; u8 child_stopped = 0 ; if (write(FORKSRV_FD + 1 , tmp, 4 ) != 4 ) return ; while (1 ) { u32 was_killed; int status; if (read(FORKSRV_FD, &was_killed, 4 ) != 4 ) _exit(1 ); if (child_stopped && was_killed) { child_stopped = 0 ; if (waitpid(child_pid, &status, 0 ) < 0 ) _exit(1 ); } if (!child_stopped) { child_pid = fork(); if (child_pid < 0 ) _exit(1 ); if (!child_pid) { close(FORKSRV_FD); close(FORKSRV_FD + 1 ); return ; } } else { kill(child_pid, SIGCONT); child_stopped = 0 ; } if (write(FORKSRV_FD + 1 , &child_pid, 4 ) != 4 ) _exit(1 ); if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0 ) < 0 ) _exit(1 ); if (WIFSTOPPED(status)) child_stopped = 1 ; if (write(FORKSRV_FD + 1 , &status, 4 ) != 4 ) _exit(1 ); } }
写入四字节到状态管道 199 ,告诉父进程准备完毕
进行循环
等待父进程,从控制管道 198 读取四字节内容,如果读取失败则抛出异常
如果 child_stopped 为 0 ,则 fork 出一个子进程,fork 失败则退出。否则关闭控制管道和状态管道并跳出循环(接下来的循环操作都是父进程的操作)
如果 child_stopped 为 1 ,且处于 persistent mode 下,此时子进程还存活,只是被暂停了,通过 kill(child_pid, SIGCONT) 来重启并设置 child_stopped 为 0
写入四字节到状态管道 199 ,并等待子进程,将状态赋值给 status
使用 WIFSTOPPED 宏确定 status 是否对应于一个暂停的子进程,在 persistent mode 下子进程会通过 SIGSTOP 来暂停自己并指示运行成功。所以设置 child_stopped 为 1 (这样就会进入上面的唤醒流程)
子进程运行结束后,写入四字节到状态管道 199 告知 AFL 本次运行结束,开始下一次循环
该过程类似 afl-as 中的 main_payload ,不同之处在于加了 persistent mode 部分的逻辑
persistent mode 相比于 deferred instrumentation ,persistent mode 并没有通过 fork 子进程来进行 fuzz 。一些库中的 API 是无状态的,可以在执行不同输入的时候进行重置,那么使用长期存活的进程来测试多个用例,以此来减少 fork 所带来的开销。
我们需要添加如下代码:
1 2 3 4 5 6 7 8 9 while (__AFL_LOOP(1000 )) { }
主要的步骤是进行一个循环:
循环的次数也就是 AFL 从头开始重新启动的最大迭代次数,官方建议数值为 1000,太大了可能出现很多意想不到的问题。
其中 __AFL_LOOP() 也就是 afl-clang-fast.c 中定义的宏定义:
1 2 3 4 5 6 7 8 9 10 11 cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)=" "({ static volatile char *_B __attribute__((used)); " " _B = (char*)\"" PERSIST_SIG "\"; " #ifdef __APPLE__ "__attribute__((visibility(\"default\"))) " "int _L(unsigned int) __asm__(\"___afl_persistent_loop\"); " #else "__attribute__((visibility(\"default\"))) " "int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); " #endif "_L(_A); })" ;
会走到 ___afl_persistent_loop 函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 int __afl_persistent_loop(unsigned int max_cnt) { static u8 first_pass = 1 ; static u32 cycle_cnt; if (first_pass) { if (is_persistent) { memset (__afl_area_ptr, 0 , MAP_SIZE); __afl_area_ptr[0 ] = 1 ; __afl_prev_loc = 0 ; } cycle_cnt = max_cnt; first_pass = 0 ; return 1 ; } if (is_persistent) { if (--cycle_cnt) { raise(SIGSTOP); __afl_area_ptr[0 ] = 1 ; __afl_prev_loc = 0 ; return 1 ; } else { __afl_area_ptr = __afl_area_initial; } } return 0 ; }
判断是否是第一次执行循环
如果设置了 is_persistent
清空 __afl_area_ptr
将 __afl_area_ptr[0] 设为 1
将 __afl_prev_loc 设为 0
设置 cycle_cnt 为 max_cnt ,first_pass 为 1 并返回 1
如果设置了 is_persistent
递减 cycle_cnt ,如果结果不为 0
发出 SIGSTOP 信号让程序暂停
将 __afl_area_ptr[0] 设为 1
将 __afl_prev_loc 设为 0
返回 1
否则将 __afl_area_ptr 置为 __afl_area_initial (无关数组)
返回 0
trace-pc-guard mode 该功能需要设置宏 AFL_TRACE_PC=1 ,并且在使用 afl-clang-fast 时传入参数 -fsanitize-coverage=trace-pc-guard
该功能的主要特点是会在每个 edge 插入桩代码,也就是都会调用 __sanitizer_cov_trace_pc_guard 函数:
1 2 3 4 5 6 7 8 9 10 void __sanitizer_cov_trace_pc_guard(uint32_t * guard) { __afl_area_ptr[*guard]++; }
简单的对 __afl_area_ptr 对应位置出进行递增
其中 guard 的初始化位于函数 __sanitizer_cov_trace_pc_guard_init 中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void __sanitizer_cov_trace_pc_guard_init(uint32_t * start, uint32_t * stop) { u32 inst_ratio = 100 ; u8* x; if (start == stop || *start) return ; x = getenv("AFL_INST_RATIO" ); if (x) inst_ratio = atoi(x); if (!inst_ratio || inst_ratio > 100 ) { fprintf (stderr , "[-] ERROR: Invalid AFL_INST_RATIO (must be 1-100).\n" ); abort (); } *(start++) = R(MAP_SIZE - 1 ) + 1 ; while (start < stop) { if (R(100 ) < inst_ratio) *start = R(MAP_SIZE - 1 ) + 1 ; else *start = 0 ; start++; } }
从环境变量 AFL_INST_RATIO 中读取 inst_ratio ,且检查 inst_ratio 是否在 0 到 100 中间,不是的话抛出错误,inst_ratio 的默认值为 100
对于 start 和 stop ,就是 guard 的起始和中止地
从 start 开始依次获得随机值 R(100) ,并检查其是否小于 inst_ratio
如果小于,则将当前的 guard 赋值为 R(MAP_SIZE - 1) + 1
否则赋值为 0
第一个 guard 总是 R(MAP_SIZE - 1) + 1
可以看的出 trace-pc-guard mode 的插桩逻辑也和之前分析的别的方法倒差不差。