首页
论坛
课程
招聘
[原创]AFL编译插桩部分源码分析
2021-2-8 16:49 6260

[原创]AFL编译插桩部分源码分析

2021-2-8 16:49
6260

AFL的编译插桩是在afl-as部分完成的。本部分主要介绍afl-as以及相关编译插桩的内容。

目录

开始之前

本篇是afl源码阅读的第二篇,在上一篇我没有主要介绍插桩相关的内容,放在这一章来简单讲一下。

 

在本篇之后还会有最后一篇第三篇来介绍AFL的 LLVM 优化的相关内容。

一个afl-gcc编译出来的程序是什么样的

首先我们不去看源码,直接先看一下插桩后的样子。
我们使用一个很简单的程序

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
 
int vuln(char *str)
{
    int len = strlen(str);
    if(str[0] == 'A' && len == 66)
    {
        raise(SIGSEGV);
        //如果输入的字符串的首字符为A并且长度为66,则异常退出
    }
    else if(str[0] == 'F' && len == 6)
    {
        raise(SIGSEGV);
        //如果输入的字符串的首字符为F并且长度为6,则异常退出
    }
    else
    {
        printf("it is good!\n");
    }
    return 0;
}
 
int main(int argc, char *argv[])
{
    char buf[100]={0};
    gets(buf);//存在栈溢出漏洞
    printf(buf);//存在格式化字符串漏洞
    vuln(buf);
 
    return 0;
}


可以看到这里已经显示了 Instrumented 10 locations

我们将其拉入IDA看一下。


可以看到afl在代码段进行了插桩,主要是 __afl_maybe_log 函数,用来探测、反馈程序此时的状态。

afl-as.c源码分析

main函数

main函数主要做了一下几步

  • 通过调用edit_params(argc, argv)编辑了参数。
  • 调用add_instrumentation()进行插桩。
  • fork出一个子进程,在子进程中执行我们编辑好的参数。
  • 等待子进程执行完退出的信号。
  • 退出,exit参数为WEXITSTATUS(status)

    edit_params(int argc, char **argv)

  • 首先获取环境变量TMPDIRAFL_AS
  • 如果设置了clang_mode,且由环境变量获取的afl_as为空
    • 设置use_clang_as = 1
    • afl_as为环境变量"AFL_CC"的值
    • 如果还是没有获取到 afl_as,令afl_as为环境变量"AFL_CXX"的值
    • 如果还是没有获取到 afl_as,令afl_as为"clang"
  • 获取tmp目录的位置,跟上一步类似
    • getenv("TEMP")
    • getenv("TMP")
    • 若前两个环境变量都没有获取到,直接令其为"/tmp"
  • as_params分配空间。
  • 接下来处理as_params[0]
    • 如果afl_as不空的话,as_params[0]=afl_as
    • 否则指定为"as"
  • as_params[argc]为0.
  • 接下来扫描argv中的参数。
    • 如果设置了"--64",令use_64bit = 1
    • 如果是32为,那么use_64bit = 0
    • 如果是macos。
      • "-arch"指定了"i386",那么abort。
        Sorry, 32-bit Apple platforms are not supported.
      • 若在clang mode下,并且当前的argv[i]不是q或Q,那么跳过这个参数,直接continue掉。
    • 否则直接将当前的argv[i]放入as_params[]参数数组中
  • 如果是macos且使用的use_clang_as
    • 向参数数组as_params[]中依次添加:-c -x assembler
  • argv[argc - 1]为input_file
  • 如果input_file以'-'开头
    • 如果是"-version"
      • 设置just_version = 1
      • modified_file = input_file
      • 直接跳转到wrap_things_up
    • 如果input_file[1]还有其他值,告知用户使用错误,abort
    • 否则input_file = NULL
  • 否则比较当前input_file是否以tmp_dir"/var/tmp/""/tmp/"开头。
    • 若均不是,则令pass_thru = 1
  • 设置modified_file
    modified_file = alloc_printf("%s/afl-%u-%u.s", tmp_dir, getpid(),(u32) time(NULL))
  • 最后到达wrap_things_up:
    • as_params[]最后一个有效参数为modified_file
    • as_params[]最后一个位置补NULL,标志结束。

      插桩函数 add_instrumentation(void)

      在编辑完as_params[]参数数组后进入了此插桩函数。
      Process input file, generate modified_file. Insert instrumentation in all the appropriate places.
  • 如果设置了input_file
    • 只读打开input_file,fd为inf
      input_file:/Users/apple/Desktop/AFL/AFL/cmake-build-debug/tmp/test-instr.s
  • 否则inf为stdin
  • 打开modified_file,返回out_fd
  • 接下来通过while循环每次从input_file(test-instr.s)中读取一行到line中(大小为8192)static u8 line[MAX_LINE];

到了真正插桩的部分了,首先明确,afl只在.text段插桩。所以先要找到.text的位置,并在对应的位置设置instr_ok = 1代表找到了一个位置。

 

首先我们跳过所有的标签、宏、注释。

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
if (line[0] == '\t' && line[1] == '.') {
 
    /* OpenBSD puts jump tables directly inline with the code, which is
       a bit annoying. They use a specific format of p2align directives
       around them, so we use that as a signal. */
 
    if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&
        isdigit(line[10]) && line[11] == '\n')
        skip_next_label = 1;
 
    if (!strncmp(line + 2, "text\n", 5) ||
        !strncmp(line + 2, "section\t.text", 13) ||
        !strncmp(line + 2, "section\t__TEXT,__text", 21) ||
        !strncmp(line + 2, "section __TEXT,__text", 21)) {
        instr_ok = 1;
        continue;
    }
 
    if (!strncmp(line + 2, "section\t", 8) ||
        !strncmp(line + 2, "section ", 8) ||
        !strncmp(line + 2, "bss\n", 4) ||
        !strncmp(line + 2, "data\n", 5)) {
        instr_ok = 0;
        continue;
    }
 
}

在这里我们判断读入的这一行line是否以"\t."开头。(即尝试匹配.s中声明的段)

  • 如果是的话进入更深的判断。
    • 首先检查是否是".p2align "指令,如果是的话设置skip_next_label = 1
    • 接下来尝试匹配:text\n "section\t.text" "section\t__TEXT,__text" "section __TEXT,__text"
      • 如果匹配到了设置instr_ok = 1,代表我们此时正在.text段。
      • 然后直接continue跳本次循环
    • 尝试匹配:"section\t" "section " "bss\n" "data\n"
      • 如果匹配到了说明我们在其他段中。设置instr_ok = 0然后continue

接下来判断一些其他信息,比如att汇编还是intel汇编,设置对应标志位。

 

AFL尝试抓住一些能标志程序变化的重要的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
If we're in the right mood for instrumenting, check for function
names or conditional labels. This is a bit messy, but in essence,
we want to catch:
 
  ^main:      - function entry point (always instrumented)
  ^.L0:       - GCC branch label
  ^.LBB0_0:   - clang branch label (but only in clang mode)
  ^\tjnz foo  - conditional branches
 
...but not:
 
  ^# BB#0:    - clang comments
  ^ # BB#0:   - ditto
  ^.Ltmp0:    - clang non-branch labels
  ^.LC0       - GCC non-branch labels
  ^.LBB0_0:   - ditto (when in GCC mode)
  ^\tjmp foo  - non-conditional jumps
 
Additionally, clang and GCC on MacOS X follow a different convention
with no leading dots on labels, hence the weird maze of #ifdefs
later on.

稍微总结一下就是,AFL试图抓住:_main:(这是必然会插桩的位置)、以及gcc和clang下的分支标记,并且还有条件跳转分支。这几个关键的位置是其着重关注的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* Conditional branch instruction (jnz, etc). We append the instrumentation
           right after the branch (to instrument the not-taken path) and at the
           branch destination label (handled later on). */
 
        if (line[0] == '\t') {
 
            if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) {
 
                fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
                        R(MAP_SIZE));
 
                ins_lines++;
 
            }
 
            continue;
 
        }

如果是形如:\tj[^m].的指令,即条件跳转指令,并且R(100)产生的随机数小于插桩密度inst_ratio,那么直接使用fprintftrampoline_fmt_64(插桩部分的指令)写入文件。写入大小为小于MAP_SIZE的随机数。R(MAP_SIZE)

 

然后插桩计数ins_lines加一。continue

 

接下来也是对于label的相关评估,有一些label可能是一些分支的目的地,需要自己的评判。

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
/* Label of some sort. This may be a branch destination, but we need to
          tread carefully and account for several different formatting
          conventions. */
 
       /* Apple: L<whatever><digit>: */
 
       if ((colon_pos = strstr(line, ":"))) {
 
           if (line[0] == 'L' && isdigit(*(colon_pos - 1))) {
               /* .L0: or LBB0_0: style jump destination */
 
                /* Apple: L<num> / LBB<num> */
 
               if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3)))
                   && R(100) < inst_ratio) {
 
                       if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;
 
               }
 
           } else {
 
               /* Function label (always instrumented, deferred mode). */
 
               instrument_next = 1;
 
           }

首先判断line中是否有形如类似:^L.*\d(:$)的字符串(比如"Ltext0:")

  • 接下来更进一步的判断L之后是否为为数字 或者 是否满足在clang mode下,line为"LBB"。(L\<num> / LBB\<num>
    • 如果匹配到了,那么在满足插桩密度以及未设置skip_next_label的情况下。
      • instrument_next = 1(defer mode)
      • 否则令skip_next_label = 0

而如果只匹配到了line中存在":"但line并非以L开头。那么说明是Function label
此时设置instrument_next = 1进行插桩。

 

这一切进行完之后,回到while函数的下一个循环中。而在下一个循环的开头,对于以deferred mode进行插桩的位置进行了真正的插桩处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* In some cases, we want to defer writing the instrumentation trampoline
     until after all the labels, macros, comments, etc. If we're in this
     mode, and if the line starts with a tab followed by a character, dump
     the trampoline now. */
 
  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++;
 
  }

这里关键的两个判断:instr_ok && instrument_next,如果在代码段中,且设置了以deferred mode进行插桩,那么就在这个地方进行插桩,写入trampoline_fmt_64

 

插桩完毕后生成的.s文件如图:

可以看到已经被插桩了。这里也就是我们一开始看到的:__afl_maybe_log

收尾工作

在插桩结束后,我们把参数打印一下:

可以看到这里在用汇编器as来将我们插桩好的.s文件生成可执行文件。
而真正的汇编过程是fork出一个子进程来执行的。

1
2
3
4
if (!(pid = fork())) {
    execvp(as_params[0], (char **) as_params);
    FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]);
}

main函数中等待子进程执行完毕后退出。

 

至此整个插桩过程就结束了。

instrumentation trampoline究竟是什么?

在上一部分我们已经知道了,64位下AFL将trampoline_fmt_64写入.s文件的指定位置作为插桩。

 

本部分主要来讨论AFL究竟插进去了什么东西。

trampoline_fmt_64

我们直接看ida中的内容,非常直观,trampoline_fmt_64就是如下汇编:

1
2
3
4
5
6
7
8
9
10
lea     rsp, [rsp-98h]
mov     [rsp+98h+var_98], rdx
mov     [rsp+98h+var_90], rcx
mov     [rsp+98h+var_88], rax
mov     rcx, 46A1h          ;注意这里46A1h就是在fptintf插桩的时候R(MAP_SIZE)产生的随机数,可以用于区分每个桩
call    __afl_maybe_log
mov     rax, [rsp+98h+var_88]
mov     rcx, [rsp+98h+var_90]
mov     rdx, [rsp+98h+var_98]
lea     rsp, [rsp+98h]

__afl_maybe_log

大体流程如下:

 

在这之前我们首先要关注几个位于bss段的变量:

1
2
3
4
5
6
7
8
.AFL_VARS:
 
  .comm   __afl_area_ptr, 8
  .comm   __afl_prev_loc, 8
  .comm   __afl_fork_pid, 4
  .comm   __afl_temp, 4
  .comm   __afl_setup_failure, 1
  .comm    __afl_global_area_ptr, 8, 8
  • __afl_area_ptr:共享内存的地址。
  • __afl_prev_loc:上一个插桩位置(R(100)随机数的值)
  • __afl_fork_pid:由fork产生的子进程的pid
  • __afl_temp:缓冲区
  • __afl_setup_failure:标志位,如果置位则直接退出。
  • __afl_global_area_ptr:一个全局指针。
1
2
3
4
5
6
7
8
9
10
__afl_maybe_log:
 
  lahf
  seto  %al
 
  /* Check if SHM region is already mapped. */
 
  movq  __afl_area_ptr(%rip), %rdx
  testq %rdx, %rdx
  je    __afl_setup

首先lahf用于将标志寄存器的低八位送入AH,即将标志寄存器FLAGS中的SF、ZF、AF、PF、CF五个标志位分别传送到累加器AH的对应位(八位中有三位是无效的)。

 

接下来seto溢出置位。

 

然后检查共享内存是否已经被设置了。即__afl_area_ptr是否为空?

  • 如果为NULL则说明还没有被设置,跳转到__afl_setup进行设置。
  • 否则继续运行。

__afl_setup

__afl_setup:中用于初始化__afl_area_ptr,只有在运行到第一个桩时会进行本次初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__afl_setup:
 
  /* Do not retry setup if we had previous failures. */
 
  cmpb $0, __afl_setup_failure(%rip)
  jne __afl_return
 
  /* Check out if we have a global pointer on file. */
 
  movq  __afl_global_area_ptr(%rip), %rdx
  testq %rdx, %rdx
  je    __afl_setup_first
 
  movq %rdx, __afl_area_ptr(%rip)
  jmp  __afl_store

如果afl_setup_failure不为0的话,直接跳转到afl_return返回。

 

接下来检查afl_global_area_ptr文件指针是否为NULL,如果为空则跳转到```afl_setup_first```。

 

否则将afl_global_area_ptr的值赋给afl_area_ptr后跳转到__afl_store

__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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
__afl_setup_first:
 
  /* Save everything that is not yet saved and that may be touched by
     getenv() and several other libcalls we'll be relying on. */
 
  leaq -352(%rsp), %rsp
 
  movq %rax,   0(%rsp)
  movq %rcx,   8(%rsp)
  movq %rdi,  16(%rsp)
  movq %rsi,  32(%rsp)
  movq %r8,   40(%rsp)
  movq %r9,   48(%rsp)
  movq %r10,  56(%rsp)
  movq %r11,  64(%rsp)
 
  movq %xmm0,  96(%rsp)
  movq %xmm1,  112(%rsp)
  movq %xmm2,  128(%rsp)
  movq %xmm3,  144(%rsp)
  movq %xmm4,  160(%rsp)
  movq %xmm5,  176(%rsp)
  movq %xmm6,  192(%rsp)
  movq %xmm7,  208(%rsp)
  movq %xmm8,  224(%rsp)
  movq %xmm9,  240(%rsp)
  movq %xmm10, 256(%rsp)
  movq %xmm11, 272(%rsp)
  movq %xmm12, 288(%rsp)
  movq %xmm13, 304(%rsp)
  movq %xmm14, 320(%rsp)
  movq %xmm15, 336(%rsp)
 
  /* Map SHM, jumping to __afl_setup_abort if something goes wrong. */
 
  /* The 64-bit ABI requires 16-byte stack alignment. We'll keep the
     original stack ptr in the callee-saved r12. */
 
  pushq %r12
  movq  %rsp, %r12
  subq  $16, %rsp
  andq  $0xfffffffffffffff0, %rsp
 
  leaq .AFL_SHM_ENV(%rip), %rdi
call _getenv
 
  testq %rax, %rax
  je    __afl_setup_abort
 
  movq  %rax, %rdi
call _atoi
 
  xorq %rdx, %rdx   /* shmat flags    */
  xorq %rsi, %rsi   /* requested addr */
  movq %rax, %rdi   /* SHM ID         */
call _shmat
 
  cmpq $-1, %rax
  je   __afl_setup_abort
 
  /* Store the address of the SHM region. */
 
  movq %rax, %rdx
  movq %rax, __afl_area_ptr(%rip)
 
  movq %rax, __afl_global_area_ptr(%rip)
  movq %rax, %rdx

1.在__afl_setup_first中,首先保存寄存器的值(包括xmm寄存器组)

 

2.接下来进行rsp对齐操作。

 

3.获取环境变量"__AFL_SHM_ID"的值(共享内存的id)。如果获取失败,那么跳转到__afl_setup_abort,说明获取失败。

 

4.获取成功后调用shmat启用对共享内存的访问。如果启用失败,跳转到__afl_setup_abort

 

5.将shmat返回的共享内存的地址存储在 __afl_area_ptr__afl_global_area_ptr全局变量中。

 

6.一切顺利的话,接下来运行 __afl_forkserver

__afl_forkserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  /* Enter the fork server mode to avoid the overhead of execve() calls. We
     push rdx (area ptr) twice to keep stack alignment neat. */
 
  pushq %rdx
  pushq %rdx
 
  /* Phone home and tell the parent that we're OK. (Note that signals with
     no SA_RESTART will mess it up). If this fails, assume that the fd is
     closed because we were execve()d from an instrumented binary, or because
     the parent doesn't want to use the fork server. */
 
  movq $4, %rdx               /* length    */
  leaq __afl_temp(%rip), %rsi /* data      */
  movq $(198 + 1), %rdi       /* file desc */
call _write
 
  cmpq $4, %rax
  jne  __afl_fork_resume

首先向FORKSRV_FD+1即199号描述符(即状态管道)中写出__afl_temp中的四个字节,来通知afl我们的fork server已经启动成功。
顺带一提,这里的向状态管道中写的值,在afl-fuzz.c中的这个位置被读出来:

这样我们整个过程就串连起来了。

 

接下来进入:__afl_fork_wait_loop:

__afl_fork_wait_loop:

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
__afl_fork_wait_loop:
 
  /* Wait for parent by reading from the pipe. Abort if read fails. */
 
  movq $4, %rdx               /* length    */
  leaq __afl_temp(%rip), %rsi /* data      */
  movq $198, %rdi             /* file desc */
call _read
  cmpq $4, %rax
  jne  __afl_die
 
  /* Once woken up, create a clone of our process. This is an excellent use
     case for syscall(__NR_clone, 0, CLONE_PARENT), but glibc boneheadedly
     caches getpid() results and offers no way to update the value, breaking
     abort(), raise(), and a bunch of other things :-( */
 
call _fork
  cmpq $0, %rax
  jl   __afl_die
  je   __afl_fork_resume
 
  /* In parent process: write PID to pipe, then wait for child. */
 
  movl %eax, __afl_fork_pid(%rip)
 
  movq $4, %rdx                   /* length    */
  leaq __afl_fork_pid(%rip), %rsi /* data      */
  movq $(198 + 1), %rdi             /* file desc */
call _write
 
  movq $0, %rdx                   /* no flags  */
  leaq __afl_temp(%rip), %rsi     /* status    */
  movq __afl_fork_pid(%rip), %rdi /* PID       */
call _waitpid
  cmpq $0, %rax
  jle  __afl_die
 
  /* Relay wait status to pipe, then loop back. */
 
  movq $4, %rdx               /* length    */
  leaq __afl_temp(%rip), %rsi /* data      */
  movq $(198 + 1), %rdi         /* file desc */
call _write
 
  jmp  __afl_fork_wait_loop

1.首先我们等待parent(fuzz)通过控制管道发来的命令,读入__afl_temp中。

 

2.如果读取失败,那么跳到 __afl_die,break出循环。

 

3._fork出一个子进程,子进程跳入执行:__afl_fork_resume

 

4.将fork出来的子进程pid赋值给__afl_fork_pid

 

5.向状态管道中写出子进程pid,告知parent。此时__afl_maybe_log中的父进程作为forksrever与我们的fuzz进行通信。

 

6.等待我们fork出的子进程执行完毕。然后写入状态管道告知fuzz。

 

7.重新执行下一轮 __afl_fork_wait_loop进行测试。

__afl_fork_resume 与

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
/* In child process: close fds, resume execution. */
 
  movq $198, %rdi
call _close
 
  movq $(198 + 1), %rdi
call _close
 
  popq %rdx
  popq %rdx
 
  movq %r12, %rsp
  popq %r12
 
  movq  0(%rsp), %rax
  movq  8(%rsp), %rcx
  movq 16(%rsp), %rdi
  movq 32(%rsp), %rsi
  movq 40(%rsp), %r8
  movq 48(%rsp), %r9
  movq 56(%rsp), %r10
  movq 64(%rsp), %r11
 
  movq  96(%rsp), %xmm0
  movq 112(%rsp), %xmm1
  movq 128(%rsp), %xmm2
  movq 144(%rsp), %xmm3
  movq 160(%rsp), %xmm4
  movq 176(%rsp), %xmm5
  movq 192(%rsp), %xmm6
  movq 208(%rsp), %xmm7
  movq 224(%rsp), %xmm8
  movq 240(%rsp), %xmm9
  movq 256(%rsp), %xmm10
  movq 272(%rsp), %xmm11
  movq 288(%rsp), %xmm12
  movq 304(%rsp), %xmm13
  movq 320(%rsp), %xmm14
  movq 336(%rsp), %xmm15
 
  leaq 352(%rsp), %rsp
 
  jmp  __afl_store

1.首先关闭子进程中的文件描述符。

 

2.恢复子进程的寄存器状态。

 

3.跳转执行__afl_store

__afl_store:

1
2
3
4
5
6
7
8
9
__afl_store:
 
  /* Calculate and store hit for the code location specified in rcx. */
 
  xorq __afl_prev_loc(%rip), %rcx
  xorq %rcx, __afl_prev_loc(%rip)
  shrq $1, __afl_prev_loc(%rip)
 
  incb (%rdx, %rcx, 1)

这一部分反编译出来如下:

1
2
3
4
tmp = _afl_prev_loc ^ a2;
_afl_prev_loc ^= tmp;
_afl_prev_loc = _afl_prev_loc >> 1;
++*(share_mem_addr + tmp);

而这个a2就是我们在调用_afl_maybe_log时传入的参数rcx
char __usercall _afl_maybe_log@<al>(char a1@<of>, __int64 a2@<rcx>

1
2
mov     rcx, 46A1h          ;注意这里46A1h就是在fptintf插桩的时候R(MAP_SIZE)产生的随机数,可以用于区分每个桩
call    __afl_maybe_log

可以看到这个rcx实际就是我们此时用于标记当前这个桩的随机数,而_afl_prev_loc就是上一个桩的随机数

 

两次异或之后_afl_prev_loc=a2,然后将_afl_prev_loc右移1位为新的_afl_prev_loc。

 

最后在共享内存中存储当前插桩位置的地方计数加一,相当于:share_mem[_afl_prev_loc ^ a2]++,实际上是存入一个64k大小的哈希表,存在碰撞几率,但是问题不大。而这个索引是通过异或得到的。

 

更进一步的,关于为什么要对_afl_prev_loc = _afl_prev_loc >> 1;进行右移1位。

 

AFL主要考虑如下情况:如果此分支是A->AB->B这样的情况那么异或之后就会都变成0,进而使得无法区分。亦或者考虑:A->BB->A的情况,异或后的key也是一样的,难以区分。

 

至此,AFL的插桩就基本分析的差不多了。下一篇会着重讲llvm mode


[公告] 欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

收藏
点赞1
打赏
分享
最新回复 (1)
雪    币: 8155
活跃值: 活跃值 (7238)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2 2021-2-8 16:51
2
0
感谢分享,请楼主分享下高效的学习方式,我也想高产似母猪
游客
登录 | 注册 方可回帖
返回