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