这篇记录 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 */// 打印bannerchar 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中的每个BasicBlockBasicBlock::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);   // 随机生成当前块的IDConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc);/* Load prev_loc */// 加载前驱块的IDLoadInst *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,代表边的执行次数加1LoadInst *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了

AFL(American Fuzzy Lop)源码详细解读(8)相关推荐

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

    AFL(American Fuzzy Lop)源码详细解读(1) 多亏大佬们的文章,对读源码帮助很大: https://eternalsakura13.com/2020/08/23/afl/ http ...

  2. AFL(American Fuzzy Lop)源码详细解读(3)

    AFL(American Fuzzy Lop)源码详细解读(3) 本篇是关于主循环阶段的内容,整个AFL最核心的部分,篇幅较长.最后简述一下afl_fuzz整体流程. 多亏大佬们的文章,对读源码帮助很 ...

  3. AFL(American Fuzzy Lop)源码详细解读(2)

    AFL(American Fuzzy Lop)源码详细解读(2) 本篇是关于 dry run (空跑.演练) 阶段的内容,一直到主循环之前. 多亏大佬们的文章,对读源码帮助很大: https://et ...

  4. AFL(American Fuzzy Lop)源码详细解读(5)

    感谢大佬们的文章: https://bbs.pediy.com/thread-265973.htm https://eternalsakura13.com/2020/08/23/afl/#more 这 ...

  5. AFL(American Fuzzy Lop)-afl-fuzz.c

    转载AFL(American Fuzzy Lop)源码详细解读(1) AFL(American Fuzzy Lop)源码详细解读(1) 多亏大佬们的文章,对读源码帮助很大: https://etern ...

  6. AFL(american fuzzy lop)学习一

    AFL(american fuzzy lop)学习一 @sizaf AFL 的模糊方法 基于改进的边缘覆盖 插桩法引导的遗传算法 流程: 插桩 从源码编译程序时进行插桩,以记录代码覆盖率(Code C ...

  7. AFL(american fuzzy lop)学习二

    AFL(american fuzzy lop)学习二 @sizaif @2022-04-10 设计思想 覆盖率计算 改进边缘覆盖: 向目标程序注入以下工具来捕获分支(边缘)覆盖率和分支命中计数 一条边 ...

  8. AFL——American Fuzzy Lop的基础使用

    AFL--American Fuzzy Lop的基础使用 因为某些奇怪的原因,我一个没搞过pwn的得来搞代码fuzz,只好学一下,顺便记一下. AFL的安装 在部分源有的情况下可以直接使用apt-ge ...

  9. MTCNN源码详细解读(1)- PNet/RNet/ONet的网络结构和损失函数

    代码地址 https://github.com/AITTSMD/MTCNN-Tensorflow 这里我就不在进行MTCNN的介绍了.分析的再清楚都不如从源码的实现去分析. Talk is cheap ...

最新文章

  1. Handler研究2-AsyncTask,AsyncQueryHandler分析
  2. 201571030335/201571030320《小学四则运算练习软件软件需求说明》结对项目报告
  3. PAT (Basic Level) 1050 螺旋矩阵(模拟)
  4. 仿QQ联系人的TableView的折叠与拉伸
  5. C++描述杭电OJ 2012. 素数判定 ||
  6. 使用Spring 3.2的DeferredResult进行长轮询
  7. 条款11 在operator=中处理“自我赋值”
  8. jsp获得文件的绝对路径
  9. 中国宽带最新速率状况报告 你家达标了吗?
  10. java最小子串覆盖_LeetCode 76. 最小覆盖子串
  11. python适合自学编程吗-风变编程:Python适合编程初学者学习吗?
  12. Voip中的音频Codec技术
  13. Fiddler抓包 - 系统找不到相应的文件FSE.exe,未能找到路径CustomRules.js的一部分,未能加载程序或程序集 “fiddle“ 或它的依赖项
  14. 我们分析了5万多场英雄联盟比赛,教你如何轻松用Python预测胜负
  15. iphone邮件服务器 263,IPHONE中设置使用企业邮箱(以263为例).doc
  16. 为什么人工智能难以达到儿童语言水平?
  17. 《指定一个用户只能在特定的时间里不能登陆》『罗斌原创』
  18. 对AWS的计费有点糊涂
  19. mysql怎么设置每天定时清表_Mysql每天定时清空表
  20. 【区块链与密码学】第9-3讲:群签名方案的安全性要求

热门文章

  1. 安信可 BL602 平台模组 OTA 升级教程
  2. 使用Python开发游戏可以吗?
  3. NXP KL03--8. KL03 ISP升级失败,jlink_jflash 与keil 的erase flash数值不一致,主要是 0x40c 0x40d区别
  4. 浅析构建SQL-to-SQL的翻译器
  5. 云技术销售需要掌握哪些知识
  6. 百度知识营销是什么,知识营销平台介绍
  7. python扫雷_【Python】扫雷小游戏(PyQt5)
  8. 谁说 JavaScript 简单的? – 码农网 http://www.codeceo.com/article/who-said-javascript-was-easy.html
  9. java类对象实验问题_Java类与对象实验答案
  10. 中国十大知名调查研究咨询机构公司瞭望