首页
论坛
课程
招聘
[Linux] [Fuzz] [原创]AFL之llvm mode部分源码分析
2021-2-14 15:11 4058

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

2021-2-14 15:11
4058

新年快乐!

 

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

 

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

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

afl-clang-fast.c源码阅读

本文件作为clang的wrapper

main

  • 首先查找运行时libraries的位置。
  • 然后编辑参数
  • 使用execvp(cc_params[0], (char**)cc_params)执行clang

    find_obj(argv[0])

    找运行时lib。
  • 首先获取环境变量"AFL_PATH"为afl_path。
    • 如果存在,生成路径tmp:tmp = alloc_printf("%s/afl-llvm-rt.o", afl_path)
    • 判断是否有读取权限。
      • 若有,令obj_path为afl_path
      • return。
  • 获取argv[0]的最后一个 /的位置为slash。dir为argv[0]
    • 尝试读取目录:alloc_printf("%s/afl-llvm-rt.o", dir)
      • 若成功,则令obj_path = dir
  • 否则尝试查找AFL_PATH宏指定的目录下是否有afl-llvm-rt.o
    • 如果找到了赋值:obj_path = AFL_PATH,return。

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

edit_params(argc, argv)

本函数编辑参数数组。

  • 首先判断如果我们用的是afl-clang-fast++
    • 设置cc_params[0]为环境变量"AFL_CXX",如果环境变量为空,设置为"clang++"
  • 否则设置cc_params[0]为环境变量"AFL_CC",如果环境变量为空,设置为"clang"

 

接下来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


  • 如果我们使用方式2(#ifdef USE_TRACE_PC)
    • #define __ANDROID__依次向参数数组中添加:
      • "-fsanitize-coverage=trace-pc-guard
      • "-mllvm"(ANDROID)
      • "-sanitizer-coverage-block-threshold=0"(ANDROID)
    • 否则添加:
      • "-fsanitize-coverage=trace-pc-guard
      • "-Xclang"
      • -load"
      • "-Xclang"
  • 再补一个"-Qunused-arguments"
  • 接下来扫描参数数组,设置对应的标志位

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      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;
  • 如果环境变量设置了"AFL_HARDEN"

    • 添加"-fstack-protector-all"
    • 如没有设置"FORTIFY_SOURCE"
      • 添加:"-D_FORTIFY_SOURCE=2"
    • 如果没有设置"-fsanitize=memory"
      • 首先尝试获取"AFL_USE_ASAN"
        • 若获取成功,满足互斥关系后添加:"-U_FORTIFY_SOURCE""-fsanitize=address"
        • 否则尝试获取"AFL_USE_MSAN"满足互斥关系后添加:"-U_FORTIFY_SOURCE""-fsanitize=memory"
  • 如果是使用的方式2进行插桩。判断是否设置"AFL_INST_RATIO",若设置了则abort

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

1
2
3
4
5
-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); })
1
2
3
4
5
6
-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)
  • 如果x_set被设置了
    • 添加参数:-x none
  • 如果非ANDROID
    • 根据不同的bit_mode来设置对应的afl-llvm-rt,并检查是否可读
      • 32位:obj_path/afl-llvm-rt-32.o
      • 64位:obj_path/afl-llvm-rt-64.o
      • 如果没有特别设置:obj_path/afl-llvm-rt.o

afl-llvm-pass.so.cc源码阅读

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace {
 
  class AFLCoverage : public ModulePass {
 
    public:
 
      static char ID;
      AFLCoverage() : ModulePass(ID) { }
 
      bool runOnModule(Module &M) override;
 
  };
 
}
  • 在AFLCoverage::runOnModule中进行如下操作。获取线程上下文。https://stackoverflow.com/questions/13184835/what-is-llvm-context

    1
    2
    3
    4
    LLVMContext &C = M.getContext();
     
    IntegerType *Int8Ty  = IntegerType::getInt8Ty(C);
    IntegerType *Int32Ty = IntegerType::getInt32Ty(C);
  • 如果stderr为终端。且未设置"AFL_QUIET"模式。输出对应的信息。
    否则设置be_quiet = 1。

    1
    2
    3
    4
    5
    6
    7
    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;
  • 设置插桩密度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* 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)");
     
    }
  • 获取指向共享内存块shm的指针。
    1
    2
    3
    GlobalVariable *AFLMapPtr =
        new GlobalVariable(M, PointerType::get(Int8Ty, 0), false,
                           GlobalValue::ExternalLinkage, 0, "__afl_area_ptr");
  • 获取前一个桩的位置(随机数编号)

    1
    2
    3
    GlobalVariable *AFLPrevLoc = new GlobalVariable(
        M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc",
        0, GlobalVariable::GeneralDynamicTLSModel, 0, false);
  • 接下来进入插桩过程,扫描basic block:

    1
    2
    BasicBlock::iterator IP = BB.getFirstInsertionPt();
        IRBuilder<> IRB(&(*IP));
  • 随机插桩(如果大于插桩密度)
    1
    if (AFL_R(100) >= inst_ratio) continue;
  • 生成当前block的随机编号

    1
    2
    3
    4
    5
    /* Make up cur_loc */
     
    unsigned int cur_loc = AFL_R(MAP_SIZE);
     
    ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc)
  • 加载上一个block的编号。
    1
    2
    3
    LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc);
        PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
        Value *PrevLocCasted = IRB.CreateZExt(PrevLoc, IRB.getInt32Ty());
  • 首先获取共享内存块的地址,然后找到对应当前桩的计数位置

    1
    2
    3
    4
    5
    6
    /* 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))
  • 该地址上对应的桩计数器加一
    1
    2
    3
    4
    5
    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));
  • 设置对应的AFLPrevLoccur_loc >> 1,关于为什么要右移1,主要是为了做路径区分,可以看上一篇。

    1
    2
    3
    4
    5
    /* 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));
  • 最后插桩计数加一:inst_blocks++
  • 开始扫描下一个block
  • 最后根据设置是否为quiet模式等。
    • 如果inst_blocks为0,说明没有插桩。
    • 否则输出各种信息。
      1
      2
      3
      4
      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);

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

afl-llvm-rt.o.c源码阅读

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

deferred instrumentation

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

 

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

 

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

 

只需将:

1
2
3
#ifdef __AFL_HAVE_MANUAL_CONTROL
  __AFL_INIT();
#endif

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

1
2
3
4
5
6
7
8
9
10
11
12
13
void __afl_manual_init(void) {
 
  static u8 init_done;
 
  if (!init_done) {
 
    __afl_map_shm();
    __afl_start_forkserver();
    init_done = 1;
 
  }
 
}
  • 没有初始化,那么首先调用__afl_map_shm()设置共享内存。
  • 然后调用__afl_start_forkserver()起forkserver。
  • 最后设置为已经初始化。
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
/* 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
 
  }
 
}

接下来是__afl_start_forkserver()

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