AFLplusplus自定义变异器—源码细节

AFL++自定义变异器

  • 支持C/C++库和Python模式
    • C/C++ library (*.so)
    • Python module

1. 简介

相关注意事项

  1. custom_mutator_stage在确定性变异之后启用;
  2. 在使用C/C++接口时,需要引入一个动态链接库(动态库路径存放在环境变量AFL_CUSTOM_MUTATOR_LIBRARY中),该库(部分)定义AFL++暴露出来的接口

可参考 custom_mutators/examples 目录下的示例文件


2. APIs

总览

C/C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void *afl_custom_init(afl_state_t *afl, unsigned int seed);
unsigned int afl_custom_fuzz_count(void *data, const unsigned char *buf, size_t buf_size);
void afl_custom_splice_optout(void *data);
size_t afl_custom_fuzz(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, unsigned char *add_buf, size_t add_buf_size, size_t max_size);
const char *afl_custom_describe(void *data, size_t max_description_len);
size_t afl_custom_post_process(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf);
int afl_custom_init_trim(void *data, unsigned char *buf, size_t buf_size);
size_t afl_custom_trim(void *data, unsigned char **out_buf);
int afl_custom_post_trim(void *data, unsigned char success);
size_t afl_custom_havoc_mutation(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, size_t max_size);
unsigned char afl_custom_havoc_mutation_probability(void *data);
unsigned char afl_custom_queue_get(void *data, const unsigned char *filename);
void (*afl_custom_fuzz_send)(void *data, const u8 *buf, size_t buf_size);
u8 afl_custom_queue_new_entry(void *data, const unsigned char *filename_new_queue, const unsigned int *filename_orig_queue);
const char* afl_custom_introspection(my_mutator_t *data);
void afl_custom_deinit(void *data);

python:

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
def init(seed):
pass

def fuzz_count(buf):
return cnt

def splice_optout()
pass

def fuzz(buf, add_buf, max_size):
return mutated_out

def describe(max_description_length):
return "description_of_current_mutation"

def post_process(buf):
return out_buf

def init_trim(buf):
return cnt

def trim():
return out_buf

def post_trim(success):
return next_index

def havoc_mutation(buf, max_size):
return mutated_out

def havoc_mutation_probability():
return probability # int in [0, 100]

def queue_get(filename):
return True

def fuzz_send(buf):
pass

def queue_new_entry(filename_new_queue, filename_orig_queue):
return False

def introspection():
return string

def deinit(): # optional for Python
pass

细节(C/C++)

变异部分

  • afl_custom_queue_get:用于种子调度,决定该种子是否被模糊测试;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Line 393 in afl-fuzz-one.c
if (unlikely(afl->custom_mutators_count)) {

/* The custom mutator will decide to skip this test case or not. */

LIST_FOREACH(&afl->custom_mutator_list, struct custom_mutator, {

if (el->afl_custom_queue_get &&
!el->afl_custom_queue_get(el->data, afl->queue_cur->fname)) {

return 1;

}

});

}
  • afl_custom_fuzz_count:fuzz次数,相当于stage_max;
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
// Line 1883 in afl-fuzz-one.c
if (el->afl_custom_fuzz_count) {

afl->stage_max = el->afl_custom_fuzz_count(el->data, out_buf, len);

} else {

afl->stage_max = saved_max;

}
...
// Line 1905 in afl-fuzz-one.c
if (!el->afl_custom_fuzz_count) {

/* If we're finding new stuff, let's run for a bit longer, limits
permitting. */

if (afl->queued_items != havoc_queued) {

if (perf_score <= afl->havoc_max_mult * 100) {

afl->stage_max *= 2;
perf_score *= 2;

}

havoc_queued = afl->queued_items;

}

}
  • afl_custom_fuzz:执行特定的变异操作;
1
2
3
// Line 1932 in afl-fuzz-one.c
size_t mutated_size =
el->afl_custom_fuzz(el->data, out_buf, len, &mutated_buf, new_buf, target_len, max_seed_size);
  • afl_custom_havoc_mutation_probability:表示afl_custom_havoc_mutation被调用的概率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Line 2041 in afl-fuzz-one.c
if (el->stacked_custom && el->afl_custom_havoc_mutation_probability) {

el->stacked_custom_prob =
el->afl_custom_havoc_mutation_probability(el->data);
if (el->stacked_custom_prob > 100) {

FATAL(
"The probability returned by "
"afl_custom_havoc_mutation_propability "
"has to be in the range 0-100.");

}

}
  • afl_custom_havoc_mutation:自定义havoc变异操作;
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
// Line 2106 in afl-fuzz-one.c
if (afl->custom_mutators_count) {

LIST_FOREACH(&afl->custom_mutator_list, struct custom_mutator, {

if (el->stacked_custom &&
rand_below(afl, 100) < el->stacked_custom_prob) {

u8 *custom_havoc_buf = NULL;
size_t new_len = el->afl_custom_havoc_mutation(
el->data, out_buf, temp_len, &custom_havoc_buf, MAX_FILE);
if (unlikely(!custom_havoc_buf)) {

FATAL("Error in custom_havoc (return %zu)", new_len);

}

if (likely(new_len > 0 && custom_havoc_buf)) {

temp_len = new_len;
if (out_buf != custom_havoc_buf) {

out_buf = afl_realloc(AFL_BUF_PARAM(out), temp_len);
if (unlikely(!afl->out_buf)) { PFATAL("alloc"); }
memcpy(out_buf, custom_havoc_buf, temp_len);

}

}

}

});

}

初始化/清理部分

  • afl_custom_init:进行一些初始化操作maybe? 初始化一些data数据?mutator->data为void类型数据,可以存放一些自定义的东西?
1
2
3
4
5
6
7
8
9
10
// Line 1838 in afl-fuzz.c
setup_custom_mutators(afl);
// -------------------------------------
// Line 335 in afl-fuzz-mutators.c
/* Initialize the custom mutator */
if (mutator->afl_custom_init) {

mutator->data = mutator->afl_custom_init(afl, rand_below(afl, 0xFFFFFFFF));

}

调用链:afl-fuzz.c main ==> setup_custom_mutators ==> load_custom_mutator ==> afl_custom_init

  • afl_custom_deinit:进行mutator的一些清理程序
1
2
// Line 2648 in afl-fuzz.c 
destroy_custom_mutators(afl);

调用链: afl-fuzz.c main ==> destroy_custom_mutators ==> afl_custom_deinit


Trim相关:

🍖 封装在trim_case_custom函数中(Line 350 in afl-fuzz-mutators.c中)

完整调用链:afl-fuzz-run.c (trim_case: 811) ==> trim_case_custom ==> xxx

1
2
3
4
5
6
7
// Line 823 in afl-fuzz-run.c
if (el->afl_custom_trim) {

trimmed_case = trim_case_custom(afl, q, in_buf, el);
custom_trimmed = true;

}
  • afl_custom_init_trim:初始化操作?可以定义trimming步长 ==> stage_max?返回trim次数?
1
2
3
4
5
6
7
8
9
10
11
12
// Line 365 in afl-fuzz-mutators.c (trim_case_custom())
afl->stage_cur = 0;
s32 retval = mutator->afl_custom_init_trim(mutator->data, in_buf, q->len);
if (unlikely(retval) < 0) {

FATAL("custom_init_trim error ret: %d", retval);

} else {

afl->stage_max = retval; // <============ trimming step!

}
  • afl_custom_trim:执行自定义的精简操作?
1
2
3
4
5
6
7
8
9
10
11
12
13
// Line 384 in afl-fuzz-mutators.c (trim_case_custom())
while (afl->stage_cur < afl->stage_max) {

u8 *retbuf = NULL;

sprintf(afl->stage_name_buf, "ptrim %s",
u_stringify_int(val_buf, trim_exec));

u64 cksum;

size_t retlen = mutator->afl_custom_trim(mutator->data, &retbuf);
...
}
  • afl_custom_post_trim:修剪后处理操作?判断修剪是否成功?并更改一些参数值?
1
2
3
4
5
// Line 476 in afl-fuzz-mutators.c (trim_case_custom())
afl->stage_cur = mutator->afl_custom_post_trim(mutator->data, 1);
...
// Line 488 in afl-fuzz-mutators.c (trim_case_custom())
s32 retval2 = mutator->afl_custom_post_trim(mutator->data, 0);

其他:

  • afl_custom_post_process:post-processing函数,在AFL将测试用例写到磁盘之前调用(write_to_testcase?),以便执行被测目标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Line 67 in afl-fuzz-run.c
u32 __attribute__((hot))
write_to_testcase(afl_state_t *afl, void **mem, u32 len, u32 fix) {
...
// Line 87 in afl-fuzz-run.c
if (el->afl_custom_post_process) {

new_size =
el->afl_custom_post_process(el->data, new_mem, new_size, &new_buf);

if (unlikely(!new_buf || new_size <= 0)) {

new_size = 0;
new_buf = new_mem;
// FATAL("Custom_post_process failed (ret: %lu)", (long
// unsigned)new_size);

} else {...}
...
}
  • afl_custom_queue_new_entry:允许额外的分析(例如,调用一个不同的工具,做一个不同的覆盖率,并将其保存在自定义变异器中)

    调用链:afl-fuzz-init.c (pivot_inputs()) ==> run_afl_custom_queue_new_entry() ==> afl_custom_queue_new_entry()

  • afl_custom_describe:给保存的文件名添加额外的描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Line 340 in afl-fuzz-bitmap.c(describe_op())
const char *custom_description = afl->current_custom_fuzz->afl_custom_describe(afl->current_custom_fuzz->data, size_left);
if (!custom_description || !custom_description[0]) {

DEBUGF("Error getting a description from afl_custom_describe");
/* Take the stage name as description fallback */
sprintf(ret + len_current, "op:%s", afl->stage_short);

} else {

/* We got a proper custom description, use it */
strncat(ret + len_current, custom_description, size_left); // <==== add describe here!

}

3. 总结

  1. AFL++ custom mutators本质上不是一个插件,而是在AFL++模糊测试阶段设置了若干函数端点,并以C/C++接口或者Python接口的方式暴露给用户,并提供给用户使用,旨在提高模糊器的功能性

  2. 多参数项目对fuzz_one()进行了大改,主要包括两个部分:第一个部分从json文件中读入参数规范,第二个部分是根据参数类型选择特定的变异操作,AFL++ custom mutators虽然提供了变异方法,但该方法是在确定性变异后、havoc变异之前执行的,此外还可以在havoc阶段添加havoc_mutator,但:

    1⃣ 多参数项目拆解了确定性变异和havoc,根据参数的类型来pick适当的变异操作,因此将多参数变异添加到custom mutators里不会有啥效果;

    2⃣ AFL++ custom mutators的流程是:在进行确定性变异之后,如果定义了custom_mutator,那么就执行custom_mutator定义的变异,否则跳转到进行havoc变异,其本质还是原生AFL++对于单个输入的顺序变异方式,并没有体现多参数的性质。因此如果要实现多参数,还是需要对引擎进行修改;

    3⃣ 解析参数规范的部分貌似无法添加到合适的接口上;


AFLplusplus自定义变异器—源码细节
http://bladchan.github.io/2023/03/09/AFLplusplus自定义变异器—源码细节/
作者
bladchan
发布于
2023年3月9日
许可协议