afl-as粗析

afl-as.c粗析

查看main函数,关键的就一句:

1
if (!just_version) add_instrumentation();

即在汇编文件中添加插桩代码。

该函数中关键部分:

1
2
3
4
5
6
7
8
9
10
if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&
instrument_next && line[0] == '\t' && isalpha(line[1])) {

fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
R(MAP_SIZE));

instrument_next = 0;
ins_lines++;

}

即通过汇编判断当前是不是一个分支语句或者函数,然后根据环境是x86还是x86-64添加汇编代码trampoline_fmt_32抑或是trampoline_fmt_64

这里分析trampoline_fmt_32trampoline_fmt_32定义在afl-as.h中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static const u8* trampoline_fmt_32 =

"\n"
"/* --- AFL TRAMPOLINE (32-BIT) --- */\n"
"\n"
".align 4\n"
"\n"
"leal -16(%%esp), %%esp\n"
"movl %%edi, 0(%%esp)\n"
"movl %%edx, 4(%%esp)\n"
"movl %%ecx, 8(%%esp)\n"
"movl %%eax, 12(%%esp)\n"
"movl $0x%08x, %%ecx\n"
"call __afl_maybe_log\n"
"movl 12(%%esp), %%eax\n"
"movl 8(%%esp), %%ecx\n"
"movl 4(%%esp), %%edx\n"
"movl 0(%%esp), %%edi\n"
"leal 16(%%esp), %%esp\n"
"\n"
"/* --- END --- */\n"
"\n";

内容大致就是首先保存上下文环境,将ecx的值设置为fprintf()所要打印的变量内容,然后调用__afl_maybe_log函数,调用完毕恢复上下文环境。

__afl_maybe_log首先检查__afl_area_ptr是否为0,是0则跳转至__afl_setup

1
2
3
movl  __afl_area_ptr, %edx
testl %edx, %edx
je __afl_setup

__afl_setup

1
2
3
movl __afl_area_ptr, %edx
testl %edx, %edx
je __afl_setup

__afl_area_ptr存储的内容为共享内存(当前tuple),若共享内存未设置就跳转至__afl_setup进行共享内存的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmpb $0, __afl_setup_failure
jne __afl_return

pushl %eax
pushl %ecx

pushl $.AFL_SHM_ENV
call getenv
addl $4, %esp

testl %eax, %eax
je __afl_setup_abort

pushl %eax
call atoi
addl $4, %esp

进行一些检查,然后获取.AFL_SHM_ENV,然后使用atoi转化为int型,即为SHM ID,共享内存的标识符。

然后使用shmat设置共享内存:

1
2
3
4
5
pushl $0  
pushl $0
pushl %eax
call shmat
addl $12, %esp

最后将共享内存地址保存在__afl_area_ptr

1
2
3
4
5
movl %eax, __afl_area_ptr
movl %eax, %edx

popl %ecx
popl %eax

__afl_setup结束就会进入__afl_forkserver部分。

__afl_forkserver

这部分的内容与lcamtuf的文章《Fuzzing random programs without execve()》大致差不多,作用就是为了避免过早的调用execve()函数。

译文:

https://ivoripuion.github.io/2021/03/13/Fuzzing%20random%20programs%20without%20execve%E8%AF%91%E6%96%87/#more

这部分执行完成会进入__afl_store

1
2
3
4
popl %edx
popl %ecx
popl %eax
jmp __afl_store

该部分用于保存分支信息。

__afl_store

这里__afl_prev_loc保存的是前一个运行到的位置,ecx保存的是当前运行到的位置,edx保存的是共享内存的地址:

1
2
3
4
5
6
movl __afl_prev_loc, %edi
xorl %ecx, %edi
shrl $1, %ecx
movl %ecx, __afl_prev_loc

incb (%edx, %edi, 1)

首先把前一个运行到的位置保存到edi中,然后与ecx进行抑或操作,将ecx右移1位,把ecx存储到__afl_prev_loc中,最后将edx[edi]加1。

trampoline_fmt_32定义中,标识处了format参数为ecx:

1
"movl $0x%08x, %%ecx\n"

结合输出函数:

1
2
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
R(MAP_SIZE));

可知:ecx内容即为R(MAP_SIZE),是一个(0-2^16)的随机数。

所以上述的AT&T汇编伪代码对应的就是afl白皮书里关于记录分支信息的伪代码:

1
2
3
cur_location = <COMPILE_TIME_RANDOM>;//当前位置信息,一个随机数
shared_mem[cur_location ^ prev_location]++;//当前信息和之前的信息抑或存放到共享内存中,计数加1
prev_location = cur_location >> 1;//前一个位置信息

即在每个插桩的地方,afl-as设置一个随机数标识当前分支信息,然后将前一个随机数和当前随机数抑或后存放到共享内存中,计数器加1累加分支语句执行的次数。

参考文章http://rk700.github.io/2017/12/28/afl-internals/