首页
社区
课程
招聘
[原创]AFL之llvm mode部分源码分析
发表于: 2021-2-14 15:11 14210

[原创]AFL之llvm mode部分源码分析

2021-2-14 15:11
14210

新年快乐!

本篇文章是AFL源码阅读系列的最后一篇,在本篇之后就不会再专门大范围的聊AFL源码了,如果后续在实践过程中学习到了一些新的AFL使用/魔改技巧会出番外篇再分享~(茶)

本篇文章主要讲3个文件:

最好是看完上一篇再来看本篇。

本文件作为clang的wrapper

如果经历以上过程均没有找到,那么abort掉。

本函数编辑参数数组。

接下来AFL提到了如下两种方式来进行插桩:
1.传统模式:使用afl-llvm-pass.so注入来插桩。
2.'trace-pc-guard' mode:使用原生的 LLVM instrumentation callbacks

第二种方式相关链接如下:
https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards

接下来扫描参数数组,设置对应的标志位

如果环境变量设置了"AFL_HARDEN"

如果是使用的方式2进行插桩。判断是否设置"AFL_INST_RATIO",若设置了则abort

接下来设置一些优化选项与对内置函数的检查。然后定义了两个宏,如下:

快速了解llvm可以看一下:
https://zhuanlan.zhihu.com/p/122522485
https://llvm.org/docs/WritingAnLLVMPass.html#introduction-what-is-a-pass
在AFL中只有一个Pass:

在AFLCoverage::runOnModule中进行如下操作。获取线程上下文。https://stackoverflow.com/questions/13184835/what-is-llvm-context

如果stderr为终端。且未设置"AFL_QUIET"模式。输出对应的信息。
否则设置be_quiet = 1。

设置插桩密度。

获取前一个桩的位置(随机数编号)

接下来进入插桩过程,扫描basic block:

生成当前block的随机编号

首先获取共享内存块的地址,然后找到对应当前桩的计数位置

设置对应的AFLPrevLoccur_loc >> 1,关于为什么要右移1,主要是为了做路径区分,可以看上一篇。

整体的过程还是非常清晰的。

afl的llvmmode中有三个功能在这里实现。

AFL尝试通过仅执行目标二进制文件一次来优化性能,
在main()之前停止它,然后克隆此“主”进程以获取
稳定提供fuzz目标。

尽管这种方法消除了许多OS,链接器和libc级别
执行程序的成本,它并不总是对二进制文件有帮助
执行其他耗时的初始化步骤-例如,解析大型配置
文件进入模糊数据。

在这种情况下,最好稍后再初始化forkserver
大多数初始化工作已经完成,但是在二进制尝试之前
读取模糊的输入并进行解析;在某些情况下,这可以提供10倍以上的收益
性能提升。

只需将:

这一段插入对应位置即可。
具体可见上文的-D__AFL_INIT()
真正起作用的是如下:

接下来是__afl_start_forkserver()

一些库提供的API是无状态的,或者可以在其中重置状态的API
处理不同的输入文件之间。进行此类重置后,
一个长期存在的过程可以重复使用,以尝试多个测试用例,
消除了重复执行fork()调用的需求以及相关的OS开销。

基本结构如下:

关于循环的最大数量,循环内指定的数值控制AFL从头重新启动过程之前的最大迭代次数。这样可以最大程度地减少内存泄漏和类似故障的影响;1000是一个很好的起点,而更高的值会增加出现hiccups的可能性,而不会给带来任何实际的性能优势。

整体过程大致如下:
当第一次运行到AFL_LOOP 时,进行初始化然后return 1,此时满足```while (AFL_LOOP(1000))```,于是执行一次fuzz。

当我们再次进入fuzz loop时,计数减1,触发:raise(SIGSTOP)暂停进程,而forkserver收到了此时的暂停信号,设置child_stopped = 1,通知afl-fuzzer。

当afl-fuzzer再进行一次fuzz时,恢复之前的子进程继续执行,并设置child_stopped为0。

此时相当于重新执行了一遍程序,重新对afl_prev_loc设置,随后直接返回1,此时又进入```while (AFL_LOOP(1000))执行一次,接下来下一次触发raise(SIGSTOP)```暂停进程。

https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards
如果想使用这个功能需要设置:

传入:With -fsanitize-coverage=trace-pc-guard
the compiler will insert the following code on every edge
然后重新编译。

此函数__sanitizer_cov_trace_pc_guard将在每个basic block edge(边界)被调用,其实就是通过(*guard)索引到共享内存对应的计数位置,然后计数加一。

而guard的初始化如下:

首先获取了插桩密度。

然后从第一个guard遍历。llvm设置guard的收尾为start与stop。并设置guard指向的值。

注意有一定的概率不进行插桩(随机插桩)。

特别的,若此时的basic block因为概率选择的原因没有进行插桩,那么设置这里的guard指向的值为0,这里的0是作为一个特殊值,代表不进行插桩。

 
 
 
 
  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;
  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;
-D__AFL_LOOP(_A)=
({ static volatile char *_B __attribute__((used));
_B = (char*)##SIG_AFL_PERSISTENT##; \
__attribute__((visibility("default")))int _L(unsigned int) __asm__("___afl_persistent_loop");
_L(_A); })
-D__AFL_LOOP(_A)=
({ static volatile char *_B __attribute__((used));
_B = (char*)##SIG_AFL_PERSISTENT##; \
__attribute__((visibility("default")))int _L(unsigned int) __asm__("___afl_persistent_loop");
_L(_A); })
-D__AFL_INIT()=
do {
  static volatile char *_A __attribute__((used)); \
  _A = (char*)##SIG_AFL_DEFER_FORKSRV## ; \
__attribute__((visibility("default")))void _I(void) __asm__("___afl_manual_init"); \
_I(); } while (0)
-D__AFL_INIT()=
do {
  static volatile char *_A __attribute__((used)); \
  _A = (char*)##SIG_AFL_DEFER_FORKSRV## ; \
__attribute__((visibility("default")))void _I(void) __asm__("___afl_manual_init"); \
_I(); } while (0)
namespace {
 
  class AFLCoverage : public ModulePass {
 
    public:
 
      static char ID;
      AFLCoverage() : ModulePass(ID) { }
 
      bool runOnModule(Module &M) override;
 
  };
 
}
namespace {
 
  class AFLCoverage : public ModulePass {
 
    public:
 
      static char ID;
      AFLCoverage() : ModulePass(ID) { }
 
      bool runOnModule(Module &M) override;
 
  };
 
}
LLVMContext &C = M.getContext();
 
IntegerType *Int8Ty  = IntegerType::getInt8Ty(C);
IntegerType *Int32Ty = IntegerType::getInt32Ty(C);
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 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;
/* Decide instrumentation ratio */
 
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)");
 
}
/* Decide instrumentation ratio */
 
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 *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);
GlobalVariable *AFLPrevLoc = new GlobalVariable(
    M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc",
    0, GlobalVariable::GeneralDynamicTLSModel, 0, false);
BasicBlock::iterator IP = BB.getFirstInsertionPt();
    IRBuilder<> IRB(&(*IP));
BasicBlock::iterator IP = BB.getFirstInsertionPt();
    IRBuilder<> IRB(&(*IP));
if (AFL_R(100) >= inst_ratio) continue;
if (AFL_R(100) >= inst_ratio) continue;
/* Make up cur_loc */
 
unsigned int cur_loc = AFL_R(MAP_SIZE);
 
ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc)
/* Make up cur_loc */
 
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 *PrevLoc = IRB.CreateLoad(AFLPrevLoc);
    PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
    Value *PrevLocCasted = IRB.CreateZExt(PrevLoc, IRB.getInt32Ty());
/* Load SHM pointer */
 
  LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr);
  MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
  Value *MapPtrIdx =
      IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc))
/* Load SHM pointer */
 
  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));
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));
/* Set prev_loc to cur_loc >> 1 */
 
    StoreInst *Store =
        IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
    Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
/* Set prev_loc to cur_loc >> 1 */
 
    StoreInst *Store =
        IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
    Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
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);
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);
 
 
 
#ifdef __AFL_HAVE_MANUAL_CONTROL
  __AFL_INIT();
#endif
#ifdef __AFL_HAVE_MANUAL_CONTROL
  __AFL_INIT();
#endif
void __afl_manual_init(void) {
 
  static u8 init_done;
 
  if (!init_done) {
 
    __afl_map_shm();
    __afl_start_forkserver();
    init_done = 1;
 
  }
 
}
void __afl_manual_init(void) {
 
  static u8 init_done;
 
  if (!init_done) {
 
    __afl_map_shm();
    __afl_start_forkserver();
    init_done = 1;
 
  }
 
}
/* SHM setup. */
 
static void __afl_map_shm(void) {
 
  u8 *id_str = getenv(SHM_ENV_VAR);//通过环境变量读取id
 
  /* If we're running under AFL, attach to the appropriate region, replacing the
     early-stage __afl_area_initial region that is needed to allow some really
     hacky .init code to work correctly in projects such as OpenSSL. */
 
  if (id_str) {                   //如果读取成功
 
    u32 shm_id = atoi(id_str);   
 
    __afl_area_ptr = shmat(shm_id, NULL, 0);//获取shm的地址为__afl_area_ptr
 
    /* Whooooops. */
 
    if (__afl_area_ptr == (void *)-1) _exit(1);
 
    /* Write something into the bitmap so that even with low AFL_INST_RATIO,
       our parent doesn't give up on us. */
 
    __afl_area_ptr[0] = 1;                  //设置__afl_area_ptr[0]为1
 
  }
 
}
/* SHM setup. */
 
static void __afl_map_shm(void) {
 
  u8 *id_str = getenv(SHM_ENV_VAR);//通过环境变量读取id
 
  /* If we're running under AFL, attach to the appropriate region, replacing the
     early-stage __afl_area_initial region that is needed to allow some really
     hacky .init code to work correctly in projects such as OpenSSL. */
 
  if (id_str) {                   //如果读取成功
 
    u32 shm_id = atoi(id_str);   
 
    __afl_area_ptr = shmat(shm_id, NULL, 0);//获取shm的地址为__afl_area_ptr
 
    /* Whooooops. */
 
    if (__afl_area_ptr == (void *)-1) _exit(1);
 
    /* Write something into the bitmap so that even with low AFL_INST_RATIO,
       our parent doesn't give up on us. */
 
    __afl_area_ptr[0] = 1;                  //设置__afl_area_ptr[0]为1
 
  }
 
}
static void __afl_start_forkserver(void) {
 
  static u8 tmp[4];
  s32 child_pid;
 
  u8  child_stopped = 0;
 
  /* Phone home and tell the parent that we're OK. If parent isn't there,
     assume we're not running in forkserver mode and just execute program. */
 
  if (write(FORKSRV_FD + 1, tmp, 4) != 4) return; //向状态管道写入4字节告知已启动
 
  while (1) {
 
    u32 was_killed;
    int status;
 
    /* Wait for parent by reading from the pipe. Abort if read fails. */
    /* 当子进程超时,父进程会kill掉子进程 */
    if (read(FORKSRV_FD, &was_killed, 4) != 4) _exit(1);
 
    /* If we stopped the child in persistent mode, but there was a race
       condition and afl-fuzz already issued SIGKILL, write off the old
       process. */
 
    /* 如果在persistent mode下,且子进程已经被killed */
    if (child_stopped && was_killed) {
      child_stopped = 0;
      if (waitpid(child_pid, &status, 0) < 0) _exit(1);
    }
 
    if (!child_stopped) {       //如果子进程真的彻底结束了
 
      /* Once woken up, create a clone of our process. */
      //重新fork一次
      child_pid = fork();
      if (child_pid < 0) _exit(1);
 
      /* In child process: close fds, resume execution. */
      //如果是fork出的子进程
      if (!child_pid) {
 
        close(FORKSRV_FD);        //关闭对应描述符。然后返回执行真正的程序
        close(FORKSRV_FD + 1);
        return;
 
      }
 
    } else {
 
      /* Special handling for persistent mode: if the child is alive but
         currently stopped, simply restart it with SIGCONT. */
 
      /* 如果子进程并非彻底结束而是暂停 */
      /* 重新启动这个暂停的子进程 */
      kill(child_pid, SIGCONT);
      child_stopped = 0;
 
    }
 
    /* In parent process: write PID to pipe, then wait for child. */
 
    //在父进程(fork-server)中,向afl-fuzzer写4字节(子进程pid)到管道,告知fuzzer
    if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) _exit(1);
    //读取子进程退出状态
    if (waitpid(child_pid, &status, is_persistent ? WUNTRACED : 0) < 0)
      _exit(1);
 
    /* In persistent mode, the child stops itself with SIGSTOP to indicate
       a successful run. In this case, we want to wake it up without forking
       again. */
    //子进程收到停止信号,此时子进程可能是停止或结束。
    if (WIFSTOPPED(status)) child_stopped = 1//child_stopped=1则不确定究竟是否彻底结束
 
 
    //向状态管道写入4字节
    if (write(FORKSRV_FD + 1, &status, 4) != 4) _exit(1);
 
  }
 
}
static void __afl_start_forkserver(void) {
 
  static u8 tmp[4];
  s32 child_pid;
 
  u8  child_stopped = 0;
 
  /* Phone home and tell the parent that we're OK. If parent isn't there,
     assume we're not running in forkserver mode and just execute program. */
 
  if (write(FORKSRV_FD + 1, tmp, 4) != 4) return; //向状态管道写入4字节告知已启动
 
  while (1) {
 
    u32 was_killed;
    int status;
 
    /* Wait for parent by reading from the pipe. Abort if read fails. */
    /* 当子进程超时,父进程会kill掉子进程 */
    if (read(FORKSRV_FD, &was_killed, 4) != 4) _exit(1);
 
    /* If we stopped the child in persistent mode, but there was a race
       condition and afl-fuzz already issued SIGKILL, write off the old
       process. */
 
    /* 如果在persistent mode下,且子进程已经被killed */
    if (child_stopped && was_killed) {
      child_stopped = 0;
      if (waitpid(child_pid, &status, 0) < 0) _exit(1);
    }
 
    if (!child_stopped) {       //如果子进程真的彻底结束了
 
      /* Once woken up, create a clone of our process. */
      //重新fork一次
      child_pid = fork();
      if (child_pid < 0) _exit(1);
 
      /* In child process: close fds, resume execution. */
      //如果是fork出的子进程
      if (!child_pid) {
 
        close(FORKSRV_FD);        //关闭对应描述符。然后返回执行真正的程序
        close(FORKSRV_FD + 1);
        return;
 
      }
 
    } else {
 
      /* Special handling for persistent mode: if the child is alive but
         currently stopped, simply restart it with SIGCONT. */
 
      /* 如果子进程并非彻底结束而是暂停 */
      /* 重新启动这个暂停的子进程 */
      kill(child_pid, SIGCONT);

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-2-14 15:13 被Roland_编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (2)
雪    币: 14517
活跃值: (17538)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
2
感谢分享,高产似那啥
2021-2-14 16:13
0
雪    币: 1552
活跃值: (1288)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
3

两种插桩模式的区别是什么?

最后于 2021-6-8 08:49 被nicaicaiwo编辑 ,原因:
2021-6-7 08:44
0
游客
登录 | 注册 方可回帖
返回
//