llvm mode
afl-llvm AFL的 llvm_mode
可以实现编译器级别的插桩,可以替代 afl-gcc
或 afl-clang
使用的比较粗暴 的汇编级别的重写的方法,且具备如下几个优势:
编译器可以进行很多优化以提升效率;
可以实现CPU无关,可以在非 x86 架构上进行fuzz;
可以更好地处理多线程目标。
afl-clang-fast 整体实现思路和afl-gcc没什么太大的区别
edit_param
将参数参数换成clang的
加载so文件
1 $ clang -Xclang load -Xclang afl-llvm-pass.so ...
然后环境变量和参数和 afl-gcc大差不差
特性:USE_TRACE_PC
1 -fsanitize-coverage=trace-pc-guard -mllvm (only Android) -sanitizer-coverage-block-threshold=0 (only Android)
定义了两个比较重要的宏 __AFL_LOOP(_A)
和 __AFL_INIT()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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)" ;
Pass LLVM Pass: 在Pass遍历LLVM IR的同时,自然就可以往里面插入新的代码。
实现了一个pass,AFLCoverage
,和插桩和覆盖率统计有关
1 2 3 4 5 6 7 8 9 10 11 12 namespace { class AFLCoverage : public ModulePass { public : static char ID; AFLCoverage () : ModulePass (ID) { } bool runOnModule (Module &M) override ; }; }
获取全局变量,也就是SHM
全局变量
1 2 3 4 5 6 7 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 );
pass ir每一个函数(Function),每个基本块(Basic Block),目的是为了插桩统计覆盖率
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 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 ; }
插桩部分:IRBuilder
1 $ clang -Xclang load -Xclang afl-llvm-pass.so ...
需要一点LLVM的知识
构建pass.so -> 使用 so 编译样本 -> 构建运行时(rt) -> 链接生成可执行文件
IRB.CreateXxx
插入了 Xxx 类型的指令,类似汇编语言
CreateLoad: load xxx 获取地址 CreateXor: xor a1, a2
llvm-rt main函数执行find_obj
,寻找runtime library,与afl-as功能类似
该文件是运行时库文件,实现了 llvm-mode 的3个特殊功能:deferred instrumentation
、persistent mode
、trace-pc-guard mode
。
重要的变量
1 2 3 4 u8 __afl_area_initial[MAP_SIZE]; u8* __afl_area_ptr = __afl_area_initial; __thread u32 __afl_prev_loc;
deferred instrumentation AFL会尝试通过只执行一次目标二进制文件来提升性能,在 main()
之前暂停程序,然后克隆“主”进程获得一个稳定的可进行持续fuzz的目标。简言之,避免目标二进制文件的多次、重复的完整运行 ,而是采取了一种类似快照 的机制。
1 2 3 #ifdef __AFL_HAVE_MANUAL_CONTROL __AFL_INIT(); #endif
初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 __attribute__((constructor (CONST_PRIO))) void __afl_auto_init(void ) { is_persistent = !!getenv (PERSIST_ENV_VAR); if (getenv (DEFER_ENV_VAR)) return ; __afl_manual_init(); } void __afl_manual_init(void ) { static u8 init_done; if (!init_done) { __afl_map_shm(); __afl_start_forkserver(); init_done = 1 ; } }
获得fuzzer创建的共享内存
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 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 ; } }
forkserver:pipe与fuzzer进程进行通信,然后fork(),子进程运行测试程序,父进程监视,与fuzzer进行通信
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 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 ); } }
persistent mode persistent mode
并没有通过fork子进程的方式来执行fuzz。一些库中提供的API是无状态的,或者可以在处理不同输入文件之间进行重置,恢复到之前的状态。执行此类重置时,可以使用一个长期存活的进程来测试多个用例,以这种方式来减少重复的 fork()
调用和操作系统的开销。
一个基础的框架大概如下:
1 2 3 4 5 6 7 8 9 while (__AFL_LOOP(1000 )) { }
一个样例
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <string.h> int main (int argc, char ** argv) { char buf[100 ]; while (__AFL_LOOP(1000 )) { memset (buf, 0 , 100 ); read (0 , buf, 100 ); if (buf[0 ] == 'f' ) { printf ("one\n" ); if (buf[1 ] == 'o' ) { printf ("two\n" ); if (buf[2 ] == 'o' ) { printf ("three\n" ); if (buf[3 ] == '!' ) { printf ("four\n" ); abort (); } } } } } return 0 ; }
主要的逻辑
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 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 ; }
逻辑
第一次执行loop循环,进行初始化,然后返回1。
执行一次fuzz,计数器cnt减1,抛出SIGSTOP信号暂停子进程;
其余执行loop循环,恢复之前暂停的子进程继续执行(forkserver),并设置 child_stopped
为0。此时相当于重新执行了一次程序,重新对 __afl_prev_loc
进行设置,随后返回1,再次进入 while(_AFL_LOOP(1000))
,执行一次fuzz,计数器cnt减1,抛出SIGSTOP信号暂停子进程;
第1000次执行,计数器cnt此时为0,不再暂停子进程,令 __afl_area_ptr
指向无关数组 __afl_area_initial
,随后子进程结束。
trace-pc 该模式需要先构建 afl-clang-fast
时指定 AFL_TRACE_PC=1
,在使用 afl-clang-fast
时加上 fsanitize-coverage=trace-pc-guard
参数来开启该功能。这种模式下的插桩,会在每个 edge 处都进行插桩,而不再是基本块。
afl-clang-lto aflplusplus
LTO(Link Time Optimization)链接时优化是链接期间的程序优化,多个中间文件通过链接器合并在一起,并将它们组合为一个程序,缩减代码体积,因此链接时优化是对整个程序的分析和跨模块的优化。
源码安装afl-clang-lto
,需要安装 llvm
和 lld
(version >= 11)
1 $ sudo apt install llvm lld
然后根据AFLplusplus/instrumentation/README.lto.md
1 2 3 4 $ export LLVM_CONFIG=llvm-config-16 $ make $ sudo make install
源码:todo
参考文章