afl覆盖信息的处理(afl-llvm-pass-so.c代码分析)

插桩统计覆盖率的相关部分主要在afl-llvm-pass-so.c中的bool AFLCoverage::runOnModule(Module &M)部分

插桩率设置

在函数开始会有相关变量的初始化工作,但是主要值得关注的是关于inst_ratio_str的设置。inst_ratio_str表示 AFL 应该插桩的基本块的比例。例如,如果 AFL_INST_RATIO 设置为 10,那么 AFL 大约会选择 10% 的基本块进行插桩。如果 AFL_INST_RATIO 未设置,或者解析失败,或者值超出了允许的范围,就会使用默认值 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)");
  }

关键全局变量

之后定义了两个关键的全局变量。
AFLMapPtr是一个指针类型的全局变量,指向 AFL 共享内存区域的开始。这个内存区域存储了 AFL 追踪数据(trace_bits),用于在程序运行时记录哪些路径(或更具体地说,哪些基本块转移)已经被执行过。当 AFL 插桩的代码执行时,它会更新这个内存区域的相应部分。

AFLPrevLoc是一个 32 位整数类型的全局变量,存储了上一个执行的基本块的 ID。注意,这个变量是线程局部的,这意味着每个线程都有自己的一份副本。这样做是为了避免在多线程环境下出现数据竞态。当 AFL 插桩的代码执行时,它会用当前基本块的 ID 更新这个变量。

/* Get globals for the SHM region and the previous location. Note that

__afl_prev_loc is thread-local. */

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

进行插桩处理

重要的解释都在下面代码相关行段,主要解释一些补充内容。

IRB的作用和用法

IRB 主要被用来在LLVM的BasicBlock中插入新的IR指令。例如,IRB.CreateLoadIRB.CreateZExtIRB.CreateGEPIRB.CreateAddIRB.CreateStore 都是通过IRB创建新的Load、ZExt(零扩展)、GEP(GetElementPointer,获取元素指针)、Add和Store指令。

创建 IRB 的时候,将一个Instruction指针传递给 IRB 的构造函数,这个Instruction指定了新创建的IR指令将被插入的位置。在这个例子中,新创建的IR指令被插入到每个BasicBlock的第一个插入点。

为什么要创建随机位置cur_loc

在AFL的插桩策略中,为每个插桩点生成一个随机位置 cur_loc 的主要原因是为了创建一个独特的、在全局范围内唯一的标识符。AFL使用的覆盖追踪策略主要依赖于这个 cur_loc。在每次执行插桩的基本块时,AFL将当前的 cur_loc 与上一个 cur_loc 进行异或操作,并将结果用作索引,将共享内存中该索引位置的计数器增加1。通过这种方式,AFL可以通过观察共享内存区的变化来知道哪些插桩的基本块被执行了,从而了解代码的覆盖情况。

Value *MapPtrIdx = IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc));解释

首先进行了一个 XOR 位运算(通过 IRB.CreateXor(PrevLocCasted, CurLoc) 实现),将上一个位置的值与当前位置的值进行异或操作。
然后使用CreateGEP函数根据异或操作的结果生成一个新的指针。GEP 代表 GetElementPointer,它是 LLVM IR 中的一种指令,用于计算复杂数据结构(如数组、结构体)中元素的地址。在 AFL 中,这个指令被用来在共享内存区中定位到一个特定的位置,这个位置对应的是当前和上一个插桩代码块的异或结果。这样就能对每一对连续执行的插桩代码块进行唯一标识,实现精确的路径覆盖统计。

LoadInst是什么数据类型

LoadInst 是一种指令类,继承自 Instruction 类。它代表了一个从内存加载数据的操作,与在 C 或者 C++ 中的读取变量值的操作相对应。

  /* Instrument all the things! */
  #定义一个计数器,用来统计插桩的BasicBlock的数量。
  int inst_blocks = 0;
  #这两个嵌套的for循环用来遍历Module M中的所有Function F和每个Function中的所有BasicBlock BB。
  for (auto &F : M)
    for (auto &BB : F) {
      #找到BB的第一个可插入点IP,然后创建一个IRBuilder对象IRB,用来在这个位置插入新的IR指令。
      BasicBlock::iterator IP = BB.getFirstInsertionPt();
      IRBuilder<> IRB(&(*IP));
      #通过生成0-100的随机数,来和inst_ratio进行比较决定该基本块是否要进行插桩
      if (AFL_R(100) >= inst_ratio) continue;
      /* Make up cur_loc */
      unsigned int cur_loc = AFL_R(MAP_SIZE);
      #这行代码的主要目的是在 LLVM IR 中创建一个表示当前位置(或基本块ID)的常量对象,以便后续的插桩代码可以使用。
      ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc);
      
      /* Load prev_loc */
      #创建一个新的 LLVM IR 的 Load 指令,加载之前存储的上一个位置的值。`AFLPrevLoc` 是一个全局变量,存储了上一个插桩块的位置(即代码路径)。`CreateLoad` 函数会生成一个加载指令,从指定的内存位置加载一个值。
      LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc);
      #给加载指令添加元数据,禁用某些编译器优化。"nosanitize" 是元数据的种类,它告诉编译器不要对这个加载指令应用任何可以改变其行为的优化。这是必要的,因为 AFL 需要能准确地跟踪代码的执行路径。
      PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
      #创建一个新的 LLVM IR 的 ZeroExtend 指令。ZeroExtend (ZExt) 是一种类型转换指令,它将一个小的整数类型扩展为一个更大的整数类型,高位用0填充。在这里,`PrevLoc`(上一位置值)被扩展为32位整数,用于后续的 XOR 操作。ZeroExtend 不会改变数的值,但会改变它的位宽,使得它可以和其他32位整数进行操作。
      Value *PrevLocCasted = IRB.CreateZExt(PrevLoc, IRB.getInt32Ty());
      
      /* Load SHM pointer */
      #创建一个新的 LLVM IR 的 Load 指令,用于加载 AFL 全局变量 `__afl_area_ptr` 的值,也就是 AFL 共享内存区域的起始地址。这个内存区域用于存储每个插桩代码块的执行计数。
      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 */
      #从上一步计算得到的地址 `MapPtrIdx` 中加载当前路径的执行计数。
      LoadInst *Counter = IRB.CreateLoad(MapPtrIdx);
      Counter->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
      #创建一个 LLVM 的加法指令,将加载出来的计数值加 1,之后再存回内存当中。
      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 */
      #创建了一个存储操作,将 `cur_loc >> 1` 的值(右移一位的当前路径位置)存储到全局变量 `__afl_prev_loc` 中。也就是说,这里将当前路径的位置保存下来,以备下一次使用。`cur_loc >> 1` 的操作是为了限制位置的范围在预定的大小以内。
      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. */
  #如果没有设置quiet模式,则输出插桩的信息。
  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;
}

注册 AFL 插桩的 Pass

registerAFLPass:创建一个新的 AFLCoverage 对象,并将其添加到传递的 Pass 管理器(PM)中。

RegisterAFLPass :这行代码创建了一个 RegisterStandardPasses 对象,它的构造函数接受两个参数,一个是枚举值 EP_ModuleOptimizerEarly,表示 Pass 的注册阶段;另一个是函数 registerAFLPass,这是一个回调函数,当到达相应的注册阶段时,Pass 管理器会调用这个函数,执行具体的注册工作。

RegisterAFLPass0:这表示即使在优化级别为 0 的情况下,也会执行这个 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);

你可能感兴趣的:(模糊测试,c语言,开发语言,模糊测试,llvm,覆盖率统计,源代码分析)