AFL源码分析计划3 -- afl-as.h

这个头文件里面放着的是汇编器编译汇编到二进制时插入的汇编代码。

这里只分析64位的

trampoline_fmt_64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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";

首先在栈上开辟一段空间,然后将rdx,rcx,rax这三个寄存器的值保存到栈上,将rcx的值赋值为一个随机数,这个随机数是插入这段汇编的时候动态传进来的。然后调用_afl_maybe_log,调用完之后,把栈上保存的值恢复回去,再恢复栈。

main_payload_64

__afl_maybe_log

1
2
3
4
/*lahf
seto %al
这两条指令大概就是将标志寄存器FLAGS,溢出进位保存到AH上面
*/
1
2
3
4
5
6
"  /* Check if SHM region is already mapped.(检查共享内存是否已经加载,
如果加载了的话,__afl_area_ptr保存了共享内存的指针,否则就是NULL) */\n"
"\n"
" movq __afl_area_ptr(%rip), %rdx\n"
" testq %rdx, %rdx\n"
" je __afl_setup\n"

__afl_store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"\n"
" /* Calculate and store hit for the code location specified in rcx.
(计算并储存代码命中位置,当前代码的位置在寄存器rcx中) */\n"
"\n"
#ifndef COVERAGE_ONLY/*假如没有定义CONVERAGE_ONLY,那么这两条xor就是将__afl_prev_loc的值与rcx的值进行交换,然后将__afl_prev_loc的值右移一下*/
" xorq __afl_prev_loc(%rip), %rcx\n"
" xorq %rcx, __afl_prev_loc(%rip)\n"
" shrq $1, __afl_prev_loc(%rip)\n"
#endif /* ^!COVERAGE_ONLY */
"\n"
#ifdef SKIP_COUNTS /*假如定义了SKIP_COUNTS,那么会执行下面的or语句*/
" orb $1, (%rdx, %rcx, 1)\n"
#else /*没有定义的话,变成这个*/
" incb (%rdx, %rcx, 1)\n" /*这里的rdx的值存的就是共享内存的地址*/
#endif /* ^SKIP_COUNTS */
"\n"

__afl_return

1
2
3
4
5
6
7
8
9
  /*先将al+0x7f,然后再把标志寄存器FLAGS的值从AH中恢复回去*/
/*注意:这里调用afl_maybe_log,其实是执行到afl_return 才返回的*/
" addb $127, %al\n"
#if defined(__OpenBSD__) || (defined(__FreeBSD__) && (__FreeBSD__ < 9))
" .byte 0x9e /* sahf */\n"
#else
" sahf\n"
#endif /* ^__OpenBSD__, etc */
" ret\n"

__afl_setup

1
2
3
/*首先判断之前有没有错误,如果有,直接返回*/
" cmpb $0, __afl_setup_failure(%rip)\n"
" jne __afl_return\n"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"  /* Check out if we have a global pointer on file.
(判断我们是否有一个文件全局指针,即__afl_global_area_ptr是否为NULL)
如果存在的话,就把afl_area_ptr的值放到rdx,调用afl_store,这里__afl_store就在上面
不存在的话就继续调用__afl_setup_first */\n"
"\n"
#ifndef __APPLE__
" movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n"
" movq (%rdx), %rdx\n"
#else
" movq __afl_global_area_ptr(%rip), %rdx\n"
#endif /* !^__APPLE__ */
" testq %rdx, %rdx\n"
" je __afl_setup_first\n"
"\n"
" movq %rdx, __afl_area_ptr(%rip)\n"
" jmp __afl_store\n"

__afl_setup_first

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
/*将剩下所有会被libc库函数影响的寄存器保存到栈上面*/
"\n"
" /* Save everything that is not yet saved and that may be touched by\n"
" getenv() and several other libcalls we'll be relying on. */\n"
"\n"
" leaq -352(%rsp), %rsp\n"
"\n"
" movq %rax, 0(%rsp)\n"
" movq %rcx, 8(%rsp)\n"
" movq %rdi, 16(%rsp)\n"
" movq %rsi, 32(%rsp)\n"
" movq %r8, 40(%rsp)\n"
" movq %r9, 48(%rsp)\n"
" movq %r10, 56(%rsp)\n"
" movq %r11, 64(%rsp)\n"
"\n"
" movq %xmm0, 96(%rsp)\n"
" movq %xmm1, 112(%rsp)\n"
" movq %xmm2, 128(%rsp)\n"
" movq %xmm3, 144(%rsp)\n"
" movq %xmm4, 160(%rsp)\n"
" movq %xmm5, 176(%rsp)\n"
" movq %xmm6, 192(%rsp)\n"
" movq %xmm7, 208(%rsp)\n"
" movq %xmm8, 224(%rsp)\n"
" movq %xmm9, 240(%rsp)\n"
" movq %xmm10, 256(%rsp)\n"
" movq %xmm11, 272(%rsp)\n"
" movq %xmm12, 288(%rsp)\n"
" movq %xmm13, 304(%rsp)\n"
" movq %xmm14, 320(%rsp)\n"
" movq %xmm15, 336(%rsp)\n"
"\n"
1
2
3
4
5
6
7
8
9
10
11
/*这里先保存r12,然后将栈指针保存到r12里面,再开一段栈空间,进行对齐*/
" /* Map SHM, jumping to __afl_setup_abort if something goes wrong. */\n"
"\n"
" /* The 64-bit ABI requires 16-byte stack alignment. We'll keep the\n"
" original stack ptr in the callee-saved r12. */\n"
"\n"
" pushq %r12\n"
" movq %rsp, %r12\n"
" subq $16, %rsp\n"
" andq $0xfffffffffffffff0, %rsp\n"
"\n"
1
2
3
4
5
6
7
/*这里就是调用getenv去拿存在环境变量中的共享内存标志符,拿不到的话,就会跳到__afl_setup_abort*/
" leaq .AFL_SHM_ENV(%rip), %rdi\n"
CALL_L64("getenv")
"\n"
" testq %rax, %rax\n"
" je __afl_setup_abort\n"
"\n"
1
2
3
4
5
6
7
8
9
10
11
12
13
/*这里调用atoi将字符串转为数字,然后调用shmat拿到共享内存,然后判断一下shamat
的结果,假如拿不到,也会跳到__afl_setup_abort*/
" movq %rax, %rdi\n"
CALL_L64("atoi")
"\n"
" xorq %rdx, %rdx /* shmat flags */\n"
" xorq %rsi, %rsi /* requested addr */\n"
" movq %rax, %rdi /* SHM ID */\n"
CALL_L64("shmat")
"\n"
" cmpq $-1, %rax\n"
" je __afl_setup_abort\n"
"\n"
1
2
3
4
5
6
7
8
9
10
11
/*这里是把共享内存的地址存到afl_area_ptr和afl_global_area_ptr指向的内存*/
" /* Store the address of the SHM region. */\n"
"\n"
" movq %rax, %rdx\n"
" movq %rax, __afl_area_ptr(%rip)\n"
"\n"
#ifdef __APPLE__
" movq %rax, __afl_global_area_ptr(%rip)\n"
#else
" movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n"
" movq %rax, (%rdx)\n"

__afl_forkserver

接下来就是fork server的逻辑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*先是push两次rdx来使得栈整齐一点
然后是将__afl_temp中的4个字节写到提前开好的管道中,这里管道的过程在afl-fuzz
的代码中。
*/
"\n"
" /* Enter the fork server mode to avoid the overhead of execve() calls. We\n"
" push rdx (area ptr) twice to keep stack alignment neat. */\n"
"\n"
" pushq %rdx\n"
" pushq %rdx\n"
"\n"
" /* Phone home and tell the parent that we're OK. (Note that signals with\n"
" no SA_RESTART will mess it up). If this fails, assume that the fd is\n"
" closed because we were execve()d from an instrumented binary, or because\n"
" the parent doesn't want to use the fork server. */\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_temp(%rip), %rsi /* data */\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n"
CALL_L64("write")
"\n"
" cmpq $4, %rax\n"
" jne __afl_fork_resume\n"
"\n"

__afl_fork_wait_loop

1
2
3
4
5
6
7
8
9
10
11
/*这里是不断地从管道中读取内容,假如读取到的字节数不为4就会跳到__afl_die*/
"\n"
" /* Wait for parent by reading from the pipe. Abort if read fails. */\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_temp(%rip), %rsi /* data */\n"
" movq $" STRINGIFY(FORKSRV_FD) ", %rdi /* file desc */\n"
CALL_L64("read")
" cmpq $4, %rax\n"
" jne __afl_die\n"
"\n"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*读取成功:这里先fork了,然后判断 fork是否成功,如果成功就会跳到__afl_fork_resume
失败跳到__afl_die
之后把fork出来的pid存到__afl_fork_pid中,再写到fuzzer通信的管道中*/
" /* Once woken up, create a clone of our process. This is an excellent use\n"
" case for syscall(__NR_clone, 0, CLONE_PARENT), but glibc boneheadedly\n"
" caches getpid() results and offers no way to update the value, breaking\n"
" abort(), raise(), and a bunch of other things :-( */\n"
"\n"
CALL_L64("fork")
" cmpq $0, %rax\n"
" jl __afl_die\n"
" je __afl_fork_resume\n"
"\n"
" /* In parent process: write PID to pipe, then wait for child. */\n"
"\n"
" movl %eax, __afl_fork_pid(%rip)\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_fork_pid(%rip), %rsi /* data */\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n"
CALL_L64("write")
"\n"
1
2
3
4
5
6
7
8
9
/*这里是父进程等待子进程,如果 waitpid返回的结果小于等于0,就会跳afl_die,waitpid
也会把子进程的状态写到afl_temp中*/
" movq $0, %rdx /* no flags */\n"
" leaq __afl_temp(%rip), %rsi /* status */\n"
" movq __afl_fork_pid(%rip), %rdi /* PID */\n"
CALL_L64("waitpid")
" cmpq $0, %rax\n"
" jle __afl_die\n"
"\n"
1
2
3
4
5
6
7
8
9
10
11
/*然后把子进程的状态通过管道写回到fuzzer中,跳回到__afl_fork_wait_loop,继续
等待fuzzer的fork请求*/
" /* Relay wait status to pipe, then loop back. */\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_temp(%rip), %rsi /* data */\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n"
CALL_L64("write")
"\n"
" jmp __afl_fork_wait_loop\n"
"\n"

__afl_fork_resume

1
2
3
4
5
6
7
8
9
10
/*这里把两个管道给关掉*/
"\n"
" /* In child process: close fds, resume execution. */\n"
"\n"
" movq $" STRINGIFY(FORKSRV_FD) ", %rdi\n"
CALL_L64("close")
"\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi\n"
CALL_L64("close")
"\n"
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
/*然后把各种寄存器恢复,跳到__afl_store*/
" popq %rdx\n"
" popq %rdx\n"
"\n"
" movq %r12, %rsp\n"
" popq %r12\n"
"\n"
" movq 0(%rsp), %rax\n"
" movq 8(%rsp), %rcx\n"
" movq 16(%rsp), %rdi\n"
" movq 32(%rsp), %rsi\n"
" movq 40(%rsp), %r8\n"
" movq 48(%rsp), %r9\n"
" movq 56(%rsp), %r10\n"
" movq 64(%rsp), %r11\n"
"\n"
" movq 96(%rsp), %xmm0\n"
" movq 112(%rsp), %xmm1\n"
" movq 128(%rsp), %xmm2\n"
" movq 144(%rsp), %xmm3\n"
" movq 160(%rsp), %xmm4\n"
" movq 176(%rsp), %xmm5\n"
" movq 192(%rsp), %xmm6\n"
" movq 208(%rsp), %xmm7\n"
" movq 224(%rsp), %xmm8\n"
" movq 240(%rsp), %xmm9\n"
" movq 256(%rsp), %xmm10\n"
" movq 272(%rsp), %xmm11\n"
" movq 288(%rsp), %xmm12\n"
" movq 304(%rsp), %xmm13\n"
" movq 320(%rsp), %xmm14\n"
" movq 336(%rsp), %xmm15\n"
"\n"
" leaq 352(%rsp), %rsp\n"
"\n"
" jmp __afl_store\n"
"\n"

__afl_die

1
2
3
4
5
/*这里就是简单的exit*/
"\n"
" xorq %rax, %rax\n"
CALL_L64("_exit")
"\n"

__afl_setup_abort

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
/*这里就是设置__afl_setup_failure为1,然后恢复下寄存器,直接返回*/
"\n"
" /* Record setup failure so that we don't keep calling\n"
" shmget() / shmat() over and over again. */\n"
"\n"
" incb __afl_setup_failure(%rip)\n"
"\n"
" movq %r12, %rsp\n"
" popq %r12\n"
"\n"
" movq 0(%rsp), %rax\n"
" movq 8(%rsp), %rcx\n"
" movq 16(%rsp), %rdi\n"
" movq 32(%rsp), %rsi\n"
" movq 40(%rsp), %r8\n"
" movq 48(%rsp), %r9\n"
" movq 56(%rsp), %r10\n"
" movq 64(%rsp), %r11\n"
"\n"
" movq 96(%rsp), %xmm0\n"
" movq 112(%rsp), %xmm1\n"
" movq 128(%rsp), %xmm2\n"
" movq 144(%rsp), %xmm3\n"
" movq 160(%rsp), %xmm4\n"
" movq 176(%rsp), %xmm5\n"
" movq 192(%rsp), %xmm6\n"
" movq 208(%rsp), %xmm7\n"
" movq 224(%rsp), %xmm8\n"
" movq 240(%rsp), %xmm9\n"
" movq 256(%rsp), %xmm10\n"
" movq 272(%rsp), %xmm11\n"
" movq 288(%rsp), %xmm12\n"
" movq 304(%rsp), %xmm13\n"
" movq 320(%rsp), %xmm14\n"
" movq 336(%rsp), %xmm15\n"
"\n"
" leaq 352(%rsp), %rsp\n"
"\n"
" jmp __afl_return\n"
"\n"
".AFL_VARS:\n"
"\n"

总结:
首先afl-fuzz这个程序会创建两个管道,然后利用afl-gcc或者afl-clang编译的程序,就会被执行,
之前在afl-as.c中也分析到了,main函数肯定会被插桩的,也就是肯定会调用__afl_maybe_log
而对于第一次运行的进程,就会作为fork-server,后面的由fork-server fork出来的才是真正被fuzz的程序
然后fork-server不断地等待fuzzer的指令去fork子进程,用waitpid去拿到子进程的结束状态,写回给fuzzer

0%