← Home

Working with libFuzzer

21 April, 2020

Libfuzzer is a coverage-guided, evolutionary, inprocess fuzzing engine. Okay lets break it down.

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;
}

source

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.)