写在前面
AFL++是一款扩展性很强的基于变异的模糊测试器,在测试二进制固件中的协议消息处理代码时向修改其原有的变异策略,引入报文结构感知以提高模糊测试效果。AFL++本身支持使用C或者python实现自定义的变异器。
预置变异器
AFL++默认预置了一些已经迁移好的变异器,在代码路径下单独编译即可使用:
- radamsa
- gramatron
- grammar_mutator
- …
第三方变异器
Superion Mutators
正在适配中的Superion: https://github.com/adrian-rt/superion-mutator,可以支持多种不同语言的语法变异
libprotobuf Mutators
以下的3个变异器也都在适配中
-
ASN.1 example: https://github.com/airbus-seclab/AFLplusplus-blogpost/tree/main/src/mutator
-
- 教程 https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator
- https://github.com/thebabush/afl-libprotobuf-mutator
抛开上述变异器不谈,我们看下如何实现自定义变异器
自定义变异器
目前支持python和c/c++编写的自定义变异器,对rust语言的支持目前还是不是太完善。简单来说,就是在模糊测试的时候可以传递自定义的变异器,在这个用户实现的变异器中可以执行自定义的变异策略,例如根据给定的语法执行变异以支持结构感知的模糊测试(这也是我们将要实现的功能之一)。
如何选择自定义变异器?
afl-fuzz自定义变异器通过AFL_CUSTOM_MUTATOR_LIBRARY 或环境变量传递AFL_PYTHON_MODULE,并且必须导出模糊函数。现在 AFL++ 还支持多个自定义变异器,可以像这样在同一个环境变量中指定AFL_CUSTOM_MUTATOR_LIBRARY。
export AFL_CUSTOM_MUTATOR_LIBRARY="full/path/to/mutator_first.so;full/path/to/mutator_second.so"
这样在模糊测试时,会依次调用给定的自定义变异器。请注意:
- 自定义的变异阶段发生在第一个非确定性阶段(例如在默认的havoc之前)
- 如果****AFL_CUSTOM_MUTATOR_ONLY设置为1,则仅使用自定义的突变策略
需要重新实现的API
C和C++
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);
- 这里官方文档提到,如果使用C或者C++的API时,并且想要改变输出缓冲区的大小,则必须使用afl_realloc()对其大小进行重新分配,否则在释放缓冲区时会导致崩溃。
Python
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
重写的API功能
init (Python中可选)
用于初始化随机数发生器、设置缓冲区以及设置状态
queue_get (可选)
判断AFL++是否应该对当前待测试的输入项目(在队列Queue里)进行模糊测试。
fuzz_count(可选)
当对种子队列中的种子进行模糊测试时,会根据几个因素选择使用此输入进行模糊测试的次数,
- 当AFL++选择一个队列中的输入项目进行模糊测试时,它会根据一些因素来确定对这个输入进行几次模糊测试。
- 但是,如果自定义变异器(custom mutator)希望自己设置对每个具体输入项目进行模糊的次数,而不是让AFL++自动决定,那么就可以使用这个函数。
- 这个函数一般在没有设置AFL_CUSTOM_MUTATOR_ONLY时使用。
splice_optout (可选)
- AFL++在调用主模糊(fuzz)函数时,默认会传入一个附加数据用于拼接操作。
- 但是如果自定义变异函数不需要这个数据,就定义这个splice_optout函数。
- 这样AFL++就不会传递拼接数据参数,避免传递无用参数浪费时间。
- 这个函数本身是一个空函数,不执行任何操作。定义它只是起到一个开关作用,告诉AFL++不需要传递该参数。
- 所以简而言之,定义splice_optout可以告诉AFL++切割数据无需传递,提高效率。本身不执行任何逻辑代码。
fuzz (可选)
- 这个方法用于对给定的输入进行自定义的变异(mutations)。
- add_buf是另一个队列中的输入内容,可以用于切割和拼接(splicing),也可以用于其他目的,或者忽略不用。如果不使用这个附加数据,可以定义splice_optout标记(见上文)。
- 这个方法是可选的。返回长度为0是一个合法值,意味着跳过此次变异结果。
- 对非Python语言来说:返回的输出缓冲区需要你自己管理内存!
describe(可选)
- 当程序在运行测试用例时 Crash 之后,AFL++会调用这个函数。
- 这个函数的目的是描述最后一个导致 Crash 的测试用例,比如返回一个名称字符串。
- AFL++会根据这个描述把产生 Crash 的测试用例文件命名为该字符串,以便于定位和复现问题。
- 所以定义这个函数可以更好地追踪和管理导致问题的测试用例,有利于快速修复错误。
havoc_mutation 和 havoc_mutation_probability(可选)
- havoc_mutation实现一个自定义的输入变异操作。
- 这个变异会与AFL++内置的havoc模块中的其他随机变异一起执行,实现联合变异。
- havoc_mutation_probability控制在havoc运行期间,是否调用havoc_mutation这个自定义变异。它返回一个概率,默认6%的概率执行。所以这两个方法一起可以实现:在AFL++的havoc随机变异阶段,以6%的概率对输入执行一次自定义变异操作。
post_process(可选)
- 对于某些情况,自定义变异器返回的已经变异过的数据格式,不能直接用来执行目标程序。
- 例如使用libprotobuf-mutator时,返回的数据是protobuf格式,对应一个语法,需要转换为目标程序预期的文本格式。
- 那么可以定义post_process函数进行数据格式转换。
- 这个函数会在执行目标程序前,将数据转换为API预期的格式。
- 它可以返回支持buffer协议和PyBUF_SIMPLE的任何python对象,如bytes和bytearray。
- 在post_process中可以选择不将变异后的数据发送给目标,如数据过短或破损。这样返回NULL缓冲区或长度为0的缓冲区。
- 注意: 该函数内不应做任意随机性的修改!
- C/C++情况下,如果可行应当直接修改data,并返回outbuf = data以优化性能。
fuzz_send (可选)
- 默认情况下,afl-fuzz会自动将测试数据传递给目标程序。
- 但是如果需要自定义数据传输方式,如使用IPC,就不能依赖afl-fuzz,需要自行处理数据传递。
- 这个方法提供了直接向目标程序自己发送数据的接口,替代部分afl_proxy功能。
- 但是有一个前提是目标程序还是需要用afl-fuzz启动,这样afl-fuzz可以继续进行漏洞检测等其他工作。
- 参考例子展示了如何实现自定义数据传输而非完全依赖afl-fuzz。
queue_new_entry (可选)
- AFL++在将新判定的输入addTest用例添加到待测队列后,会调用这个方法。
- 这个方法的目的是让用户知道这个新addTest用例文件是否在添加过程中被内容修改过。
- 如果内容修改了,返回True;如果没有修改,返回False。
- 这对于追踪测试用例的变化过程很有帮助。
introspection (可选)
- 当使用了INTROSPECTION选项重新编译AFL++时,每个新找到的队列条目、Crash或Timeout后,这个方法会被调用。
- 在这个方法里,自定义变异器可以返回一个字符串(const char*),用来报告确切使用了哪些变异。
deinit (Python中可选)
- 这个方法是自定义变异器相关方法中最后一个被调用的方法。
- 它用来去初始化变异器的状态。
种子裁剪(trimming)
AFL++中的通用输入减小算法容易破坏一些复杂格式的结构, 可能导致测试用例队列中有很多目标程序还可以处理,但Python模块已经处理不了的用例。对于这种情况,应该实现自定义的裁剪算法。自定义减小算法需要实现三个方法:
- init_trim: 用来初始化,返回可能的减小步数。
- trim: 每次实际减小操作,返回减小后的输入。
- post_trim: 每次减小后检查是否成功,返回下次步数索引
环境变量:
-
AFL_CUSTOM_MUTATOR_ONLY: 只启用自定义变异(mutator)阶段,禁用其它变异阶段(如havoc、splice等)。这可以防止由于这些阶段产生的非法测试用例填满队列。最好与自定义减小(trimming)例程一起使用。因为减小也可能会像havoc、splice那样破坏输入结构,导致测试用例无法处理。
-
AFL_PYTHON_ONLY: 已经移除,使用AFL_CUSTOM_MUTATOR_ONLY代替。
-
AFL_DEBUG: 当与 AFL_NO_UI 结合使用时,这会导致 C 修剪代码发出有关自定义修剪器的性能和操作的附加消息。 用这个来看看它是否有效:)
总的来说,这些环境变量主要用于控制AFL的mutation(变异)和trimming(减小)过程,以便实现和调试自定义的mutator和trimmer。
一些其他的注意事项
对于 C/C++ 自定义变异器,可以在 afl_custom_init() 中获得指向 afl_state_t *afl 的指针。如果访问并且更新其内容,则需要重新编译自定义变异器,因为结构可能已更改。
- AFL_CUSTOM_INFO_PROGRAM 它保存执行的目标程序(target program)的名称。如果使用了qemu等模式(-Q参数),它仍然会保存目标程序名称,而不是afl-qemu-trace之类的名称。
- AFL_CUSTOM_INFO_PROGRAM_INPUT 如果在使用afl-fuzz时添加了-f参数指定了输入文件,那么这个输入文件的路径就会保存在这个环境变量中。
- AFL_CUSTOM_INFO_PROGRAM_ARGV 它保存的是给定给目标程序的参数字符串。包含@@作为占位符的标识。
注意:
- 如果AFL_CUSTOM_INFO_PROGRAM_INPUT为空,且AFL_CUSTOM_INFO_PROGRAM_ARGV也为空或者不包含@@,那么目标程序会通过标准输入(stdin)获取输入。
- AFL_CUSTOM_INFO_OUT这个环境变量保存的就是afl-fuzz指定的输出目录路径。
编译
对于C/C++:
gcc -shared -Wall -O3 example.c -o example.so
请注意,如果指定了多个自定义修改器,则相应的函数将按照指定的顺序调用。 例如,将调用 example_first.so 的第一个 post_process 函数,然后调用 example_second.so 的 post_process 函数。
运行
C/C++
export AFL_CUSTOM_MUTATOR_LIBRARY="/full/path/to/example_first.so;/full/path/to/example_second.so"
afl-fuzz /path/to/program
Python
export PYTHONPATH=`dirname /full/path/to/example.py`
export AFL_PYTHON_MODULE=example
afl-fuzz /path/to/program
样例
可以参考 example.c 和 example.py.