avatar

Catalog
AFL源码分析03:afl-as.h

前言

本篇是(普通)插桩的最后一部分,主要是分析afl-as.h中关于桩代码的实现过程,文中涉及到大量汇编,需要一定的汇编语言基础才能看懂。此外,由于本人不喜AT&T格式的汇编,因此在分析时,选择用gdb查看经过afl-gcc编译后的程序,并以Intel汇编格式的桩代码进行分析。

前置知识

系统调用/库函数

本篇的内容以汇编为主,同时也用到了一些系统调用或者库函数调用,这里对它们做简要介绍,完整的内容信息可以通过man 2 ***(系统调用)man 3 ***(库函数)进行查看:

c
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
/* 作用:将一个字符串转换为整数(这是一个库函数,其余均为系统调用)
返回值:返回转换后的值*/
int atoi(const char *nptr);

/* 作用:启动对shmid标记的共享内存的访问,并把共享内存连接到当前进程的地址空间
返回值:执行成功时返回一个指向共享内存第一个字节的指针;如果失败,返回-1 */
void *shmat(int shmid, const void *shmaddr, int shmflg);

/* 作用:从缓冲区buf中,读取count指定长度的字节,并将其写入到文件描述符fd所指定的文件中
返回值:如果执行成功,返回指定字节的数量;如果失败,则返回-1 */
ssize_t write(int fd, const void *buf, size_t count);

/* 作用:从文件描述符fd指定的文件中,读取指定大小字节count,到buf指定的缓冲区中
返回值:若读取成功,则返回读取字节数;如果失败,就返回-1 */
ssize_t read(int fd, void *buf, size_t count);

/* 作用:通过复制调用它的进程来创建一个新进程
返回值:如果是父进程,则返回值为子进程的PID;如果是子进程,则返回0 */
pid_t fork(void);

/* 作用:等待改变状态的进程(通常用于等待子进程结束)
返回值:如果执行成功,返回发生状态改变的子进程的PId */
pid_t waitpid(pid_t pid, int *wstatus, int options);

/* 作用:终止调用它的进程
返回值:该函数不返回 */
void _exit(int status);

/* 作用:关闭一个文件描述符,该文件描述符将不再指向任何文件
返回值:如果成功返回0;如果失败返回-1 */
int close(int fd);

afl-as.h源码分析

关键变量

Code
1
2
3
4
5
6
7
8
9
10
11
(gdb) x/20sw 0x4018
0x4018 <__afl_area_ptr>: U""
0x401c <__afl_area_ptr+4>: U""
0x4020 <__afl_prev_loc>: U""
0x4024 <__afl_prev_loc+4>: U""
0x4028 <__afl_fork_pid>: U""
0x402c <__afl_temp>: U""
0x4030 <__afl_setup_failure>: U""
0x4034 <__afl_setup_failure>: U""
0x4038 <__afl_global_area_ptr>: U""
0x403c <__afl_global_area_ptr+4>:U""
  • __afl_area_ptr:用于存储共享内存地址
  • __afl_prev_loc:上一个插桩的位置,由R(MAP_SIZE)生成的一个随机数
  • __afl_fork_pid:存储fork出来的子进程的pid
  • __afl_temp:临时缓冲区
  • __afl_setup_failure:标志位,若该值不为0,则退出程序
  • __afl_global_area_ptr:用于存储共享内存地址(全局变量

trampoline

trampoline的含义是“蹦床”,在插桩时,这就是需要被插入的桩代码,不过它本身并没有实现什么功能,主要作用是调用__afl_maybe_log函数,所以在这里可以把它理解为跳板,类似构造ROP链时用到的gadget。在afl-as.h文件中共定义了两种形式的trampoline,用于适配64/32位的运行环境,总体相差不大,这里我们介绍64位的trampoline_fmt_64:

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static const u8* trampoline_fmt_64 =
"\n"
"/* --- AFL TRAMPOLINE (64-BIT) --- */\n"
"\n"
".align 4\n"
"\n"
"leaq -(128+24)(%%rsp), %%rsp\n"
"movq %%rdx, 0(%%rsp)\n"
"movq %%rcx, 8(%%rsp)\n"
"movq %%rax, 16(%%rsp)\n"
"movq $0x%08x, %%rcx\n"
"call __afl_maybe_log\n"
"movq 16(%%rsp), %%rax\n"
"movq 8(%%rsp), %%rcx\n"
"movq 0(%%rsp), %%rdx\n"
"leaq (128+24)(%%rsp), %%rsp\n"
"\n"
"/* --- END --- */\n"
"\n";

以上是afl-as.h文件中,以AT&T格式存在的源码;我们可以用gdb打开经过afl-gcc编译后的文件,执行如下指令,就可以看到Intel格式的汇编了,这里在主函数开头,刚好有一段trampoline,我们可以据此分析:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) set disassembly-flavor intel 
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000001220 <+0>: lea rsp,[rsp-0x98]
0x0000000000001228 <+8>: mov QWORD PTR [rsp],rdx
0x000000000000122c <+12>: mov QWORD PTR [rsp+0x8],rcx
0x0000000000001231 <+17>: mov QWORD PTR [rsp+0x10],rax
0x0000000000001236 <+22>: mov rcx,0x89ef
0x000000000000123d <+29>: call 0x15b8 <__afl_maybe_log>
0x0000000000001242 <+34>: mov rax,QWORD PTR [rsp+0x10]
0x0000000000001247 <+39>: mov rcx,QWORD PTR [rsp+0x8]
0x000000000000124c <+44>: mov rdx,QWORD PTR [rsp]
0x0000000000001250 <+48>: lea rsp,[rsp+0x98]
0x0000000000001258 <+56>: endbr64

由于这部分汇编很简单,就不在代码中注释了,首尾是保存和恢复现场的操作。核心就只有两条指令:

  • mov rcx, 0x89ef:这里的 0x89af 实际上是一个随机数,用于定位执行到的代码块。参考上一篇add_instrumentation函数中的插桩语句

    c
    1
    2
    3
    4
    5
    #define R(x) (random() % (x))                         --> types.h

    /* 前文中出现的R(MAP_SIZE),就是用来计算出一个数值,表示插桩时所在的代码块,其中MAP_SIZE的大小为64k,
    计算后将得到一个范围在(0/0x0~64K/0xFFFF)之间的一个数。本例中的0x89ef正是某次计算出的结果 */
    fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, R(MAP_SIZE));
  • call 0x15b8 <__afl_maybe_log>:调用__afl_maybe_log函数

如下图所示,在经过 afl-gcc 插桩编译后,可以看到汇编中增加了表明代码块的 mov 语句和对 __afl_maybe_log() 调用,当执行到这段代码,fuzzer 知道这段代码被触发,从而统计每次输入样本的边缘覆盖率

__afl_maybe_log

该函数处理流程大致如下(图片来自初号机ScUpax0s

Code
1
2
3
4
5
6
7
8
(gdb) disassemble __afl_maybe_log 
Dump of assembler code for function __afl_maybe_log:
0x00000000000015b8 <+0>: lahf
0x00000000000015b9 <+1>: seto al
0x00000000000015bc <+4>: mov rdx,QWORD PTR [rip+0x2a55] # 0x4018 <__afl_area_ptr>
0x00000000000015c3 <+11>: test rdx,rdx
0x00000000000015c6 <+14>: je 0x15e8 <__afl_setup>
End of assembler dump.
  1. lahf:将EFLAGS的低八位送入AH,即将标志寄存器EFLAGS中的SF、ZF、AF、PF、CF五个标志位分别传送到累加器AH的对应位。完整的EFLAGS可以参考这里(注意:x64与x86Eflags寄存器的低32位相同,高32位保留,因此这里可直接拿来用)
  2. seto:溢出位置位
  3. 判断 __afl_area_ptr 是否为NULL:
    • 如果为NULL,则跳转到__afl_setup函数对__afl_area_ptr进行初始化;
    • 如果不为NULL,继续执行

__afl_setup

Code
1
2
3
4
5
6
7
8
9
10
11
(gdb) disassemble __afl_setup
Dump of assembler code for function __afl_setup:
0x00000000000015e8 <+0>: cmp BYTE PTR [rip+0x2a41],0x0 # 0x4030 <__afl_setup_failure>
0x00000000000015ef <+7>: jne 0x15e0 <__afl_return>
0x00000000000015f1 <+9>: lea rdx,[rip+0x2a40] # 0x4038 <__afl_global_area_ptr>
0x00000000000015f8 <+16>: mov rdx,QWORD PTR [rdx]
0x00000000000015fb <+19>: test rdx,rdx
0x00000000000015fe <+22>: je 0x1609 <__afl_setup_first>
0x0000000000001600 <+24>: mov QWORD PTR [rip+0x2a11],rdx # 0x4018 <__afl_area_ptr>
0x0000000000001607 <+31>: jmp 0x15c8 <__afl_store>
End of assembler dump.
  1. 判断__afl_setup_failure的值是否为0:

    • 如果为0,继续执行
    • 如果不为0,则跳转到__afl_return返回
  2. 检查__afl_global_area_ptr文件指针是否为NULL:

    • 如果为NULL,则跳转到__afl_setup_first进一步初始化

    • 如果不为NULL,就将_afl_global_area_ptr的值赋给 _afl_area_ptr,然后跳转到__afl_store

__afl_setup_first

Code
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
(gdb) disassemble __afl_setup_first 
Dump of assembler code for function __afl_setup_first:
0x0000000000001609 <+0>: lea rsp,[rsp-0x160]
0x0000000000001611 <+8>: mov QWORD PTR [rsp],rax
0x0000000000001615 <+12>: mov QWORD PTR [rsp+0x8],rcx
0x000000000000161a <+17>: mov QWORD PTR [rsp+0x10],rdi
0x000000000000161f <+22>: mov QWORD PTR [rsp+0x20],rsi
0x0000000000001624 <+27>: mov QWORD PTR [rsp+0x28],r8
0x0000000000001629 <+32>: mov QWORD PTR [rsp+0x30],r9
0x000000000000162e <+37>: mov QWORD PTR [rsp+0x38],r10
0x0000000000001633 <+42>: mov QWORD PTR [rsp+0x40],r11
0x0000000000001638 <+47>: movq QWORD PTR [rsp+0x60],xmm0
0x000000000000163e <+53>: movq QWORD PTR [rsp+0x70],xmm1
0x0000000000001644 <+59>: movq QWORD PTR [rsp+0x80],xmm2
0x000000000000164d <+68>: movq QWORD PTR [rsp+0x90],xmm3
0x0000000000001656 <+77>: movq QWORD PTR [rsp+0xa0],xmm4
0x000000000000165f <+86>: movq QWORD PTR [rsp+0xb0],xmm5
0x0000000000001668 <+95>: movq QWORD PTR [rsp+0xc0],xmm6
0x0000000000001671 <+104>: movq QWORD PTR [rsp+0xd0],xmm7
0x000000000000167a <+113>: movq QWORD PTR [rsp+0xe0],xmm8
0x0000000000001684 <+123>: movq QWORD PTR [rsp+0xf0],xmm9
0x000000000000168e <+133>: movq QWORD PTR [rsp+0x100],xmm10
0x0000000000001698 <+143>: movq QWORD PTR [rsp+0x110],xmm11
0x00000000000016a2 <+153>: movq QWORD PTR [rsp+0x120],xmm12
0x00000000000016ac <+163>: movq QWORD PTR [rsp+0x130],xmm13
0x00000000000016b6 <+173>: movq QWORD PTR [rsp+0x140],xmm14
0x00000000000016c0 <+183>: movq QWORD PTR [rsp+0x150],xmm15
0x00000000000016ca <+193>: push r12
0x00000000000016cc <+195>: mov r12,rsp
0x00000000000016cf <+198>: sub rsp,0x10
0x00000000000016d3 <+202>: and rsp,0xfffffffffffffff0
0x00000000000016d7 <+206>: lea rdi,[rip+0x2c1] # 0x199f <.AFL_SHM_ENV>
0x00000000000016de <+213>: call 0x1130
0x00000000000016e3 <+218>: test rax,rax
0x00000000000016e6 <+221>: je 0x18ce <__afl_setup_abort>
0x00000000000016ec <+227>: mov rdi,rax
0x00000000000016ef <+230>: call 0x1200
0x00000000000016f4 <+235>: xor rdx,rdx
0x00000000000016f7 <+238>: xor rsi,rsi
0x00000000000016fa <+241>: mov rdi,rax
0x00000000000016fd <+244>: call 0x11f0
0x0000000000001702 <+249>: cmp rax,0xffffffffffffffff
0x0000000000001706 <+253>: je 0x18ce <__afl_setup_abort>
0x000000000000170c <+259>: mov rdx,rax
0x000000000000170f <+262>: mov QWORD PTR [rip+0x2902],rax # 0x4018 <__afl_area_ptr>
0x0000000000001716 <+269>: lea rdx,[rip+0x291b] # 0x4038 <__afl_global_area_ptr>
0x000000000000171d <+276>: mov QWORD PTR [rdx],rax
0x0000000000001720 <+279>: mov rdx,rax
End of assembler dump.
  1. 在栈中保存部分64位通用寄存器以及128位xmm寄存器组
  2. 读取环境变量_AFL_SHM_ENV的值,来获取共享内存的shm_id:
    • 如果获取失败,就跳转到__afl_setup_abort
    • 如果获取成功:
      • 先调用atoi转换一下获取shm_id的值
      • 再调用shmat启用对共享内存的访问,并把共享内存连接到当前进程的地址空间。这里如果启用失败的话,也会跳转到__afl_setup_abort
  3. 最后,将shmat返回的共享内存地址保存在变量 afl_area_ptr 和 afl_global_area_ptr 中
  4. 后面,将开始运行__afl_forkserver

__afl_forkserver

Code
1
2
3
4
5
6
7
8
9
10
11
(gdb) disassemble __afl_forkserver 
Dump of assembler code for function __afl_forkserver:
0x0000000000001723 <+0>: push rdx
0x0000000000001724 <+1>: push rdx
0x0000000000001725 <+2>: mov rdx,0x4 # length
0x000000000000172c <+9>: lea rsi,[rip+0x28f9] # 0x402c <__afl_temp>
0x0000000000001733 <+16>: mov rdi,0xc7 # file desc
0x000000000000173a <+23>: call 0x1170
0x000000000000173f <+28>: cmp rax,0x4
0x0000000000001743 <+32>: jne 0x17e1 <__afl_fork_resume>
End of assembler dump.

通过代码中的注释,我们可以理解这部分汇编的含义就是,将缓冲区__afl_temp中的4个字节,写入到0xc7号描述符所指定的文件中。由于这是在gdb中显示的,所以我们看到的是已经编译后的汇编,这里的0xc7,在实际源码中表现为 FORKSRV_FD+1(198+1 -> 0xc7),它代表着一个状态管道。

因此,这里真正的含义是,向状态管道中写入__afl_temp中的4个字节,告诉afl-fuzz(后面会分析到),forkserver已经成功启动,等待下一步指示

__afl_fork_wait_loop

Code
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
(gdb) disassemble __afl_fork_wait_loop 
Dump of assembler code for function __afl_fork_wait_loop:
0x0000000000001749 <+0>: mov rdx,0x4
0x0000000000001750 <+7>: lea rsi,[rip+0x28d5] # 0x402c <__afl_temp>
0x0000000000001757 <+14>: mov rdi,0xc6
0x000000000000175e <+21>: call 0x11b0
0x0000000000001763 <+26>: cmp rax,0x4
0x0000000000001767 <+30>: jne 0x18c6 <__afl_die>
0x000000000000176d <+36>: call 0x1210
0x0000000000001772 <+41>: cmp rax,0x0
0x0000000000001776 <+45>: jl 0x18c6 <__afl_die>
0x000000000000177c <+51>: je 0x17e1 <__afl_fork_resume>
0x000000000000177e <+53>: mov DWORD PTR [rip+0x28a4],eax # 0x4028 <__afl_fork_pid>
0x0000000000001784 <+59>: mov rdx,0x4
0x000000000000178b <+66>: lea rsi,[rip+0x2896] # 0x4028 <__afl_fork_pid>
0x0000000000001792 <+73>: mov rdi,0xc7
0x0000000000001799 <+80>: call 0x1170
0x000000000000179e <+85>: mov rdx,0x0
0x00000000000017a5 <+92>: lea rsi,[rip+0x2880] # 0x402c <__afl_temp>
0x00000000000017ac <+99>: mov rdi,QWORD PTR [rip+0x2875] # 0x4028 <__afl_fork_pid>
0x00000000000017b3 <+106>: call 0x11e0
0x00000000000017b8 <+111>: cmp rax,0x0
0x00000000000017bc <+115>: jle 0x18c6 <__afl_die>
0x00000000000017c2 <+121>: mov rdx,0x4
0x00000000000017c9 <+128>: lea rsi,[rip+0x285c] # 0x402c <__afl_temp>
0x00000000000017d0 <+135>: mov rdi,0xc7
0x00000000000017d7 <+142>: call 0x1170
0x00000000000017dc <+147>: jmp 0x1749 <__afl_fork_wait_loop>
End of assembler dump.
  • 等待fuzzer通过控制管道(文件描述符为0xc6,即源码中FORKSRV_FD的值)发送来的命令,并读取4个字节到__afl_temp中 –> read(0xc6, __afl_temp, 0x4);

    • 如果读取失败,则跳转到__afl_die

    • 如果读取成功,说明afl-fuzz命令新建进程执行一次测试,则调用fork创建一个子进程:

      • 如果返回值小于0,说明创建失败,跳转到__afl_die
      • 如果返回值等于0,说明位于子进程,跳转到__afl_fork_resume中执行
      • 如果返回值大于0,说明位于父进程,父进程充当for server和afl-fuzz进行通信,并进行如下操作
        1. 将返回值,也就是子进程的pid,保存到变量__afl_fork_pid中
        2. 调用write将子进程的pid写入到状态管道(文件描述为0xc7)告知afl-fuzz –> write(0xc7, __afl_fork_pid, 0x4)
        3. 等待子进程结束,其中afl_temp将保存执行结果的状态信息 –> `waitpid(afl_for_pid, &__afl_temp, 0x0)`
          • 如果返回值为0,就跳转到__afl_die
        4. 调用write将__afl_temp中的值写入状态管道告知afl-fuzz –> write(0xc7, __afl_temp, 0x4)
        5. 跳转回__afl_fork_wait_loop的开头,开始执行下一轮,继续不断从控制管道读取命令

__afl_fork_resume

Code
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
(gdb) disassemble __afl_fork_resume 
Dump of assembler code for function __afl_fork_resume:
0x00000000000017e1 <+0>: mov rdi,0xc6
0x00000000000017e8 <+7>: call 0x11a0
0x00000000000017ed <+12>: mov rdi,0xc7
0x00000000000017f4 <+19>: call 0x11a0
0x00000000000017f9 <+24>: pop rdx
0x00000000000017fa <+25>: pop rdx
0x00000000000017fb <+26>: mov rsp,r12
0x00000000000017fe <+29>: pop r12
0x0000000000001800 <+31>: mov rax,QWORD PTR [rsp]
0x0000000000001804 <+35>: mov rcx,QWORD PTR [rsp+0x8]
0x0000000000001809 <+40>: mov rdi,QWORD PTR [rsp+0x10]
0x000000000000180e <+45>: mov rsi,QWORD PTR [rsp+0x20]
0x0000000000001813 <+50>: mov r8,QWORD PTR [rsp+0x28]
0x0000000000001818 <+55>: mov r9,QWORD PTR [rsp+0x30]
0x000000000000181d <+60>: mov r10,QWORD PTR [rsp+0x38]
0x0000000000001822 <+65>: mov r11,QWORD PTR [rsp+0x40]
0x0000000000001827 <+70>: movq xmm0,QWORD PTR [rsp+0x60]
0x000000000000182d <+76>: movq xmm1,QWORD PTR [rsp+0x70]
0x0000000000001833 <+82>: movq xmm2,QWORD PTR [rsp+0x80]
0x000000000000183c <+91>: movq xmm3,QWORD PTR [rsp+0x90]
0x0000000000001845 <+100>: movq xmm4,QWORD PTR [rsp+0xa0]
0x000000000000184e <+109>: movq xmm5,QWORD PTR [rsp+0xb0]
0x0000000000001857 <+118>: movq xmm6,QWORD PTR [rsp+0xc0]
0x0000000000001860 <+127>: movq xmm7,QWORD PTR [rsp+0xd0]
0x0000000000001869 <+136>: movq xmm8,QWORD PTR [rsp+0xe0]
0x0000000000001873 <+146>: movq xmm9,QWORD PTR [rsp+0xf0]
0x000000000000187d <+156>: movq xmm10,QWORD PTR [rsp+0x100]
0x0000000000001887 <+166>: movq xmm11,QWORD PTR [rsp+0x110]
0x0000000000001891 <+176>: movq xmm12,QWORD PTR [rsp+0x120]
0x000000000000189b <+186>: movq xmm13,QWORD PTR [rsp+0x130]
0x00000000000018a5 <+196>: movq xmm14,QWORD PTR [rsp+0x140]
0x00000000000018af <+206>: movq xmm15,QWORD PTR [rsp+0x150]
0x00000000000018b9 <+216>: lea rsp,[rsp+0x160]
0x00000000000018c1 <+224>: jmp 0x15c8 <__afl_store>
End of assembler dump.
  1. 调用close关闭子进程中的文件描述符(状态管道和控制管道)
  2. 恢复子进程的寄存器状态
  3. 跳转到__afl_store开始执行

__afl_store

Code
1
2
3
4
5
6
7
(gdb) disassemble __afl_store 
Dump of assembler code for function __afl_store:
0x00000000000015c8 <+0>: xor rcx,QWORD PTR [rip+0x2a51] # 0x4020 <__afl_prev_loc>
0x00000000000015cf <+7>: xor QWORD PTR [rip+0x2a4a],rcx # 0x4020 <__afl_prev_loc>
0x00000000000015d6 <+14>: shr QWORD PTR [rip+0x2a43],1 # 0x4020 <__afl_prev_loc>
0x00000000000015dd <+21>: inc BYTE PTR [rdx+rcx*1]
End of assembler dump.
  1. 计算并存储代码命中位置,__afl_prev_loc为前一次跳转位置,rcx为当前跳转位置,rdx为共享内存的地址
  2. 令__afl_prev_loc获取到rcx的值,即当前跳转位置,然后右移一位
  3. 在共享内存中,在新的存储路径的位置(将__afl_prev_loc与rcx异或后的结果作为下标)自增1

这部分直接通过汇编来解释,不是很好理解,拖进IDA,查看伪代码

先来看几个关键的变量,首先是a4,这里__afl_maybe_log虽然有4个参数,原因是IDA识别的问题,IDA误将保存现场的操作当成了参数传递。因此在整个执行流程中,这里的a1,a2,a3都没有用到,真正的参数只有这里的a4。回顾前文trampoline中的汇编指令,可以得知,这里的a4,就是那通过 R(MAP_SIZE) 生成的一个随机数,这个随机数的作用就是定位当前插桩的位置。另一个关键变量v6,这个通过对前面__afl_setup__afl_setup_first的分析,可以得出,这个v6就是共享内存的地址。根据这些信息,我们对这部分伪代码做个优化(这里摘取了sakurahicookie的分析):

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 
AFL是根据二元tuple(跳转的源位置,目标位置)来记录分支信息,从而获取目标程序的执行流程和代码覆盖情况
1. 为了简化连接复杂对象的过程和保存xor输出平均分布,当前位置是随机产生的。用sakura的话来说,AFL为每个代码块生成一个
随机数,作为该代码块“位置”的记录。
2. share_mem[]数组是一个调用者传给被插桩程序的64KB大小的共享内存区域,数组的元素是Byte。数组中的每个元 素都会被编码,
在这里就是将“源位置”和“目标位置”的两个随机数进行异或运算,生成一个key,保存每个分支的执行次数,实际上是一个哈希表。
这个数组的大小要应该能存2K~10K个分支节点,这样既可以减少冲突(当然还是会存在碰撞的问题,但对于不是很复杂的目标,碰
撞概率还是可以接受的),也可以实现毫秒级别的分析。这种形式的覆盖率,相对于简单的基本块覆盖率来说,对程序运行路径提供
了一个更好的描述。以下面两个路径产生的tulpes为例:
A -> B -> C -> D -> E (tuples: AB, BC, CD, DE)
A -> B -> D -> C -> E (tuples: AB, BD, DC, CE)
这更有助于发现代码的漏洞,因为大多数安全漏洞经常是一些没有预料到的状态转移,而不是因为没有覆盖那一块代码
3. 最后一行右移操作是用来保持tuples的定向性,会将cur_location右移1位后,再保存到prev_location中。如果没有右移操作,
假设target中存在A->A和B->B这样两个跳转,那么这两个分支对应的异或后的key都是0,从而无法区分。对于B->A和A->B的情况
也是一样
*/

cur_location = a4;
shared_mem = v6;
v7 = cur_location ^ prev_location;
shared_mem[v7]++;
prev_location = (prev_location ^ v7) >> 1
= cur_location >> 1

__afl_die

Code
1
2
3
4
5
(gdb) disassemble __afl_die 
Dump of assembler code for function __afl_die:
0x00000000000018c6 <+0>: xor rax,rax
0x00000000000018c9 <+3>: call 0x1150 <_exit@plt>
End of assembler dump.
  1. 设置参数status的值为0
  2. 调用_exit终止程序

__afl_setup_abort

Code
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
(gdb) disassemble __afl_setup_abort 
Dump of assembler code for function __afl_setup_abort:
0x00000000000018ce <+0>: inc BYTE PTR [rip+0x275c] # 0x4030 <__afl_setup_failure>
0x00000000000018d4 <+6>: mov rsp,r12
0x00000000000018d7 <+9>: pop r12
0x00000000000018d9 <+11>: mov rax,QWORD PTR [rsp]
0x00000000000018dd <+15>: mov rcx,QWORD PTR [rsp+0x8]
0x00000000000018e2 <+20>: mov rdi,QWORD PTR [rsp+0x10]
0x00000000000018e7 <+25>: mov rsi,QWORD PTR [rsp+0x20]
0x00000000000018ec <+30>: mov r8,QWORD PTR [rsp+0x28]
0x00000000000018f1 <+35>: mov r9,QWORD PTR [rsp+0x30]
0x00000000000018f6 <+40>: mov r10,QWORD PTR [rsp+0x38]
0x00000000000018fb <+45>: mov r11,QWORD PTR [rsp+0x40]
0x0000000000001900 <+50>: movq xmm0,QWORD PTR [rsp+0x60]
0x0000000000001906 <+56>: movq xmm1,QWORD PTR [rsp+0x70]
0x000000000000190c <+62>: movq xmm2,QWORD PTR [rsp+0x80]
0x0000000000001915 <+71>: movq xmm3,QWORD PTR [rsp+0x90]
0x000000000000191e <+80>: movq xmm4,QWORD PTR [rsp+0xa0]
0x0000000000001927 <+89>: movq xmm5,QWORD PTR [rsp+0xb0]
0x0000000000001930 <+98>: movq xmm6,QWORD PTR [rsp+0xc0]
0x0000000000001939 <+107>: movq xmm7,QWORD PTR [rsp+0xd0]
0x0000000000001942 <+116>: movq xmm8,QWORD PTR [rsp+0xe0]
0x000000000000194c <+126>: movq xmm9,QWORD PTR [rsp+0xf0]
0x0000000000001956 <+136>: movq xmm10,QWORD PTR [rsp+0x100]
0x0000000000001960 <+146>: movq xmm11,QWORD PTR [rsp+0x110]
0x000000000000196a <+156>: movq xmm12,QWORD PTR [rsp+0x120]
0x0000000000001974 <+166>: movq xmm13,QWORD PTR [rsp+0x130]
0x000000000000197e <+176>: movq xmm14,QWORD PTR [rsp+0x140]
0x0000000000001988 <+186>: movq xmm15,QWORD PTR [rsp+0x150]
0x0000000000001992 <+196>: lea rsp,[rsp+0x160]
0x000000000000199a <+204>: jmp 0x15e0 <__afl_return>
End of assembler dump.
  1. 将变量__afl_setup_failure的值置1
  2. 恢复寄存器的状态
  3. 调用__afl_return

__afl_return

Code
1
2
3
4
5
6
7
(gdb) disassemble __afl_return 
Dump of assembler code for function __afl_return:
0x00000000000015e0 <+0>: add al,0x7f
0x00000000000015e2 <+2>: sahf
0x00000000000015e3 <+3>: ret
0x00000000000015e4 <+4>: nop DWORD PTR [rax+0x0]
End of assembler dump.
  1. 调整一下寄存器AL的值,此操作不影响AH的值
  2. 将AH的内容送给标志寄存器的低8位,也就是恢复EFLAGS低8位的值
  3. 执行ret指令返回

伪代码

将经过插桩编译后的程序,扔进IDA,找到函数__afl_maybe_log并查看伪代码,便可以更加清晰的理解整个桩代码的执行流程:

参考资料

  1. skr:sakuraのAFL源码全注释
  2. Seebug:AFL 二三事——源码分析
  3. AFL内部实现细节小记
  4. AFL:afl-as.h
  5. HICOOKIE:AFL-Learning
  6. 简书:AFL源码分析
  7. EFLAGS寄存器图解
  8. 初号机/ScUpax0s:AFL源码阅读笔记之gcc与fuzz部分
  9. CSDN:x86汇编基础
  10. Jussi Judin:afl-fuzz on different file systems
Author: cataLoc
Link: http://cata1oc.github.io/2022/01/07/AFL%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%9003/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶