FuzzBuilder_src_understand

更新中…

启动docker命令:sudo docker exec -it c_ares_FB /bin/bash

读取文件

这里比较关键的是构建target。在parse_conf里面,经过了一系列错误判断,最终落入本函数中。

image-20220404221412100

这里最后有一个target全局变量。打开make_target可以看到其实是把用来描述target的文件作为json文件读取,然后放在一个vector中。放入的格式是(name,fuzz,len)。第一个参数是函数名,第二个是buffer在第几个参数的位置,第三个是buffer的长度。

exec部分

image-20220411210304438

这里代表了参数是exec时,fuzzbuilder会做的事情。main中调用了Execgen::generate,其代码如下。

image-20220404213740712

首先,函数调用load(),把之前json格式的数据加载进来。

load file

image-20220404213905137

这里引入了一个新的属性,this->modules。这是一个记录了所有file和Module*格式的文件的一个vector。这里的get_files()和Module*类型的文件留到后面分析。只需要知道这里把文件和对应的模块都放到一个vector中即可。

循环 in generate

image-20220404214624297

接着的循环,首先读取module_size也就是刚才load进来的module vector的大小。接着get_module返回每一个vector的第二个参数,也就是push进来的module类型变量。这是一个IRREADER类型的数据。我们看看这个类型是什么。

IRReader

image-20220404214655591

重点看到这里的构造函数。不过打开一看,也就是简单赋值。

image-20220404214744913

后面我们用到了其中的两个函数,一个是get_functions_to_fuzz(),另一个是get_functions_to_remove。现在提前来看看。

  • get_functions_to_fuzz

首先是获得要被fuzz的函数。期间还做了一些检查,但是没看明白。里面有一个test变量,不知道是什么意思。返回了一个ret用来储存所有的要被fuzz的函数。

接下来有一个is_target的判断。如果是就把该函数push到tmps向量中。这里的ret是上面get_test_functions的返回值。

image-20220404220244038

这里的is_target很有意思。大概作用是先获得所有target函数名。(关于target定义写在读取文件部分)之后获取参数函数的所有调用的函数,然后一旦找到调用函数name和target相同就返回true,表示该函数是一个有效的target。为什么要这样做? 为了看看当前要被测试函数的调用的函数中,是否有我们需要的target。回顾一下,targets是target function,而这里的形参f是外面的ret,也就是test_func。

image-20220404220234521

这里两者的不同需要澄清。看到他给的示例文件。所谓target就是目标要fuzz的函数,test所指示的可能是所有可能的能够找到上述调用target函数的集合开头(不必写全,因为可以看到后面是字符串比较)。因此上面这段代码的用途就清楚了:在下面这张图中test开头的所有函数里,找调用target的函数如果找到不包含target的test_开头的函数,就把函数名称返回出来。(这样做看似很奇怪)

image-20220404222120110

但是看到这里就清楚要干啥了。

image-20220404223137589

==这里有一个没分析的get_callees==,比较复杂后面再看。

同样的,下面一半是获得skips的函数。这里就比较清晰。 获取skip的函数,并从ret中删掉这些函数。获得去除不调用target函数以及在skip集合中的函数之后的函数集合ret

image-20220404223637633

回到外部

(接着上面的 循环 部分)

接着再循环里面看到第二个get_functions_to_remove函数。

image-20220404224230026

这个操作感觉很傻,大致意思是找到所有在_test开头文件中并且没有调用target的。这里都用的是vector的比对操作。

因此,两个操作,为了确保找到fuzz的函数的调用函数,并且确保这些函数是”test”规则开头的函数,并且不在skips列表里面

在一顿report之后,到了这里

image-20220404230505772

insert interface

重点看以下两个函数。

image-20220404230640661

get_entry_function

image-20220404230732939

用到了之前提到的module。每一个module是一个<file,module>的二元组。可以看到这里是用了module的getFunction方法。

image-20220404235446410

这里又牵涉到module的概念。

image-20220405093248536

我觉得可以理解为每一个ELF文件的文件头部信息?

回到上面的get_function。这里的意思就是从之前的module结构体中找到包含main的module,然后这个作为entry function返回。接着初始化一个IRwriter类型变量,将之前得到的main函数指针赋值到这里。

image-20220405093718623

这里又牵涉到另一个类,IRwriter。后面用到的时候再分析。

==writer.interface==

这个函数及其复杂,==留到后面分析==,位于irwriter.cc中。大致看一眼发现用到了很多神奇的操作

image-20220405100104966

比如这里的getContext, get_global_buffer等。看起来像是为main函数设置好调用上下文。

回到外部

接下来是这三个函数

image-20220405093926414

insert_fuzz_to_tests

image-20220405094034331

这里的srcs是之前的target,也就是所有要fuzz的函数总和。可以看到这里为每个函数初始化了一个IRwriter类型的变量,然后进入fuzz函数。于是接着看writer.fuzz()是什么意思。

writer.fuzz

重点分析下面这些函数。(好多啊)

image-20220405100550970

get_target_instructions

接受一个函数名作为参数

image-20220405100730365

首先调用getFunction。这个没找到定义,是Module* m类型变量的成员函数。

image-20220405101126231

可以看到是先获得module里面是不是有参数对应的函数名,如果找到,进入下面get_calls

后面是get_calls

image-20220405101652398

接受一个函数指针作为参数,从该函数的basicblock中,找到所有的instruction。在github上找到了相关说明。后面这句in的作用就是找到所有指令中包含invoke或者call的部分。然后插入到ret中。

之后is_target判断call的是不是目标函数。如果是就插入到集合里面返回。

is_modified

这里接受一个之前获得的instruction集合

image-20220405102525507

就是看MODIFIED里面有没有i这一条命令。有就返回true。这个MIDIFIED还是在writer.fuzz中找到了初始化。后面再分析。第一次调用的时候应该是空。也就是返回false。于是会直接进入get_callee

get_callee

image-20220405103108404

这个函数结构还是相对清晰。主要作用是找到call或者invoke调起的函数,然后返回出来。

这个操作之后返回一个callee变量(回顾一下,也就是把terget函数getparent返回的的所有函数中的所有call和Invoke的目标函数拿出来)现在callee里储存的就是所有的目标函数。

get_fuzz

image-20220405104400160

get_fuzz()接受目标函数函数名作为参数。在targets里面找到所有名字一样的函数,调用get_fuzz()。我觉得writer.fuzz到目前为止的意思就是从target函数的caller中找到所有调用target函数的语句,然后现在把这些target放到get_fuzz中。但是这里e->get_fuzz很奇怪,只能返回一个size_t类型的变量,将作为fuzz_slot返回到外部。==不知道fuzz是什么意思,或许是从参数中获取的能fuzz的参数位置吗==。现在觉得应该是的

image-20220405104541553

image-20220405104931527

image-20220405104945420

接下来有一个len_slot。初步猜测就是获得能fuzz的长度。

IRbuilder

image-20220405111315535

首先看到构造函数IRBuilder。在网上找到资料这个意思大概是创建一个IIVM的代码块。记住这里的e是所有调用target函数的instruction

==get_global_buffer==

image-20220405112536493

第一次进来的时候肯定是empty。进入get_unique_global_variable_name中。

image-20220405113139919

可以看到这个函数从module的第二个参数(也就是module类型变量)中尝试获取name(这里看了是fuzzbuilder_buffer_1,fuzzbuilder_buffer_2这样名字的变量),如果找到了就返回。(但是感觉应该返回空?)

于是这里gv应该返回为空。

image-20220405113740789

在网上查到getOrInsertglobal作用。第二个参数type是llvm特有的i8*类型,但是网上也没有说的很详细。==后面有一个setLinkage不太理解作用==。可能是把当前找到的变量和某个外部变量联系起来?

image-20220405113951467

==get_global_size==

这个和上面函数长得很像。但是也是一样的问题,不太理解作用是啥。

image-20220405115300973

回到外部(修改调用参数)

image-20220405130336259

这里set_argument定义如下

image-20220405130641025

可以看出这里就是关键的地方!这里把我们之前定位到的call或者invoke位置的函数设置了参数。其中idx代表参数的位置(也就是用户之前填写的),v是参数值,在这里就相当于是上面get_global_buffer返回的内容。相当于直接patch掉了原先invoke或者call的函数参数,变为我们之前创造的global buffer对象。这里就和论文的描述一致了。

这里一共patch了两个变量,分别是buffer和buffer的长度。之后把这一条语句放在modified里面

image-20220405131111523

然后打出一个log表示这个调用语句已经被patch过了。

返回值处理

接下来一部分用于处理返回值。(终于快结束了)

image-20220405131408200

首先判断,如果返回值是void就直接过。

接着创建了一个IRFreader。这个的代码量很短

image-20220405131603822

下一行就有用到get_asserts()。我们看看其含义。大概就是获得所有调用了abort和assert_fail的instruction。接着回来

image-20220405131907581

看到这里eraseFromParent应该是从调用函数这里删掉所有assert语句(个人猜测这应该是为了去除debug文件特有的assert函数来提高成功率)接着关于i的操作(这个getNextNode网上也查不到啊),大概是下一个control block?

接着判断如果i的下一个control block是可以不可达到的就删掉(我觉得大概是为了判断先把e(也就是assert语句)删掉了,确保把e删掉之后原来assert后面代码块是不可达的,这样就把他删掉)

至此,终于分析完了writer.fuzz。也就完成了对于insert_fuzz_to_test的分析。

insert_skip_to_tests

skip的处理相对简单

image-20220405133447257

主要是writer.skip

writer.skip

下面这张图一开始的地方写错了,应该是,一旦不是32为int或者void就返回false

image-20220405134318569

可以看到能够skip的函数都是void返回类型或者Int32的才能处理。能够skip的原因是通过新建一个要么返回0要么返回void的函数来代替。

dump

最后一个dump

image-20220405134731210

image-20220405134741183

就是把上面所做的修改放回到.mod.bc文件中

芜湖!终于分析完了一半!

exec的总结

finish on April4.5 13:49

总结一下看了将近15小时的exec部分!大概就是根据要fuzz的函数,先从目标文件中找到所有调用目标函数的,再从中去掉skip的函数。之后硬改调用目标函数的语句,把buffer和长度都改为一个全局变量(这个是我没看懂的部分,后面再尝试理解)去掉assert函数以及把所有返回值为void或者int32的能skip的函数全部patch为空,返回void以及0。然后把这个文件dump出来。

最后可以看一下用户指定的文件的真实面目如何。其实正是包含了刚才所说的大部分变量内容(target是目标muzz函数,包括了buffer位置和长度,tests是一个函数名前缀,表示test_开头的全部函数都需要被找到,files表示要找的.bc文件,skips表示需要跳过的函数)。

image-20220405135451329

seed部分

首先看看seed是什么。seed就是一种样例输入。在灰盒测试里面seed能带来更高的代码覆盖率。这里的seed部分关注如何自动生成一些有用的seed

image-20220411215418472

seed部分主要在seedgen.cc里面。seed部分主要调用generate函数来生成seed。有以下函数需要关注。

image-20220411210015213

get_target_functions

获取目标函数。

image-20220411212347913

这里比较的f和target分别是什么呢? f是我们新加载进来的模块。而target,就是我们在上面花了很久找到的寻找target函数的逻辑。可以看到上面的逻辑是在target中逐个找module里面的函数,如果找到了就放到数组中。(为什么要找?大概是为了看新的文件里面有哪些是我们要fuzz的?)

image-20220411213423792

后面两句就是输出target func。然后到最关键的最后一步:insert_collect_to_targets()

insert_collect_to_targets

image-20220411213613519

这里传入的参数就是target。这是一个很可怕的函数。。。

writer.collect()

注意到下面所操作的f都是初始化的时候带入的f。

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
void IRWriter::collect() {

// function大小为0直接返回
if(this->f->size() == 0) {
return;
}

BasicBlock& entry1 = this->f->getEntryBlock();
// getFirstInsertionPt():
// Returns an iterator to the first instruction in this block that is suitable for inserting a non-PHI instruction
Instruction& inst = *(entry1.getFirstInsertionPt());
BasicBlock* link = entry1.splitBasicBlock(&inst);
entry1.begin()->eraseFromParent();
IRBuilder<> builder(&entry1);
LLVMContext& ctx = this->f->getContext();
Module& module = *(this->f->getParent());

BasicBlock* entry2 = BasicBlock::Create(ctx, "", this->f);

// 创建buffer。第一个是buf的name,这个是之前设置的全局变量
Value* buffer = this->f->arg_begin() +
(Config::get()->get_fuzz(string(this->f->getName())) - 1);
// 下面创建size
Value* size = nullptr;
// size是用config::get->getsize()得到的。如果不是0,设置为原本函数相关内容。如果是0,首先在function中创建一个call(?)把buffer设置为之前初始化的buffer。
if(Config::get()->get_size(string(this->f->getName())) != 0) {
size = this->f->arg_begin() +
(Config::get()->get_size(string(this->f->getName())) - 1 );
} else {
size = builder.CreateCall(get_strlen_function(module),
{ buffer });
}

Value* fd = builder.CreateAlloca(Type::getInt32Ty(module.getContext()));
Value* path = builder.CreateGlobalString(COLLECT_PATH);
Value* func_name = builder.CreateGlobalString(string(this->f->getName()));
Value* func_name_size = builder.getInt32(string(this->f->getName()).size());
Value* splitter = builder.CreateGlobalString(SPLITTER);
Value* splitter_size = builder.getInt32(SPLITTER.size());
Value* newline = builder.CreateGlobalString("\n");

Value* cmp = builder.CreateICmpUGT(size, builder.getInt32(1));
builder.CreateCondBr(cmp, entry2, link);

builder.SetInsertPoint(entry2);

Function* func = get_open_function(module);

Value* call = builder.CreateCall(func,
{ builder.CreateInBoundsGEP(path, {builder.getInt32(0), builder.getInt32(0)}),
builder.getInt32(1089), builder.getInt32(420) });
Value* call1 = builder.CreateCall(get_flock_function(module),
{ call, builder.getInt32(2) });
Value* call2 = builder.CreateCall(get_write_function(module),
{ call, builder.CreateInBoundsGEP(func_name, {builder.getInt32(0), builder.getInt32(0)}),
func_name_size });
Value* call3 = builder.CreateCall(get_write_function(module),
{ call, builder.CreateInBoundsGEP(newline, {builder.getInt32(0), builder.getInt32(0)}),
builder.getInt32(1) });
Value* call4 = builder.CreateCall(get_write_function(module),
{ call, buffer, size });
Value* call5 = builder.CreateCall(get_write_function(module),
{ call, builder.CreateInBoundsGEP(newline, {builder.getInt32(0), builder.getInt32(0)}), builder.getInt32(1) });
Value* call6 = builder.CreateCall(get_write_function(module),
{ call, builder.CreateInBoundsGEP(splitter, {builder.getInt32(0), builder.getInt32(0)}), splitter_size });
Value* call7 = builder.CreateCall(get_flock_function(module),
{ call, builder.getInt32(8)} );
Value* call8 = builder.CreateCall(get_close_function(module),
{ call });
builder.CreateBr(link);

Logger::get()->log(INFO, "Collect Instrumented at " + string(this->f->getName()));
}

用到很多次的create_call。参数为函数类型,自己调用的函数,参数列表,twine是一种用字符串表示的临时变量。最后一个mdnode实在没看明白。

image-20220412174958624

运行结果

以下是运行了seed之后的结果。

image-20220411220955272

文章目录
  1. 1. 读取文件
  2. 2. exec部分
    1. 2.1. load file
    2. 2.2. 循环 in generate
      1. 2.2.1. IRReader
    3. 2.3. 回到外部
      1. 2.3.1. insert interface
        1. 2.3.1.1. get_entry_function
        2. 2.3.1.2. ==writer.interface==
    4. 2.4. 回到外部
      1. 2.4.1. insert_fuzz_to_tests
        1. 2.4.1.1. writer.fuzz
          1. 2.4.1.1.1. get_target_instructions
        2. 2.4.1.2. is_modified
        3. 2.4.1.3. get_callee
        4. 2.4.1.4. get_fuzz
        5. 2.4.1.5. IRbuilder
          1. 2.4.1.5.1. ==get_global_buffer==
          2. 2.4.1.5.2. ==get_global_size==
        6. 2.4.1.6. 回到外部(修改调用参数)
        7. 2.4.1.7. 返回值处理
      2. 2.4.2. insert_skip_to_tests
        1. 2.4.2.1. writer.skip
      3. 2.4.3. dump
    5. 2.5. exec的总结
  3. 3. seed部分
    1. 3.1. get_target_functions
    2. 3.2. insert_collect_to_targets
      1. 3.2.1. writer.collect()
    3. 3.3. 运行结果
|