————
一、LLVM模式插桩概述
AFL的 llvm_mode
可以实现编译器级别的插桩,可以替代 afl-gcc
或 afl-clang
使用的比较“粗暴”的汇编级别的重写的方法
优点:
- 编译器可以进行很多优化以提升效率
- 可以实现CPU无关,可以在非 x86 架构上进行fuzz
- 可以更好地处理多线程目标
AFL的 llvm_mode
文件夹下包含3个文件:
afl-llvm-rt.o.c
文件主要是重写了afl-as.h
文件中的main_payload
部分,方便调用afl-llvm-pass.so.cc
文件主要是当通过afl-clang-fast
调用 clang 时,这个pass被插入到 LLVM 中,告诉编译器添加与afl-as.h
中大致等效的代码afl-clang-fast.c
文件本质上是 clang 的 wrapper,最终调用的还是 clang 。但是与afl-gcc
一样,会进行一些参数处理。
llvm_mode
的插桩思路就是通过编写pass来实现信息记录,对每个基本块都插入探针,具体代码在 afl-llvm-pass.so.cc
文件中,初始化和forkserver操作通过链接完成
Clang 编译器,从源码至机器码的流程如下:
代码首先由编译器前端clang处理后得到中间代码IR,然后经过各 LLVM Pass 进行优化和转换,最终交给编译器后端生成机器码,其中LLVM Pass 可以在中间过程处理 IR 、用户自定义的内容,可以用来遍历、修改 IR 以达到插桩、优化、静态分析等目的。
AFL进入llvm_mode进行编译afl-clang-fast
(注意:llvm和clang版本要一致)
一个坑:最开始使用的llvm和clang版本是14版本,但编译会报错;改为11版本可以正常编译
# root @ zzz in ~/Desktop/AFL/llvm_mode on git:master o [14:18:42] C:2 |
二、afl-clang-fast
afl-clang-fast实际上是CC/CXX的wrapper。它定义了一些宏,设置了一些参数,最终调用真正的编译器(CC指代C语言编译器,CXX指代C++编译器)
/* Main entry point */ |
三、afl-llvm-pass
afl-llvm-pass.so.cc
实现了 LLVM-mode 下的一个插桩 LLVM Pass
llvm_mode是怎么插桩的呢?答:IRBuilder
- afl-llvm-pass中,只有一个pass : AFLCoverage 该pass会在每一个基础块的第一个可插入指令处插桩,检测 控制流的覆盖程度
class AFLCoverage : public ModulePass {
runOnModule
函数首先找出当前线程上下文中所对应的IntegerType,并且打印bannerbool AFLCoverage::runOnModule(Module &M) {
LLVMContext &C = M.getContext();
IntegerType *Int8Ty = IntegerType::getInt8Ty(C);
IntegerType *Int32Ty = IntegerType::getInt32Ty(C);
/* Show a 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 */
// 获取代码的插桩率(0-100)
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. */
// 指向 用于输出控制流覆盖次数的共享内存 的指针
GlobalVariable *AFLMapPtr =
new GlobalVariable(M, PointerType::get(Int8Ty, 0), false,
GlobalValue::ExternalLinkage, 0, "__afl_area_ptr");
// AFLPrevLoc 用来表示前一个基本块的编号
GlobalVariable *AFLPrevLoc = new GlobalVariable(
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc",
0, GlobalVariable::GeneralDynamicTLSModel, 0, false); - 获取上述这些信息后,for循环开始遍历所有基本块,进行插桩:
- 首先寻找BB中适合插入桩代码的位置,然后通过初始化
IRBuilder
实例执行插入:/* Instrument all the things! */
int inst_blocks = 0;
for (auto &F : M)
for (auto &BB : F) {
// 在每一个基础块前都插入代码。先查找插入点
BasicBlock::iterator IP = BB.getFirstInsertionPt();
IRBuilder<> IRB(&(*IP));
// 根据代码插桩率,随机插桩
if (AFL_R(100) >= inst_ratio) continue; - 插入
load
指令,获取当前基础块与上一个基础块的编号// 随机获取当前的基础块编号
unsigned int cur_loc = AFL_R(MAP_SIZE);
ConstantInt *CurLoc = ConstantInt::get(Int32Ty, cur_loc);
/* Load prev_loc */
// 加载上一个基础块的编号
LoadInst *PrevLoc = IRB.CreateLoad(AFLPrevLoc);
// Metadata在这里可以看作是一种调试信息
PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
Value *PrevLocCasted = IRB.CreateZExt(PrevLoc, IRB.getInt32Ty()); - 插入
load
指令,获取共享内存地址;并通过上述两个编号,调用CreateGEP
函数获取共享内存中指定index的地址/* Load SHM pointer */
// 获取指向共享内存的指针
LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr);
MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
// GEP: GetElementPtr
// 根据当前基础块与上一个基础块的编号,计算指向特定地址的指针
Value *MapPtrIdx =
IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc)); - 插入
load
指令,获取对应index地址的值;同时插入add
指令,使该地址上的计数器递增;再插入store
指令写入新值,并更新共享内存/* Update bitmap */
// 该指针上的counter值自增一
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)); - 右移
cur_loc
,插入store
指令,设置__afl_prev_loc
,作为下一个插桩基础块的 “上一个基础块编号”之所以要将当前基础块编号右移一位,是因为当基础块跳转/* Set prev_loc to cur_loc >> 1 */
// 将当前基础块的编号右移1位后,存入AFLPrevLoc
StoreInst *Store =
IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
inst_blocks++;A->A
和B->B
,或A->B
和B->A
,它们的编号做异或后的结果是相同的,无法区分,所以其中一个编号要右移一位。 - 当前基础块插桩完成,开始遍历下一个基础块
- 首先寻找BB中适合插入桩代码的位置,然后通过初始化
- 当插桩完成后,输出相关信息并返回
/* 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插桩主要完成以下几点:
- 随机计算出当前基础块的编号
- 通过当前基础块编号与上一个基础块编号,计算出共享内存中对应的索引值idx 这块共享内存的实质就是一个
hashtable
- __afl_area_ptr[idx]++
- 设置
__afl_prev_loc
为当前的基础块编号,当前基础块插桩结束,准备插桩下一个基础块
- 作用:
- 当有控制流到达当前基础块时,其共享内存对应位置,用于计数的值就会加一
- 而AFL可以根据该共享内存上的数据来判断控制流的覆盖程度,调整输入样本,使控制流能够覆盖更多的基础块
- 缺点:
- 编号存在碰撞。不过根据AFL文档中的介绍,对于不是很复杂的目标,碰撞概率还是可以接受的
Branch cnt | Colliding tuples | Example targets
------------+------------------+-----------------
1,000 | 0.75% | giflib, lzo
2,000 | 1.5% | zlib, tar, xz
5,000 | 3.5% | libpng, libwebp
10,000 | 7% | libxml
20,000 | 14% | sqlite
50,000 | 30% | -
- 编号存在碰撞。不过根据AFL文档中的介绍,对于不是很复杂的目标,碰撞概率还是可以接受的
- 该pass插桩主要完成以下几点:
- 示例 设置
export AFL_PATH=/root/Desktop/AFL
,使其可以找到afl-llvm-rt.o
和afl-llvm-pass.so
使用afl-clang-fast
,对源码进行插桩,得到插桩后的IR指令其中,源码test.c:root@zzZ:/home/zzz/Desktop/AFL# ./afl-clang-fast -S -emit-llvm test.c
afl-clang-fast 2.57b by <lszekeres@google.com>
afl-llvm-pass 2.57b by <lszekeres@google.com>
[+] Instrumented 5 locations (non-hardened mode, ratio 100%).
root@zzZ:/home/zzz/Desktop/AFL插桩后的IR指令://test.c
int main(int argc, char *argv[]) {
char buffer[3] = {0};
int i;
int *null = 0;
read(0, buffer, 3);
if (buffer[0] == '6' && buffer[1] == '2' && buffer[2] == 'a') {
i = *null;
}
puts("No problem");
}可以看到插入了的IR代码:; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
@.str = private unnamed_addr constant [11 x i8] c"No problem\00", align 1
@__afl_area_ptr = external global i8*
@__afl_prev_loc = external thread_local global i32
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main(i32 %0, i8** %1) #0 !dbg !7 {
%3 = load i32, i32* @__afl_prev_loc, align 4, !nosanitize !2
%4 = load i8*, i8** @__afl_area_ptr, align 8, !nosanitize !2
%5 = xor i32 %3, 3216
%6 = getelementptr i8, i8* %4, i32 %5
%7 = load i8, i8* %6, align 1, !nosanitize !2
%8 = add i8 %7, 1
store i8 %8, i8* %6, align 1, !nosanitize !2
store i32 1608, i32* @__afl_prev_loc, align 4, !nosanitize !2
%9 = alloca i32, align 4
%10 = alloca i32, align 4
%11 = alloca i8**, align 8
%12 = alloca [3 x i8], align 1
%13 = alloca i32, align 4
%14 = alloca i32*, align 8
store i32 0, i32* %9, align 4
store i32 %0, i32* %10, align 4
call void @llvm.dbg.declare(metadata i32* %10, metadata !14, metadata !DIExpression()), !dbg !15
store i8** %1, i8*** %11, align 8
call void @llvm.dbg.declare(metadata i8*** %11, metadata !16, metadata !DIExpression()), !dbg !17
call void @llvm.dbg.declare(metadata [3 x i8]* %12, metadata !18, metadata !DIExpression()), !dbg !22
%15 = bitcast [3 x i8]* %12 to i8*, !dbg !22
call void @llvm.memset.p0i8.i64(i8* align 1 %15, i8 0, i64 3, i1 false), !dbg !22
call void @llvm.dbg.declare(metadata i32* %13, metadata !23, metadata !DIExpression()), !dbg !24
call void @llvm.dbg.declare(metadata i32** %14, metadata !25, metadata !DIExpression()), !dbg !27
store i32* null, i32** %14, align 8, !dbg !27
%16 = getelementptr inbounds [3 x i8], [3 x i8]* %12, i64 0, i64 0, !dbg !28
%17 = call i64 @read(i32 0, i8* %16, i64 3), !dbg !29
%18 = getelementptr inbounds [3 x i8], [3 x i8]* %12, i64 0, i64 0, !dbg !30
%19 = load i8, i8* %18, align 1, !dbg !30
%20 = sext i8 %19 to i32, !dbg !30
%21 = icmp eq i32 %20, 54, !dbg !32
br i1 %21, label %22, label %53, !dbg !33
22: ; preds = %2
%23 = load i32, i32* @__afl_prev_loc, align 4, !dbg !34, !nosanitize !2
%24 = load i8*, i8** @__afl_area_ptr, align 8, !dbg !34, !nosanitize !2
%25 = xor i32 %23, 40101, !dbg !34
%26 = getelementptr i8, i8* %24, i32 %25, !dbg !34
%27 = load i8, i8* %26, align 1, !dbg !34, !nosanitize !2
%28 = add i8 %27, 1, !dbg !34
store i8 %28, i8* %26, align 1, !dbg !34, !nosanitize !2
store i32 20050, i32* @__afl_prev_loc, align 4, !dbg !34, !nosanitize !2
%29 = getelementptr inbounds [3 x i8], [3 x i8]* %12, i64 0, i64 1, !dbg !34
%30 = load i8, i8* %29, align 1, !dbg !34
%31 = sext i8 %30 to i32, !dbg !34
%32 = icmp eq i32 %31, 50, !dbg !35
br i1 %32, label %33, label %53, !dbg !36
33: ; preds = %22
%34 = load i32, i32* @__afl_prev_loc, align 4, !dbg !37, !nosanitize !2
%35 = load i8*, i8** @__afl_area_ptr, align 8, !dbg !37, !nosanitize !2
%36 = xor i32 %34, 39935, !dbg !37
%37 = getelementptr i8, i8* %35, i32 %36, !dbg !37
%38 = load i8, i8* %37, align 1, !dbg !37, !nosanitize !2
%39 = add i8 %38, 1, !dbg !37
store i8 %39, i8* %37, align 1, !dbg !37, !nosanitize !2
store i32 19967, i32* @__afl_prev_loc, align 4, !dbg !37, !nosanitize !2
%40 = getelementptr inbounds [3 x i8], [3 x i8]* %12, i64 0, i64 2, !dbg !37
%41 = load i8, i8* %40, align 1, !dbg !37
%42 = sext i8 %41 to i32, !dbg !37
%43 = icmp eq i32 %42, 97, !dbg !38
br i1 %43, label %44, label %53, !dbg !39
44: ; preds = %33
%45 = load i32, i32* @__afl_prev_loc, align 4, !dbg !40, !nosanitize !2
%46 = load i8*, i8** @__afl_area_ptr, align 8, !dbg !40, !nosanitize !2
%47 = xor i32 %45, 25417, !dbg !40
%48 = getelementptr i8, i8* %46, i32 %47, !dbg !40
%49 = load i8, i8* %48, align 1, !dbg !40, !nosanitize !2
%50 = add i8 %49, 1, !dbg !40
store i8 %50, i8* %48, align 1, !dbg !40, !nosanitize !2
store i32 12708, i32* @__afl_prev_loc, align 4, !dbg !40, !nosanitize !2
%51 = load i32*, i32** %14, align 8, !dbg !40
%52 = load i32, i32* %51, align 4, !dbg !42
store i32 %52, i32* %13, align 4, !dbg !43
br label %53, !dbg !44
53: ; preds = %44, %33, %22, %2
%54 = load i32, i32* @__afl_prev_loc, align 4, !dbg !45, !nosanitize !2
%55 = load i8*, i8** @__afl_area_ptr, align 8, !dbg !45, !nosanitize !2
%56 = xor i32 %54, 59797, !dbg !45
%57 = getelementptr i8, i8* %55, i32 %56, !dbg !45
%58 = load i8, i8* %57, align 1, !dbg !45, !nosanitize !2
%59 = add i8 %58, 1, !dbg !45
store i8 %59, i8* %57, align 1, !dbg !45, !nosanitize !2
store i32 29898, i32* @__afl_prev_loc, align 4, !dbg !45, !nosanitize !2
%60 = call i32 @puts(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @.str, i64 0, i64 0)), !dbg !45
%61 = load i32, i32* %9, align 4, !dbg !46
ret i32 %61, !dbg !46
}
; Function Attrs: nounwind readnone speculatable willreturn
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
; Function Attrs: argmemonly nounwind willreturn writeonly
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1 immarg) #2
declare dso_local i64 @read(i32, i8*, i64) #3
declare dso_local i32 @puts(i8*) #3
attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { nounwind readnone speculatable willreturn }
attributes #2 = { argmemonly nounwind willreturn writeonly }
attributes #3 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!3, !4, !5}
!llvm.ident = !{!6}
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "Ubuntu clang version 11.1.0-6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "test.c", directory: "/root/Desktop/AFL")
!2 = !{}
!3 = !{i32 7, !"Dwarf Version", i32 4}
!4 = !{i32 2, !"Debug Info Version", i32 3}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{!"Ubuntu clang version 11.1.0-6"}
/// other informations其中,%1 = load i32* @__afl_prev_loc, align 4, !nosanitize !25
%2 = load i8** @__afl_area_ptr, align 8, !nosanitize !25
%3 = xor i32 %1, 47106
%4 = sext i32 %3 to i64
%5 = getelementptr i8* %2, i64 %4
%6 = load i8* %5, align 1, !nosanitize !25
%7 = add i8 %6, 1
store i8 %7, i8* %5, align 1, !nosanitize !25
store i32 23553, i32* @__afl_prev_loc, align 4, !nosanitize !2547106
是当前基本块的随机编号(key),23553
是47106 >> 1
的结果