Fuzz源码理解

关于 AFL 与 AFLNet 中对代码功能的一些理解,未完。

AFL

run_target函数功能

首先将tarce_bits置0,用于记录本次运行的命中的路径及其数量,然后进行pipe设置执行程序,在之后对tarce_bits进行规整,搜集信息,判断返回状态并返回。

top_rated[]结构体数组,top_rated[i]是一个结构体,包含了关于第 i 个测试用例的覆盖率和评分情况。

这里的i表示的是每一条路径,top_rated[i]的意思是对于当前这条路径 i,top_rated[i]位置指向的测试用例是最优的一个


判断条件的理解:

MAP_SIZE大小为 65535 .表示路径数。MAP_SIZE>>3 表示按8个路径为一组进行划分,每 1 bit代表一条路径,所以需要MAP_SIZE>>3个byte来存储这些路径,每 1 byte存储8条路径。

(i&7) 相当于是 i对 7 取模,相当于 i 除以 8 的余数,写成位来看就比较好理解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
假设i从0到8
i = 0 :
000 i
& 111 7
------------
000 0

i = 1 :
001 i
& 111 7
------------
00 1
···
i = 8 :
1000 i
& 0111 7
------------
0000 0

因为 MAP_SIZE>>3后,每 1 bit来表示一条路径,所以在对每个字节的检查中,需要检查的是各个位的位置,范围是0到7之间。

1<<(i&7)也就是,1分别左移0,1,2,3,···,7位,同样,换成位来看:

1
2
3
4
5
6
i = 0: 1<<0 00000001 
i = 1: 1<<1 00000010
i = 2: 1<<2 00000100
i = 3: 1<<3 00001000
···
i = 7: 1<<7 10000000

这样就是依次检查该字节的各个位(bit)的情况

calibrate_case函数中调用到的关键函数和主要流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
calibrate_case(){
init_forkserver();
for(){
write_to_testcase();//写入.cur_input

run_target();

判断hash得到的cksum{
has_new_bits();
}
}
update_bitmap_score();
}

run_target函数关键流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
run_target(){

memset(trace_bits, 0, MAP_SIZE);

对pipe进行设置;

通知forkserver执行程序,j或调用execvp执行程序;

classify_counts();对tarce_bits进行规整

判断程序执行结果,返回对应的错误值;

}

common_fuzz_stuff函数中调用的关键函数和主要流程:

1
2
3
4
5
6
7
8
9
common_fuzz_stuff(){

write_to_testcase(out_buf, len);

fault = run_target(argv, exec_tmout);

save_if_interesting(out_buf, len, fault);

}

save_if_interesting函数中调用的关键函数和主要流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
save_if_interesting(){
if(!has_new_bits){
return
}

add_to_queue();

fault = calibrate_case();

保存到本地文件;

switch(fault){
}
}

下面是次一级重要的函数:

trim_case函数:

1

疑问

1.关于forkserver插桩的代码部分:在第一次执行桩代码时的,__afl_fork_wait_loop这个函数内,会不断执行fork?还是只执行一次,这个循环是什么?
对于每一个测试用例,或重新fork一次?还是重新execvp一次?
答:对于每一个测试用例会重新fork一次,生成一个新的子进程,如果未设置forkserver则会重新execvp()

2.forkserver,父子进程进行通信,通信的内容是什么?
写入管道内的__afl_temp 4字节大小的信息是什么?

3.如何给进程传递testcase中的输入?
答:在试运行过程中,通过read函数读取文件内容到use_mem变量中,然后作为参数传递给calibrate_case函数
追问:在calibrate_case函数内,通过write_to_testcase函数将文件内容use_mem写入到out_file(.cur_input)文件,但是fork得到的进程如何获取输入,在源码哪里体现出来?

4.resume模式,多次提到的resuming_fuzz变量

5.跟踪字节引用计数,tc_ref变量的作用

  1. extras的作用

7.timeout_given,超时时间是谁的超时时间?
答:target程序运行的超时时间

8.cur_depth,max_depth,和q->depth,这些变量里的depth指的是什么?如何理解?

9.路径数,是不是应该叫测试用例数更符合,这里的path理解成路径的话,这个路径是指的什么路径?

10:q->handicap这个变量的作用?

几个模块:反馈收集、种子筛选、种子变异、种子调度。

AFLNET

源码理解

AFLNET主要在AFL的基础上实现了:

  1. socket 通信发送测试用例
  2. 一套与代码覆盖率并行的状态机引导机制
  3. 增加了消息序列级别的变异策略

klib库

khint_t

在 klib 库中,khint_t 是一个无符号整数类型,用于表示哈希表中的键、值和哈希函数的返回值等。

khint_t 的实现可能会因不同的编译器和平台而异,通常会被定义为 size_tunsigned int。在 klib 库中,khint_t 通常用于表示哈希表中键值对的索引或哈希值,它是哈希表中的关键类型之一。

例如,在使用 klib 库中的哈希表时,可以使用 khint_t 类型来表示键、值和哈希函数的返回值。在向哈希表中添加、删除、查找或更新键值对时,需要使用 khint_t 类型来指定相应的操作。khint_t 可以保证足够大,以便在大多数哈希表中存储索引或哈希值。

kh_get()

kh_get 是 klib 库中哈希表操作的一个函数之一,用于获取指定键的值。

该函数的原型为:

1
khint_t kh_get(kh_t, khkey_t key);

其中,kh_t 表示哈希表的类型,khkey_t 表示键的类型,khint_t 表示哈希表索引的类型。该函数的返回值为哈希表中与指定键相关联的索引,如果指定键不存在,则返回哈希表的结束位置。

kh_get 函数的作用是从哈希表中查找指定键的值,并返回该键对应的索引。在哈希表中,每个键都会被哈希成一个索引,而该索引通常用于查找存储在哈希表中的值。kh_get 函数通过查找指定键的索引来获取该键对应的值。

kh_end()

kh_end 是 klib 库中哈希表操作的一个函数之一,用于获取哈希表的结束位置。

该函数的原型为:

1
khint_t kh_end(kh_t);

其中,kh_t 表示哈希表的类型,khint_t 表示哈希表索引的类型。该函数的返回值为哈希表的结束位置,即哈希表中最后一个键值对的后一个位置。

kh_end 函数的作用是获取哈希表中最后一个键值对的后一个位置。在 klib 库中,哈希表中的键值对是按照哈希值存储的,而哈希表的大小通常是事先确定的。因此,可以通过 kh_end 函数来获取哈希表的结束位置,并遍历哈希表中的所有键值对。

kh_val()

kh_val 是 klib 库中哈希表操作的一个函数之一,用于获取与指定索引相关联的值。

该函数的原型为:

1
khval_t kh_val(kh_t, khint_t);

其中,kh_t 表示哈希表的类型,khint_t 表示哈希表索引的类型,khval_t 表示值的类型。该函数的返回值为与指定索引相关联的值。

kh_val 函数的作用是获取与指定索引相关联的值。在哈希表中,每个键都会被哈希成一个索引,而该索引通常用于查找存储在哈希表中的值。kh_val 函数通过指定索引来获取该索引对应的值。

疑问

Target State Selector 目标状态选择器,什么是state?初始状态从哪里获取?

下一个状态?如何选择下一个状态?如何根据hash表存储状态并索引

send_over_network函数中最后HANDLE_REPONSES:为什么要 while(1)if {has_new_bits==2} break;

种子的region是什么?

答:先看一下add_to_queue函数时,写入到文件保存下来的内容:

在save_regions_to_file函数中,每一个region格式为:Region %d - Start: %d, End: %d\n

我们再来看下生成的文件的内容:

image-20230706204811073

可以看到,rtsp_requests_aac.raw对应的regions的数量是3,即有三个region,

第一个region从0开始,在157(0x9D处结束)。

我们看一下rtsp_requests_aac.raw内容:

image-20230706205049246

红色竖线刚好是0x9D的位置,我们可以看到,region的起止范围正好是第一条请求的全部内容

所以,regions就是该种子中请求的数量,一个region对应一个请求

was_fuzzed_map二维数组是如何标记states和qentries的

qentries又指的是states里的什么?

这里的states的seed指的是什么,kl_message