AFL(American Fuzzy Lop)源码详细解读(6)

感谢大佬们的文章:
http://rk700.github.io/2017/12/28/afl-internals/
https://bbs.pediy.com/thread-265973.htm
这篇记录 afl-as.h 的代码解读,这个文件中是要插入的桩代码,基本上都是汇编码,但是并不难理解,可读性比较高。插入的桩代码是分了32位和64位的,但是差别不大,这里只分析64位的情况。

afl-as.h

插桩代码的流程

在这里插入图片描述

以64位为例,提前介绍几个变量,这些变量位于bss段,即存放的是未初始化的全局变量或是局部静态变量,并且只存放变量的大小。
具体关于bss段的解释请参考:https://www.jianshu.com/p/ddfb284c1f7a

".AFL_VARS:\n"

  "  .lcomm   __afl_area_ptr, 8\n"				// 共享内存地址
#ifndef COVERAGE_ONLY
  "  .lcomm   __afl_prev_loc, 8\n"				// 前一个块ID
#endif /* !COVERAGE_ONLY */
  "  .lcomm   __afl_fork_pid, 4\n"				// fork出的子进程的PID
  "  .lcomm   __afl_temp, 4\n"					// 存放fork server 状态的缓冲区
  "  .lcomm   __afl_setup_failure, 1\n"			// 标志位,是否已经调用过__afl_setup

#endif /* ^__APPLE__ */

  "  .comm    __afl_global_area_ptr, 8, 8\n"	// 全局指针,指向共享内存地址
  "\n"
  ".AFL_SHM_ENV:\n"
  "  .asciz \"" SHM_ENV_VAR "\"\n"				// 这个变量位于.text段,获取环境变量的字符串
1. trampoline_fmt_64
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"   		// 将随机数random() % MAP_SIZE 存入rcx寄存器,我称其为块ID
  "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";
2. __afl_maybe_log
#if defined(__OpenBSD__)  || (defined(__FreeBSD__) && (__FreeBSD__ < 9))
  "  .byte 0x9f /* lahf */\n"
#else
  "  lahf\n"    										// 将标志寄存器的低八位送入AH
#endif /* ^__OpenBSD__, etc */
  "  seto  %al\n"   									// seto溢出置位指令,如果溢出,操作数置1
  "\n"													// 用lahf + seto 代替pop,因为pop很慢。但是会污染到al,不过问题不大
  "  /* Check if SHM region is already mapped. */\n"    // 检查共享内存地址是否映射完毕
  "\n"
  "  movq  __afl_area_ptr(%rip), %rdx\n"    			// __afl_area_ptr 共享内存的地址存至rdx
  "  testq %rdx, %rdx\n"    							// 判断变量是否为null
  "  je    __afl_setup\n"   							// ZF标志位为1时进行跳转,__afl_area_ptr 为null,即没有设置共享内存地址时,跳转
  "\n"
  "__afl_store:\n"										// 共享内存地址已设置,则继续往下执行__afl_store
3. __afl_setup 获取共享内存地址
"__afl_setup:\n"
  "\n"
  "  /* Do not retry setup if we had previous failures. */\n"   // 不管成功与否,只设置一次,不重复设置,所以会有下面这个标志位
  "\n"
  "  cmpb $0, __afl_setup_failure(%rip)\n"  					// 判断__afl_setup_failure 是否为0
  "  jne __afl_return\n"    									// 不为0,说明已经重新设置过了,跳转至返回代码
  "\n"
  "  /* Check out if we have a global pointer on file. */\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"   										// 判断全局指针是否为null
  "  je    __afl_setup_first\n" 								// 如果为null,跳转
  "\n"
  "  movq %rdx, __afl_area_ptr(%rip)\n" 						// 如果不为null,将值赋给__afl_area_ptr
  "  jmp  __afl_store\n" 										// 跳转至 __afl_store
  "\n"
4. **__afl_setup_first **设置指向共享内存的全局变量
"__afl_setup_first:\n"
  "\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"  	// 因为可能会被getenv()和接下来要调用的函数使用
  "\n"
  "  leaq -352(%rsp), %rsp\n"	// 预留空间,用于保存寄存器的状态。这里是不是可以直接用push?但是push很慢,所以用lea+mov替代?
  "\n"
  "  movq %rax,   0(%rsp)\n"    // 保存寄存器的状态,包括xmm寄存器组
  "  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"	// 最后一个保存数据的位置351
  "\n"
  "  /* Map SHM, jumping to __afl_setup_abort if something goes wrong. */\n"	// 映射共享内存,如果出错则跳转至__afl_setup_abort
  "\n"
  "  /* The 64-bit ABI requires 16-byte stack alignment. We'll keep the\n"  	// 64 位 ABI 需要 16 字节堆栈对齐
  "     original stack ptr in the callee-saved r12. */\n"   					// 在被调用者的 r12 寄存器中保存调用者的堆栈指针rsp
  "\n"
  "  pushq %r12\n"  						// 这里最后一个push而不是mov,应该是要更新rsp栈顶指针
  "  movq  %rsp, %r12\n"    				// 将父函数的rsp保存至r12寄存器中
  "  subq  $16, %rsp\n"     				// 
  "  andq  $0xfffffffffffffff0, %rsp\n" 	// sub + and 这两步是对齐操作,最低位对齐为0
  "\n"
  "  leaq .AFL_SHM_ENV(%rip), %rdi\n"   	// 这好像是在将"SHM_ENV_VAR"这个字符串的地址放入rdi
  CALL_L64("getenv")    					// 调用getenv函数,获取共享内存地址
  "\n"
  "  testq %rax, %rax\n"        			// 判断是否获取成功
  "  je    __afl_setup_abort\n" 			// 如果为null,跳转
  "\n"
  "  movq  %rax, %rdi\n"    				// 将获取到的字符串地址放入rdi
  CALL_L64("atoi")  						// 调用atoi,将字符串转换为int型
  "\n"
  "  xorq %rdx, %rdx   /* shmat flags    */\n"  // shmat函数的参数 标志位,通常为0
  "  xorq %rsi, %rsi   /* requested addr */\n"  // shmat函数的参数 让系统来选择共享内存的链接地址
  "  movq %rax, %rdi   /* SHM ID         */\n"  // shmat函数的参数 共享内存ID 即转换后的整数存入
  CALL_L64("shmat") 							// 调用shmat 启动对共享内存的访问,并把共享内存映射到当前进程的地址空间
  "\n"
  "  cmpq $-1, %rax\n"  						// 判断共享内存地址是否设置成功
  "  je   __afl_setup_abort\n"  				// 如果失败,跳转
  "\n"
  "  /* Store the address of the SHM region. */\n"  // 保存共享内存地址
  "\n"
  "  movq %rax, %rdx\n" 						// 将共享内存地址存至rdx
  "  movq %rax, __afl_area_ptr(%rip)\n" 		// 将共享内存地址存至__afl_area_ptr
  "\n"
#ifdef __APPLE__
  "  movq %rax, __afl_global_area_ptr(%rip)\n"	// 不执行
#else
  "  movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n" // 全局指针存至rdx
  "  movq %rax, (%rdx)\n"   					// 将共享内存地址存入全局指针指向的位置
#endif /* ^__APPLE__ */
  "  movq %rax, %rdx\n"							// 将共享内存地址存至rdx
  "\n"
  "__afl_forkserver:\n"							// 继续往下执行__afl_forkserver
5. __afl_forkserver 通知fuzzer, forkserver准备就绪
"__afl_forkserver:\n"
  "\n"
  "  /* Enter the fork server mode to avoid the overhead of execve() calls. We\n"   // 进入fork server模式以避免 execve()调用开销。 
  "     push rdx (area ptr) twice to keep stack alignment neat. */\n"   			// 两次将 rdx 压栈以保持堆栈对齐
  "\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" 	// (请注意,没有 SA_RESTART 的信号会搞砸)
  "     closed because we were execve()d from an instrumented binary, or because\n" // 如果失败,假设 fd 已关闭
  "     the parent doesn't want to use the fork server. */\n"   // 因为执行了插桩的二进制文件,或者因为父级不想使用 fork server。
  "\n"
  "  movq $4, %rdx               /* length    */\n"    						// write函数的参数 要写入的数据长度为4
  "  leaq __afl_temp(%rip), %rsi /* data      */\n" 						// write函数的参数 存放要写入数据的缓冲区
  "  movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi       /* file desc */\n"   // write函数的参数 要写入文件的文件描述符
  CALL_L64("write") 														// 调用write
  "\n"
  "  cmpq $4, %rax\n"   					// 判断是否成功写入
  "  jne  __afl_fork_resume\n"  			// forkserver 启动失败,跳转到__afl_fork_resume恢复寄存器状态,结束进程
  "\n"										// 成功写入,已通知fuzzer  forkserver准备就绪
  "__afl_fork_wait_loop:\n" 				// fork server 进入等待状态
6. __afl_fork_wait_loop 等待指令并fork子进程
"__afl_fork_wait_loop:\n" 				// fork server 进入等待状态
  "\n"
  "  /* Wait for parent by reading from the pipe. Abort if read fails. */\n"    // 等待fuzzer从控制管道传过来的命令
  "\n"
  "  movq $4, %rdx               /* length    */\n" 						// read函数的参数 要读取的数据长度为4
  "  leaq __afl_temp(%rip), %rsi /* data      */\n" 						// read函数的参数 保存读取数据的缓冲区地址
  "  movq $" STRINGIFY(FORKSRV_FD) ", %rdi             /* file desc */\n"   // read函数的参数 要读取文件的文件描述符
  CALL_L64("read")  														// 调用read函数
  "  cmpq $4, %rax\n"   					// 判断是否成功读取
  "  jne  __afl_die\n"  					// 读取失败 跳转
  "\n"
  "  /* Once woken up, create a clone of our process. This is an excellent use\n"   // 收到fuzzer的指令后,开始fork子进程
  "     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"  // 但是 glibc 笨拙地缓存 getpid() 结果
  "     abort(), raise(), and a bunch of other things :-( */\n" // 并且无法更新值、破坏 abort()、raise() 和一堆其他东西
  "\n"
  CALL_L64("fork")  						// 调用fork,创建子进程
  "  cmpq $0, %rax\n"   					// 判断进程id是否为0
  "  jl   __afl_die\n"  					// 如果fork返回值小于0,即fork失败,跳转
  "  je   __afl_fork_resume\n"  			// 如果返回值为0,即是子进程,跳转至子进程执行的代码 __afl_fork_resume
  "\n"  // 否则,是父进程,继续往下执行。父进程将子进程的PID通过管道发送给fuzzer,并等待子进程执行完毕
  "  /* In parent process: write PID to pipe, then wait for child. */\n"    
  "\n"
  "  movl %eax, __afl_fork_pid(%rip)\n" 	// 将子进程的PID存至__afl_fork_pid
  "\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") 						// 调用write向状态管道写入子进程PID
  "\n"
  "  movq $0, %rdx                   /* no flags  */\n" 	// waitpid的参数 为0表示阻塞父进程等待子进程退出
  "  leaq __afl_temp(%rip), %rsi     /* status    */\n" 	// waitpid的参数 指针,用于获得子进程的退出状态
  "  movq __afl_fork_pid(%rip), %rdi /* PID       */\n" 	// waitpid的参数 等待的子进程的PID
  CALL_L64("waitpid")  										// 调用waitpid
  "  cmpq $0, %rax\n"										// 判断是否执行成功
  "  jle  __afl_die\n"  									// 执行失败 则跳转
  "\n"
  "  /* Relay wait status to pipe, then loop back. */\n"    // 将结束状态写入管道发送给fuzzer,之后再次进入等待状态
  "\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") 										// 调用write向状态管道写入子进程结束状态
  "\n"
  "  jmp  __afl_fork_wait_loop\n"   						// 再次进入等待状态
  "\n"
7. __afl_fork_resume 关闭资源,恢复寄存器状态
"__afl_fork_resume:\n"
  "\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"  											// 子进程恢复执行状态
  "  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"    						// 跳转至__afl_store
  "\n"
8. __afl_store 计算边命中次数
"__afl_store:\n"
  "\n"  // 计算并存储 rcx 中指定的代码位置的命中次数
  "  /* Calculate and store hit for the code location specified in rcx. */\n"   
  "\n"
#ifndef COVERAGE_ONLY
  "  xorq __afl_prev_loc(%rip), %rcx\n" 	// prev ^ cur 存至 rcx
  "  xorq %rcx, __afl_prev_loc(%rip)\n" 	// prev ^ cur ^ prev = cur 这两步更新prev
  "  shrq $1, __afl_prev_loc(%rip)\n"   	// prev >> 1
#endif /* ^!COVERAGE_ONLY */
  "\n"
#ifdef SKIP_COUNTS
  "  orb  $1, (%rdx, %rcx, 1)\n"
#else
  "  incb (%rdx, %rcx, 1)\n"    			// 命中次数加1
#endif /* ^SKIP_COUNTS */
  "\n"
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值