Ten wpis jest częścią cyklu “Od zera do bughuntera” – listę wszystkich postów znajdziesz tutaj.
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”
Comments are closed.