AFL(American Fuzzy Lop)源码详细解读(8)

这篇记录 llvm mode 中的 afl-llvm-pass.so.cc 文件和 afl-llvm-rt.o.c 文件,以及整体流程的简述。
对llvm这部分理解还比较浅。
可以阅读大佬的这篇文章:https://eternalsakura13.com/2020/08/23/afl/#more,讲的很透彻。

afl-llvm-pass.so.cc

using namespace llvm;

namespace {
    // 在命名空间中定义一个继承自ModulePass的类AFLCoverage。ModulePass是最通用的一个父类,继承该类表明此Pass将整个程序作为一个单元
    // 因此选择继承不同类型的Pass父类就是从不同的粒度对程序进行处理。这里选择继承ModulePass就是对整个程序都进行处理。
  class AFLCoverage : public ModulePass {

    public:

      static char ID;   					// LLVM识别Pass的标识符
      AFLCoverage() : ModulePass(ID) { }

      bool runOnModule(Module &M) override;

      // StringRef getPassName() const override {
      //  return "American Fuzzy Lop Instrumentation";
      // }

  };

}


char AFLCoverage::ID = 0;


bool AFLCoverage::runOnModule(Module &M) {

  LLVMContext &C = M.getContext();  // 获取线程上下文

  IntegerType *Int8Ty  = IntegerType::getInt8Ty(C);
  IntegerType *Int32Ty = IntegerType::getInt32Ty(C);

  /* Show a banner */
  // 打印banner
  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)");

  }

  /* Get globals for the SHM region and the previous location. Note that
     __afl_prev_loc is thread-local.   
     获取指向 SHM 区域和上一个块ID的全局变量指针。 注意 __afl_prev_loc 是线程本地的 
     */

  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);

  /* Instrument all the things! */

  int inst_blocks = 0;

  for (auto &F : M)     	// 遍历Module中的每个
    for (auto &BB : F) {    // 遍历Function中的每个BasicBlock

      BasicBlock::iterator IP = BB.getFirstInsertionPt();
      IRBuilder<> IRB(&(*IP));

      if (AFL_R(100) >= inst_ratio) continue;   // 以一定概率进行插桩

      /* Make up cur_loc */

      unsigned int cur_loc = AFL_R(MAP_SIZE);   // 随机生成当前块的ID

      ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc);

      /* Load prev_loc */
      // 加载前驱块的ID
      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));

      /* Update bitmap */
      // 更新位图,将对应位置加1,代表边的执行次数加1
      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 */
      // 更新全局变量AFLPrevLoc的值为 当前基本块的ID右移一位
      StoreInst *Store =
          IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
      Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));

      inst_blocks++;    // 插桩基本块数量加一

    }

  /* Say something nice. */
  // 打印一些信息
  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;

}

// 注册Pass
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);

使用下面两条指令可以分别得到插桩前和插桩后的IR文件

clang -emit-llvm -S test.c -o a.ll			 	# a.ll插桩前的IR
afl-clang-fast -emit-llvm -S test.c -o b.ll		# b.ll插桩后的IR

afl-llvm-rt.o.c

根据README文件可知,该文件主要提供了三个额外的功能

deferred instrumentation

AFL会尝试通过仅执行一次目标二进制文件来优化性能。它会暂停控制流,然后复制该“主”进程以持续提供fuzzer的目标。该功能在某些情况下可以减少操作系统、链接与libc内部执行程序的成本。

要想使用这个功能,需要在合适的位置插入如下代码。

#ifdef __AFL_HAVE_MANUAL_CONTROL
 __AFL_INIT();
#endif

需要注意的是,不能在如下几个位置插入上述代码

  • 任何重要线程或子进程的创建 - 因为 forkserver 无法轻松克隆它们。
  • 通过 setitimer() 或等效调用初始化计时器。
  • 临时文件、network sockets、偏移敏感文件描述符和类似的共享状态资源的创建——但前提是它们的状态会有意义地影响以后程序的行为。
  • 对模糊册数输入的任何访问,包括读取有关其大小的元数据。

然后 afl-clang-fast 重新编译即可。需要注意的是afl-gcc 和 afl-clang 是没有这个功能的。 这很容易理解,因为关于 __AFL_HAVE_MANUAL_CONTROL __AFL_INIT() 的定义是在 afl-clang-fast 在处理参数时添加的,再交由真正的编译器处理。回顾上一篇打印出来的afl-clang-fast处理后的参数

在这里插入图片描述

其中__AFL_INIT()展开后大概就是这样。摘自https://eternalsakura13.com/2020/08/23/afl/#more

#define __AFL_INIT() \
 do { \
     static char *_A;  \
     _A = (char*)"##SIG_AFL_DEFER_FORKSRV##"; \
     __afl_manual_init(); \
 } while (0)

可以看到调用了__afl_manual_init,该函数在未初始化的情况下调用__afl_map_shm__afl_start_forkserver设置共享内存和初始化forkserver

1. __afl_map_shm 设置共享内存

  • 获取环境变量 SHM_ENV_VAR 赋值给 id_str
  • 如果环境变量存在
    • 转换为整数,赋值给shm_id
    • 将共享内存映射到当前进程的地址空间,__afl_area_ptr指向该地址
    • 如果__afl_area_ptr = -1,表示映射失败,退出程序
    • 将共享内存的第一个位置置1。源码注释解释道:在位图中写入一些内容,这样即使 AFL_INST_RATIO 较低,我们的父进程也不会放弃我们

2. __afl_start_forkserver

  • child_stopped = 0
  • 向状态管道中写入4字节,通知fuzzer,fork server准备完毕。tmp无论为何值,只要能正确写入或是读取四个字节就可以。
  • 进入while循环
    • 从控制管道读取父进程传来的命令
    • 如果child_stopped 为 1并且was_killed不为0,表示如果子进程处于暂停状态,并且被kill了,则等待子进程彻底结束
      • child_stopped = 0
      • 等待子进程结束
    • 如果child_stopped 为 0
      • fork出一个子进程
      • 子进程关闭状态管道和控制管道,return恢复执行
    • 如果child_stopped为1,这是对于persistent mode的特殊处理,此时子进程还活着,只是被暂停了,所以可以通过kill(child_pid, SIGCONT)来简单的重启,然后设置child_stopped为0。
    • 将PID发送给Fuzzer
    • 等待子进程结束,注意这里在persistent mode时,会设置waitpid的第三个参数为WUNTRACED,代表若子进程进入暂停状态,则该函数马上返回
    • WIFSTOPPED(status)如果子进程的结束状态为暂停,置child_stopped = 1
    • 将子进程的结束状态发送给Fuzzer

persistent mode

该功能也仅适用于 afl-clang-fast

persistent mode的功能是将已经完成fuzz的一个testcase的子进程进行复用,从而节省计算机开销,但是需要注意的是每次fuzz过程都会改变一些进程或线程的状态变量,因此,在复用这个fuzz子进程的时候需要将这些变量恢复成初始状态,否则会导致下一次fuzz过程的不准确。所以状态初始化的工作只对第一个循环做。之后的初始化工作都交给父进程。

程序的基本结构

while (__AFL_LOOP(1000)) {

   /* Read input data. */
   /* Call library code to be fuzzed. */
   /* Reset state. */

 }

循环次数为1000是因为这最大限度地减少了内存泄漏和类似故障的影响,更高的值会增加“打嗝”的可能性,从而没有了性能优势。

__AFL_LOOP也是在 afl-clang-fast 在处理参数时添加的

#define __AFL_LOOP() \
 do { \
     static char *_B; \
     _B = (char*)"##SIG_AFL_PERSISTENT##"; \
     __afl_persistent_loop(); \
 }while (0)

3. __afl_persistent_loop

  • first_pass = 1
  • 如果first_pass 为 1,即表示第一次执行
    • 如果在persistent模式下
      • 共享内存清空为0
      • __afl_area_ptr[0] = 1,应该跟上面__afl_map_shm中最后一步的作用一样
      • __afl_prev_loc = 0,前一个基本块ID置0
    • cycle_cnt 置为max_cnt
    • first_pass 置为0
    • return 1
  • 如果不是第一次执行
    • 如果在persistent模式下
    • cycle_cnt 减1 如果大于0
      • 发出SIGSTOP信号,暂停当前进程
      • __afl_area_ptr[0] = 1
      • __afl_prev_loc = 0
      • return 1
    • 如果cycle_cnt 减1 如果等于 0
      • __afl_area_ptr指向一个虚拟的区域

4. __afl_auto_init

需要注意的是 被 _attribute_ constructor 修饰的函数将在main执行之前自动运行

  • 读取环境变量PERSIST_ENV_VAR的值,赋值给is_persistent,表示以persistent模式fuzz
  • 如果存在环境变量 DEFER_ENV_VAR ,直接返回。这代表__afl_auto_init和deferred instrumentation不通用,因为deferred instrumentation会自己选择合适的时机,手动init,不需要用这个函数来init,所以这个函数只在没有手动init的时候会自动init。
  • 调用__afl_manual_init,该函数在未初始化的情况下调用__afl_map_shm__afl_start_forkserver设置共享内存和初始化fork server

trace-pc-guard mode

如果想尝试一下这个功能,则需要执行这条代码来重新编译afl-clang-fast,从而在执行afl-clang-fast的时候传入-fsanitize-coverage=trace-pc-guard参数,来开启这个功能,和之前我们的插桩不同,开启了这个功能之后,我们不再是仅仅只对每个基本块插桩,而是对每条edge都进行了插桩。

AFL_TRACE_PC=1 make clean all

5. __sanitizer_cov_trace_pc_guard

__sanitizer_cov_trace_pc_guard这个函数将在每个edge调用,该函数利用函数参数guard指针所指向的uint32值来确定共享内存上所对应的地址。
每个edge上都有应该有其不同的guard值

6. __sanitizer_cov_trace_pc_guard_init

  • inst_ratio插桩概率为100
  • 如果start等于stop或是,直接return
  • 获取环境变量"AFL_INST_RATIO"的值赋给x
  • 如果环境变量存在,转换为整数赋值给inst_ratio
  • 如果inst_ratio的值不合法,抛出异常
  • 遍历startstop,设置为[1, MAP_SIZE)中的一个随机数,为0则表示对这个edge不进行插桩

整体流程简述

  • 使用afl-clang-fast test.c -o a.out 编译test.c文件后
  • afl-clang-fast 会处理参数,这期间会将 afl-llvm-pass.so 添加到参数当中
  • llvm根据 afl-llvm-pass.so中的代码对文件进行插桩
  • forkserver 是何时启动的呢?注意afl-clang-fast最后还将 afl-llvm-rt.o 添加到参数当中
  • afl-llvm-rt.o当中有这么个__attribute__((constructor(CONST_PRIO))) void __afl_auto_init(void)函数,会在main函数前自动运行
  • 于是就调用了__afl_manual_init()
  • __afl_manual_init()有调用了__afl_map_shm()__afl_start_forkserver() 就将共享内存映射到当前进程的地址空间中了,以及启动了fork server
  • 之后,就正常fuzz了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值