LLVM LibFuzzer – Fast track #1

Słowo wstępu

Dużo ostatnio było o podatnościach – straciłem rachubę ile było to postów pod rząd 😉

W tym poście chciałbym przybliżyć bardzo ciekawy projekt LLVM LibFuzzer, w którym poziom testowania przeniesiony jest na poziom funkcji a nie całych binarek. Oczywiście generuje to pewne problemy, jak i również ma wiele zalet – ale o tym za chwilę.

Jako rekomendację dodam, że LLVM LibFuzzer używany jest przez Google’a do testowania Chromium oraz innych projektów w ramach ClusterFuzz oraz OSS-Fuzz.

O co w ogóle chodzi?

Generalnie o to aby było szybko i efektywnie – LibFuzzer wykonuje cały proces fuzzowania w pamięci, bez dotykania HDD (chyba, że znajdzie crasha 😉 ). Dodatkowo wykorzystuje AddressSanitizer, MemorySanitizer oraz UndefinedBehaviorSanitizer, co pozwala wykrywać różne problemy z pamięcią programu, nawet jeżeli dostarczone dane nie spowodują całkowitej awarii w normalnej wersji programu (np. błędy typu out of bounds read).

Czym różni się to od persistent mode w American Fuzzy Lop?

Generalnie niczym – poza tym, że musimy sobie sami napisać fuzzer tego co chcemy testować (bez logiki “mieszania” danych wejściowych). W tym miejscu rada za 100 punktów: zwalniajmy pamięć w naszych fuzzerach – bardzo szybko jesteśmy w stanie wysycić cały dostępny RAM (mój osobisty rekord to 30 GB; domyślne ograniczenie to 2GB 😉 )

Całość wygląda jak na załączonym poniżej listingu (dla projektu Yara) – “mięso” zaczyna się w 19. linijce:

#include <stdlib.h>
#include <string>
#include <yara.h>

typedef struct COMPILER_RESULTS
{
  int errors;
  int warnings;
} COMPILER_RESULTS;

int callback(int message, void* message_data, void* user_data)
{
   return 0;
}

std::string file_path = "/usr/bin/strings";
int timeout = 1000000;

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    COMPILER_RESULTS cr;
    YR_COMPILER* compiler = NULL;
    YR_RULES* rules = NULL;
    std::string s(reinterpret_cast<const char *>(Data), Size);
    
    yr_initialize();
    
    if (yr_compiler_create(&compiler) != ERROR_SUCCESS){
	return 0;
    }
    
    if (yr_compiler_add_string(compiler, s.c_str(), NULL) == 0)
    {
        if (yr_compiler_get_rules(compiler, &rules) == ERROR_SUCCESS)
        {

	    yr_rules_scan_file(rules,
		               file_path.c_str(),
		               0,
		               callback,
		               NULL,
		               timeout);

        }
    }
  
    if(compiler != NULL) {
    	yr_compiler_destroy(compiler);  
    }
    
    if(rules != NULL) {
    	yr_rules_destroy(rules);
    }
    
    yr_finalize();
    return 0;
}

Dużo przykładów gotowych fuzzerów dla projektów open-source można znaleźć tutaj i tutaj.

A tutaj pełen tutorial w przypadku, kiedy mamy już pomysły na własne fuzzery 🙂

Chcę to mieć, jak to zrobić?

Jedynym wymaganiem jest skomplilowanie testowanego projektu wraz Clangiem 5.0 i odpowiednimi flagami. Działać będziemy na wcześniej wspomnianym projekcie Yara.

Pobieramy najnowszego Clanga (skrypt tutaj):

mkdir TMP_CLANG
cd TMP_CLANG
git clone https://chromium.googlesource.com/chromium/src/tools/clang
cd ..
TMP_CLANG/clang/scripts/update.py
sudo cp -rf  third_party/llvm-build/Release+Asserts/lib/clang /usr/local/lib/
sudo cp -rf third_party/llvm-build/Release+Asserts/bin/* /usr/local/bin

Ściągamy najnowszą wersję LibFuzzera:

svn co http://llvm.org/svn/llvm-project/llvm/trunk/lib/Fuzzer 
./Fuzzer/build.sh

Klonujemy repozytorium Yary i kompilujemy ją z odpowiednimi flagami:

git clone https://github.com/VirusTotal/yara
cd yara
./bootstrap.sh
CC=clang -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div ./configure
make

W tym miejscu na chwilę przerwiemy działanie w trybie copy & paste i wyjaśnię do czego służą w/w flagi Clanga:

  • -fno-omit-frame-pointer – “ładne” stack-trace’y
  • -fsanitize=address – włączenie AddressSanitizera
  • -fsanitize-coverage – włączenie obsługi badania pokrycia kodu (SanitizerCoverage) – drugi post będzie całkowicie poświęcony temu zagadnieniu
    • trace-pc-guard – podstawowa metoda badania code coverage’u dla fuzzera, brak tej opcji powoduje niemożność korzystania z LibFuzzera
    • trace-cmp – śledzenie zmian flow programu podczas instrukcji porównania oraz switch
    • trace-div – śledzenie argumentów dzielenia liczb całkowitych
    • trace-gep – śledzenie indeksów w buforach (dla dociekliwych czytelników)

Klonujemy repozytorium z gotowym korpusem testowym dla Yary:

git clone https://github.com/fumfel/yara-fuzzing-corpus

Kompilujemy fuzzer z wcześniejszej części posta (zwracam kolejny raz uwagę na flagi kompilacji):

clang++ -std=c++11 yara_fuzzer.cc -Iyara/libyara/include/ ./yara/libyara/.libs/libyara.a ./libFuzzer.a -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div -lcrypto -lssl -o yara_fuzzer

Minimalizujemy korpus (przełącznik -merge=1) – nie wszystkie przypadki testowe są użyteczne:

» ./yara_fuzzer -merge=1  yara-fuzzing-corpus-min/ yara-fuzzing-corpus/
INFO: Seed: 1335465087
INFO: Loaded 1 modules (33 guards): [0x86adf0, 0x86ae74), 
INFO: -max_len is not provided, using 1048576
MERGE-OUTER: 1114 files, 0 in the initial corpus
MERGE-OUTER: attempt 1
INFO: Seed: 1356956865
INFO: Loaded 1 modules (33 guards): [0x86adf0, 0x86ae74), 
INFO: -max_len is not provided, using 1048576
MERGE-INNER: using the control file '/tmp/libFuzzerTemp.4178.txt'
MERGE-INNER: 1114 total files; 0 processed earlier; will process 1114 files now
#1	pulse  cov: 13 exec/s: 0 rss: 33Mb
#2	pulse  cov: 13 exec/s: 0 rss: 33Mb
#4	pulse  cov: 13 exec/s: 0 rss: 34Mb
#8	pulse  cov: 13 exec/s: 0 rss: 36Mb
#16	pulse  cov: 13 exec/s: 0 rss: 40Mb
#32	pulse  cov: 21 exec/s: 0 rss: 47Mb
#64	pulse  cov: 21 exec/s: 0 rss: 63Mb
#128	pulse  cov: 21 exec/s: 0 rss: 92Mb
#256	pulse  cov: 21 exec/s: 0 rss: 151Mb
#256	pulse  cov: 21 exec/s: 128 rss: 311Mb
#512	pulse  cov: 21 exec/s: 256 rss: 311Mb
#1024	pulse  cov: 21 exec/s: 381 rss: 311Mb
MERGE-OUTER: the control file has 139186 bytes
MERGE-OUTER: consumed 0Mb (31Mb rss) to parse the control file
MERGE-OUTER: 6 new files with 23 new features added

Jak widać wstępny korpus generalnie jest słaby (niska wartość parametru pokrycia kodu “cov” – 23), lecz nie przejmujemy się tym i odpalamy fuzzer 🙂

Parametry:

  • -detect_leaks=0 – nie “łapiemy” wycieków pamięci
  • -max_total_time=60 – czas działania fuzzera w sekundach
  • -print_final_stats=1 – wyświetlenie podumowania pracy
» ./yara_fuzzer -detect_leaks=0 -max_total_time=60 -print_final_stats=1 yara-fuzzing-corpus-min/
INFO: Seed: 3862824616
INFO: Loaded 1 modules (33 guards): [0x86adf0, 0x86ae74), 
Loading corpus dir: yara-fuzzing-corpus-min/
INFO: -max_len is not provided, using 220
#0	READ units: 7
#7	INITED cov: 23 ft: 24 corp: 7/382b exec/s: 0 rss: 38Mb
#8192	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2730 rss: 184Mb
#16384	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2730 rss: 192Mb
#32768	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2520 rss: 202Mb
#65536	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2340 rss: 210Mb
#131072	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2299 rss: 213Mb
#137987	DONE   cov: 23 ft: 24 corp: 7/382b exec/s: 2262 rss: 216Mb
Done 137987 runs in 61 second(s)
stat::number_of_executed_units: 137987
stat::average_exec_per_sec:     2262
stat::new_units_added:          0
stat::slowest_unit_time_sec:    0
stat::peak_rss_mb:              216

To by było na tyle w tej części – umiemy już odpalać fuzzery oraz wiemy jak wyglądają.

W kolejnej odsłonie fast tracka przedstawię badanie pokrycia kodu za pomocą SanitizerCoverage oraz tuning efektywności fuzzera 🙂

One thought on “LLVM LibFuzzer – Fast track #1

Leave a Reply

Your email address will not be published. Required fields are marked *