AFL++使用
模糊测试
模糊测试 (fuzz testing, fuzzing)是一种软件测试技术。其核心思想是将自动或半自动生成的随机数据输入到一个程序中,并监视程序异常,如崩溃,断言(assertion)失败,以发现可能的程序错误,比如内存泄漏。模糊测试常常用于检测软件或计算机系统的安全漏洞。
模糊测试最早由威斯康星大学的Barton Miller于1988年提出。他们的工作不仅使用随机无结构的测试数据,还系统的利用了一系列的工具去分析不同平台上的各种软件,并对测试发现的错误进行了系统的分析。此外,他们还公开了源代码,测试流程以及原始结果数据。
模糊测试工具主要分为两类,变异测试(mutation-based)以及生成测试(generation-based)。模糊测试可以被用作白盒,灰盒或黑盒测试。文件格式与网络协议是最常见的测试目标,但任何程序输入都可以作为测试对象。常见的输入有环境变量,鼠标和键盘事件以及API调用序列。甚至一些通常不被考虑成输入的对象也可以被测试,比如数据库中的数据或共享内存。
对于安全相关的测试,那些跨越可信边界的数据是最令人感兴趣的。比如,模糊测试那些处理任意用户上传的文件的代码比测试解析服务器配置文件的代码更重要。因为服务器配置文件往往只能被有一定权限的用户修改。
AFL
AFL是fuzzing使用率较高的工具,全称是American Fuzzy Lop,由Google安全工程师Michał Zalewski开发的一款开源fuzzing测试工具,可以高效地对二进制程序进行fuzzing,挖掘可能存在的内存安全漏洞,如栈溢出、堆溢出、UAF、double free等。由于需要在相关代码处插桩,因此AFL主要用于对开源软件进行测试。配合QEMU等工具,也可对闭源二进制代码进行fuzzing,但执行效率会受到影响。AFL基于覆盖引导(Coverage-guided),它通过记录输入样本的代码覆盖率,从而调整输入样本以提高覆盖率,增加发现漏洞的概率。
AFL对开源代码进行fuzzing的过程可以用以下五步描述:
- 从源码编译程序时进行插桩,以记录代码覆盖率(Code Coverage)
- 选择一些输入文件,作为初始测试集加入输入队列(queue)
- 将队列中的文件按一定的策略进行 “突变”
- 如果经过变异文件更新了覆盖范围,则将其保留添加到队列中
- 上述过程会一直循环进行,期间触发了crash的文件会被记录下来
AFLplusplus
AFL++ 是 AFL 的高级分支——更快的速度、更多更好的突变、更多更好的工具、自定义模块支持等。
AudioFile
一个简单的头文件 C++ 库,用于读写音频文件。这是我fuzzing的目标。
FUZZING
安装
最简单的方式就是使用docker
docker pull aflplusplus/aflplusplus
docker run -ti -v /location/of/your/target:/src aflplusplus/aflplusplus
准备被测试源码
根据官方提供的示例代码略做修改做为测试代码。
1 |
|
我开启了AFL的Persistent Mode,开启方法是在调用函数前加上下面的代码。
while (__AFL_LOOP(10000)) {}
Persistent Mode消除重复 fork() 调用和相关操作系统开销的需要,因为有些函数只要简单重置状态值就可以处理不同的输入文件,这样一个进程就可以一直执行。在迭代了10000次后,AFL会重新启动进程,这最大限度地降低了内存泄漏和类似错误的影响。
插桩编译
首先运行docker镜像
docker run -ti -v /location/of/your/target:/src aflplusplus/aflplusplus
路径是源代码所在目录,这里需要填入绝对路径。
准备命令
export AFL_USE_ASAN=1
表示编译器开启 -fsanitize=address 进行编译sudo afl-system-config
这会重新配置系统以获得最佳Fuzzing速度编译
AudioFile原本是用cmake生成Makefile进行编译,但我运行的时候编译不成功。所以改为直接编写Makefile编译,将头文件和源文件放在同一目录下。
make CXX=afl-clang-fast++
1 | objects :=examples.o |
处理种子语料库
输入文件选择AudioFile中原有的示例音频文件。
afl-cmin -i in/ -o in_cmin -m none -- ./first_try @@
使用 afl++ 工具 afl-cmin 从语料库中删除不会在目标中生成新路径的输入。
@@
表示是文件输入,如果是标准输入去除@@
即可。
开始测试
afl-fuzz -i in_cmin/ -o out -m none -- ./first_try @@
处理crashes
会产生crash的文件放在out/default/crashes中,编译测试crashes的程序,去除掉源代码中的AFL_LOOP
g++ -g -fsanitize=address -o asantry examples.cpp AudioFile.h
之后逐个输入crash,查看结果。
第一个heap buff overflowin位于AudioFile.h:502,decodeWaveFile()函数
1
./asantry ./out/default/crashes/id\:000005\,sig\:06\,src\:000006\,time\:84641\,op\:havoc\,rep\:2
asan结果:
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**********************
Running Example: Load Audio File and Print Summary
**********************
=================================================================
==23==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000738 at pc 0x55da0cb245e9 bp 0x7ffc6e244e90 sp 0x7ffc6e244e80
READ of size 1 at 0x602000000738 thread T0
#0 0x55da0cb245e8 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy_chars<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(char*, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >) /usr/include/c++/10/bits/basic_string.h:379
#1 0x55da0cb226a7 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::forward_iterator_tag) /usr/include/c++/10/bits/basic_string.tcc:225
#2 0x55da0cb1fba7 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct_aux<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::__false_type) /usr/include/c++/10/bits/basic_string.h:247
#3 0x55da0cb1cfd5 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >) /usr/include/c++/10/bits/basic_string.h:266
#4 0x55da0cb19e45 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, void>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::allocator<char> const&) /usr/include/c++/10/bits/basic_string.h:628
#5 0x55da0cb11fcd in AudioFile<float>::decodeWaveFile(std::vector<unsigned char, std::allocator<unsigned char> >&) /src/AudioFile.h:502
#6 0x55da0cb0d359 in AudioFile<float>::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) /src/AudioFile.h:481
#7 0x55da0cb0554d in examples::loadAudioFileAndPrintSummary(char*) /src/examples.cpp:95
#8 0x55da0cb04d0e in main /src/examples.cpp:26
#9 0x7fede8fdb0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#10 0x55da0cb04c0d in _start (/src/asantry+0x4c0d)
0x602000000738 is located 2 bytes to the right of 6-byte region [0x602000000730,0x602000000736)
allocated by thread T0 here:
#0 0x7fede95a2f17 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.6+0xb1f17)
#1 0x55da0cb1da08 in __gnu_cxx::new_allocator<unsigned char>::allocate(unsigned long, void const*) /usr/include/c++/10/ext/new_allocator.h:115
#2 0x55da0cb1ac79 in std::allocator_traits<std::allocator<unsigned char> >::allocate(std::allocator<unsigned char>&, unsigned long) /usr/include/c++/10/bits/alloc_traits.h:460
#3 0x55da0cb16819 in std::_Vector_base<unsigned char, std::allocator<unsigned char> >::_M_allocate(unsigned long) /usr/include/c++/10/bits/stl_vector.h:346
#4 0x55da0cb195b6 in std::vector<unsigned char, std::allocator<unsigned char> >::_M_default_append(unsigned long) /usr/include/c++/10/bits/vector.tcc:635
#5 0x55da0cb11896 in std::vector<unsigned char, std::allocator<unsigned char> >::resize(unsigned long) /usr/include/c++/10/bits/stl_vector.h:940
#6 0x55da0cb0d192 in AudioFile<float>::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) /src/AudioFile.h:465
#7 0x55da0cb0554d in examples::loadAudioFileAndPrintSummary(char*) /src/examples.cpp:95
#8 0x55da0cb04d0e in main /src/examples.cpp:26
#9 0x7fede8fdb0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
SUMMARY: AddressSanitizer: heap-buffer-overflow /usr/include/c++/10/bits/basic_string.h:379 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy_chars<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(char*, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >)
Shadow bytes around the buggy address:
0x0c047fff8090: fa fa fd fd fa fa fd fd fa fa 00 02 fa fa 00 02
0x0c047fff80a0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
0x0c047fff80b0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
0x0c047fff80c0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
0x0c047fff80d0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
=>0x0c047fff80e0: fa fa 00 02 fa fa 06[fa]fa fa fa fa fa fa fa fa
0x0c047fff80f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==23==ABORTING第二个heap buff overflowin位于AudioFile.h:1148,determineAudioFileFormat()函数
1
./asantry ./out/default/crashes/id\:000000\,sig\:06\,src\:000006\,time\:291\,op\:havoc\,rep\:16
asan结果:
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**********************
Running Example: Load Audio File and Print Summary
**********************
=================================================================
==13==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000731 at pc 0x561c434c55e9 bp 0x7ffeb0ea5a50 sp 0x7ffeb0ea5a40
READ of size 1 at 0x602000000731 thread T0
#0 0x561c434c55e8 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy_chars<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(char*, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >) /usr/include/c++/10/bits/basic_string.h:379
#1 0x561c434c36a7 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::forward_iterator_tag) /usr/include/c++/10/bits/basic_string.tcc:225
#2 0x561c434c0ba7 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct_aux<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::__false_type) /usr/include/c++/10/bits/basic_string.h:247
#3 0x561c434bdfd5 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >) /usr/include/c++/10/bits/basic_string.h:266
#4 0x561c434bae45 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, void>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::allocator<char> const&) /usr/include/c++/10/bits/basic_string.h:628
#5 0x561c434b2a75 in AudioFile<float>::determineAudioFileFormat(std::vector<unsigned char, std::allocator<unsigned char> >&) /src/AudioFile.h:1148
#6 0x561c434ae2ee in AudioFile<float>::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) /src/AudioFile.h:477
#7 0x561c434a654d in examples::loadAudioFileAndPrintSummary(char*) /src/examples.cpp:95
#8 0x561c434a5d0e in main /src/examples.cpp:26
#9 0x7f99260b30b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#10 0x561c434a5c0d in _start (/src/asantry+0x4c0d)
0x602000000731 is located 0 bytes to the right of 1-byte region [0x602000000730,0x602000000731)
allocated by thread T0 here:
#0 0x7f992667af17 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.6+0xb1f17)
#1 0x561c434bea08 in __gnu_cxx::new_allocator<unsigned char>::allocate(unsigned long, void const*) /usr/include/c++/10/ext/new_allocator.h:115
#2 0x561c434bbc79 in std::allocator_traits<std::allocator<unsigned char> >::allocate(std::allocator<unsigned char>&, unsigned long) /usr/include/c++/10/bits/alloc_traits.h:460
#3 0x561c434b7819 in std::_Vector_base<unsigned char, std::allocator<unsigned char> >::_M_allocate(unsigned long) /usr/include/c++/10/bits/stl_vector.h:346
#4 0x561c434ba5b6 in std::vector<unsigned char, std::allocator<unsigned char> >::_M_default_append(unsigned long) /usr/include/c++/10/bits/vector.tcc:635
#5 0x561c434b2896 in std::vector<unsigned char, std::allocator<unsigned char> >::resize(unsigned long) /usr/include/c++/10/bits/stl_vector.h:940
#6 0x561c434ae192 in AudioFile<float>::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) /src/AudioFile.h:465
#7 0x561c434a654d in examples::loadAudioFileAndPrintSummary(char*) /src/examples.cpp:95
#8 0x561c434a5d0e in main /src/examples.cpp:26
#9 0x7f99260b30b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
SUMMARY: AddressSanitizer: heap-buffer-overflow /usr/include/c++/10/bits/basic_string.h:379 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy_chars<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > >(char*, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >)
Shadow bytes around the buggy address:
0x0c047fff8090: fa fa fd fd fa fa fd fd fa fa 00 02 fa fa 00 02
0x0c047fff80a0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
0x0c047fff80b0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
0x0c047fff80c0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
0x0c047fff80d0: fa fa 00 02 fa fa 00 02 fa fa 00 02 fa fa 00 02
=>0x0c047fff80e0: fa fa 00 02 fa fa[01]fa fa fa fa fa fa fa fa fa
0x0c047fff80f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c047fff8130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==13==ABORTING