xfuzz

本文最后更新于:2023年8月16日 晚上

cc_params传参调用execv

1
2
3
4
5
6
7
8
9
10
11
12
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
"({ static volatile char *_B __attribute__((used)); "
" _B = (char*)\"" PERSIST_SIG "\"; "
#ifdef __APPLE__
"__attribute__((visibility(\"default\"))) "
"int _L(unsigned int) __asm__(\"___afl_persistent_loop\"); "
#else
"__attribute__((visibility(\"default\"))) "
"int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); "
#endif /* ^__APPLE__ */
"_L(_A); })";

编译器 -D 选项,编译时拓展宏定义

读完 persistent mode 运行流程

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
int __afl_persistent_loop(unsigned int max_cnt) {

static u8 first_pass = 1;
static u32 cycle_cnt;

if (first_pass) {

/* Make sure that every iteration of __AFL_LOOP() starts with a clean slate.
On subsequent calls, the parent will take care of that, but on the first
iteration, it's our job to erase any trace of whatever happened
before the loop. */

if (is_persistent) {

memset(__afl_area_ptr, 0, MAP_SIZE);
__afl_area_ptr[0] = 1;
__afl_prev_loc = 0;
}

cycle_cnt = max_cnt;
first_pass = 0;
return 1;

}

if (is_persistent) {

if (--cycle_cnt) {

raise(SIGSTOP);

__afl_area_ptr[0] = 1;
__afl_prev_loc = 0;

return 1;

} else {

/* When exiting __AFL_LOOP(), make sure that the subsequent code that
follows the loop is not traced. We do that by pivoting back to the
dummy output region. */

__afl_area_ptr = __afl_area_initial;

}

}

return 0;

}
  • 首先在main函数之前读取共享内容,然后以当前进程为fork server,去和AFL fuzz通信。

  • 当AFL fuzz通知进行一次fuzz,由于此时child_stopped为0,则fork server先fork出一个子进程。

  • 这个子进程会很快执行到__AFL_LOOP包围的代码,因为是第一次执行loop,所以会先清空__afl_area_ptr和设置__afl_prev_loc为0,并向共享内存的第一个元素写一个值,然后设置循环次数1000,随后返回1,此时while(__AFL_LOOP)满足条件,于是执行一次fuzzAPI。

  • 然后因为是while循环,会再次进入__AFL_LOOP里,此时将循环次数减一,变成999,然后发出信号SIGSTOP来让当前进程暂停,因为我们设置了WUNTRACED,所以waitpid函数就会返回,fork server将继续执行。

  • fork server在收到SIGSTOP信号后就知道fuzzAPI已经被成功执行结束了,就设置child_stopped为1,并告知AFL fuzz

  • 然后当AFL fuzz通知再进行一次fuzz的时候,fork server将不再需要去fork出一个新的子进程去进行fuzz,只需要恢复之前的子进程继续执行,并设置child_stopped为0

  • 因为我们是相当于重新执行一次程序,所以将__afl_prev_loc设置为0,并向共享内存的第一个元素写一个值,随后直接返回1,此时while(__AFL_LOOP)满足条件,于是执行一次fuzzAPI,然后因为是while循环,会再次进入__AFL_LOOP里,再次减少一次循环次数变成998,并发出信号暂停。

  • 上述过程重复执行,直到第1000次执行时,先恢复执行,然后返回1,然后执行一次fuzzAPI,然后因为是while循环,会再次进入__AFL_LOOP里,再次减少一次循环次数变成0,此时循环次数cnt已经被减到0,就不会再发出信号暂停子进程,而是设置__afl_area_ptr指向一个无关数组__afl_area_initial,随后将子进程执行到结束。

这是因为程序依然会向后执行并触发到instrument,这会向__afl_area_ptr里写值,但是此时我们其实并没有执行fuzzAPI,我们并不想向共享内存里写值,于是将其指向一个无关数组,随意写值。同理,在deferred instrumentation模式里,在执行__afl_manual_init之前,也是向无关数组里写值,因为我们将fork点手动设置,就代表在这个fork点之前的path我们并不关心。

  • 重新整理一下上面的逻辑

    • loop第一次执行的时候,会初始化,然后返回1,执行一次fuzzAPI,然后cnt会减到999,然后抛出信号暂停子进程。
    • loop第二次执行的时候,恢复执行,清空一些值,然后返回1,执行一次fuzzAPI,然后cnt会减到998,然后抛出信号暂停子进程。
    • loop第1000次执行的时候,恢复执行,清空一些值,然后返回1,执行一次fuzzAPI,然后cnt会减到0,然后就设置指向无关数组,返回0,while循环结束,程序也将执行结束。
  • 此时fork server将不再收到SIGSTOP信号,于是child_stopped仍为0。

  • 所以当AFL fuzz通知fork server再进行一次fuzz的时候,由于此时child_stopped为0,则fork server会先fork出一个子进程,然后后续过程和之前一样了。

涉及 编译时的 __AFL_LOOP 宏展开,以及宏内定义的 __afl_persistent_loop( ) 函数

参考:

https://eternalsakura13.com/2020/08/23/afl/

内3.3.2部分

回到 xfuzz 关系的分析

C++语法:

throw,try,catch


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!