Working with libFuzzer
21 April, 2020
Libfuzzer is a coverage-guided, evolutionary, inprocess fuzzing engine. Okay lets break it down.
-
Coverage Guided Fuzz target instrumented to get code coverage reached by each fuzz input. With this info the fuzzer then decides which part of input to mutate to maximize coverage.
-
Evolutionary Fuzzing Using the program's feedback from the test input to learn the input formate overtime. This works basing onn algorithms similar to genetic algorithms and requires instrumentation.
-
In Process We mutate inputs directly in memory instead of launching a new process for evert input.
Libfuzzer makes it really feasible to fuzz individual components of a program. Meaning that we don't need to craft a well formed payload and launch huge binaries every run. We can just simply create a fuzzer by just importing all the necessary libraries and calling that one function inside the main fuzzing function.
Example
Say we want to fuzz a function fuzz_me which is a part of a large program and a good fuzz target(more on that later). We just need to write this.
#include <stdint.h>
#include <stddef.h>
// We include all necessary headers for the FuzzMe function to work.
bool FuzzMe(const uint8_t *Data, size_t DataSize) {
return DataSize >= 3 &&
Data[0] == 'F' &&
Data[1] == 'U' &&
Data[2] == 'Z' &&
Data[3] == 'Z'; // :‑<
}
// Now FuzzMe function is written in such a way that it uses the functions
// that we want to fuzz.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
FuzzMe(Data, Size);
return 0;
}
Now compile it using the required instrumentation with clang.
clang++ -g -fsanitize=address,fuzzer fuzz_me.cc
-fsanitize = fuzzer [Links with libFuzzer runtime] -fsanitize = fuzzer, address [enables Address Sanitizer] -fsanitize = fuzzer, signed-integer-overflow [enables UBSAN] -fsanitize = fuzzer, memory [enables MSAN]
Now lets run the binary.
silv3r@meltd0wn ~/libFuzzer> ./a.out
INFO: Seed: 3197843689
INFO: Loaded 1 modules (7 inline 8-bit counters): 7 [0x7a6e80, 0x7a6e87),
INFO: Loaded 1 PC tables (7 PCs): 7 [0x56f7c0,0x56f830),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 3 ft: 4 corp: 1/1b exec/s: 0 rss: 27Mb
#5 NEW cov: 4 ft: 5 corp: 2/4b lim: 4 exec/s: 0 rss: 27Mb L: 3/3 MS: 3 InsertByte-ChangeByte-InsertByte-
#1435 NEW cov: 5 ft: 6 corp: 3/19b lim: 17 exec/s: 0 rss: 27Mb L: 15/15 MS: 5 EraseBytes-EraseBytes-ShuffleBytes-InsertRepeatedBytes-CMP- DE: "F\x00\x00\x00"-
#1438 REDUCE cov: 5 ft: 6 corp: 3/14b lim: 17 exec/s: 0 rss: 27Mb L: 10/10 MS: 3 ChangeBit-PersAutoDict-EraseBytes- DE: "F\x00\x00\x00"-
#1439 REDUCE cov: 5 ft: 6 corp: 3/12b lim: 17 exec/s: 0 rss: 27Mb L: 8/8 MS: 1 EraseBytes-
#1470 REDUCE cov: 5 ft: 6 corp: 3/11b lim: 17 exec/s: 0 rss: 27Mb L: 7/7 MS: 1 EraseBytes-
#1526 REDUCE cov: 5 ft: 6 corp: 3/10b lim: 17 exec/s: 0 rss: 27Mb L: 6/6 MS: 1 EraseBytes-
#1623 REDUCE cov: 5 ft: 6 corp: 3/8b lim: 17 exec/s: 0 rss: 27Mb L: 4/4 MS: 2 ChangeBinInt-EraseBytes-
#1680 REDUCE cov: 5 ft: 6 corp: 3/7b lim: 17 exec/s: 0 rss: 27Mb L: 3/3 MS: 2 ChangeBit-EraseBytes-
#6397 REDUCE cov: 6 ft: 7 corp: 4/31b lim: 63 exec/s: 0 rss: 27Mb L: 24/24 MS: 2 InsertRepeatedBytes-ChangeBit-
#6404 REDUCE cov: 6 ft: 7 corp: 4/22b lim: 63 exec/s: 0 rss: 27Mb L: 15/15 MS: 2 CMP-EraseBytes- DE: ",\x00\x00\x00"-
#6477 REDUCE cov: 6 ft: 7 corp: 4/15b lim: 63 exec/s: 0 rss: 27Mb L: 8/8 MS: 3 CopyPart-ChangeBit-EraseBytes-
#6483 REDUCE cov: 6 ft: 7 corp: 4/14b lim: 63 exec/s: 0 rss: 27Mb L: 7/7 MS: 1 EraseBytes-
#6570 REDUCE cov: 6 ft: 7 corp: 4/12b lim: 63 exec/s: 0 rss: 27Mb L: 5/5 MS: 2 ChangeBit-EraseBytes-
#6658 REDUCE cov: 6 ft: 7 corp: 4/10b lim: 63 exec/s: 0 rss: 27Mb L: 3/3 MS: 3 ChangeByte-ChangeByte-EraseBytes-
#11580 REDUCE cov: 7 ft: 8 corp: 5/14b lim: 110 exec/s: 0 rss: 28Mb L: 4/4 MS: 2 InsertByte-ChangeBit-
=================================================================
==17776==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000414f3 at pc 0x000000551313 bp 0x7ffec3b75390 sp 0x7ffec3b75388
READ of size 1 at 0x6020000414f3 thread T0
#0 0x551312 in FuzzMe(unsigned char const*, unsigned long) /home/silv3r/fuzzing/tutorial/libFuzzer/fuzz_me.cc:9:7
#1 0x5513b4 in LLVMFuzzerTestOneInput /home/silv3r/fuzzing/tutorial/libFuzzer/fuzz_me.cc:13:3
#2 0x459411 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:553:15
#3 0x458c55 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:469:3
#4 0x45aef7 in fuzzer::Fuzzer::MutateAndTestOne() /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:695:19
#5 0x45bc15 in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:831:5
#6 0x4499d8 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:825:6
#7 0x472e42 in main /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19:10
#8 0x7fd32099cb96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
#9 0x41db69 in _start (/home/silv3r/fuzzing/tutorial/libFuzzer/a.out+0x41db69)
0x6020000414f3 is located 0 bytes to the right of 3-byte region [0x6020000414f0,0x6020000414f3)
allocated by thread T0 here:
#0 0x51f08d in malloc /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:145:3
#1 0x432247 in operator new(unsigned long) (/home/silv3r/fuzzing/tutorial/libFuzzer/a.out+0x432247)
#2 0x458c55 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:469:3
#3 0x45aef7 in fuzzer::Fuzzer::MutateAndTestOne() /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:695:19
#4 0x45bc15 in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:831:5
#5 0x4499d8 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:825:6
#6 0x472e42 in main /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19:10
#7 0x7fd32099cb96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/silv3r/fuzzing/tutorial/libFuzzer/fuzz_me.cc:9:7 in FuzzMe(unsigned char const*, unsigned long)
Shadow bytes around the buggy address:
0x0c0480000240: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
0x0c0480000250: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
0x0c0480000260: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa
0x0c0480000270: fa fa fd fa fa fa fd fa fa fa fd fd fa fa fd fd
0x0c0480000280: fa fa fd fd fa fa fd fd fa fa fd fa fa fa fd fa
=>0x0c0480000290: fa fa fd fa fa fa fd fa fa fa fd fa fa fa[03]fa
0x0c04800002a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c04800002b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c04800002c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c04800002d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c04800002e0: 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
==17776==ABORTING
MS: 1 EraseBytes-; base unit: 095aa46062dcd6efcd6534616616fce2a45abf67
0x46,0x55,0x5a,
FUZ
artifact_prefix='./'; Test unit written to ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60
Base64: RlVa
It should exit quickly throwing out this huge report finding a buffer overflow in FuzzMe function. Easy isn't it?(Trust me..Not always.)