LLVM LibFuzzer – Fast track #2

Słowo wstępu

Kolejna część szybkiego wprowadzenia do zagadnień związanych z LibFuzzerem – w tym poście skupię się na nieco “głębszym” wykorzystaniu wbudowanych funkcjonalności, tunigu wydajności fuzzera oraz pokryciu kodu za pomocą SanitizerCoverage.

Bez zbędnego rozwlekania, przechodzimy do konkretów 🙂

Jak robić to lepiej?

W moim rozumieniu: lepiej = większy code coverage || więcej execs/s.. Generalnie w przypadku fuzzerów zawsze należy pamiętać o prawidłowości: więcej execs/s -> większy korpus -> większy code coverage -> więcej potencjalnych crashy.

Autorzy LibFuzzera jako minimum przyjmują 1000 execs/s dla efektywnego fuzzingu.

Słowniki

Słowniki znacząco przyczyniają się do większego pokrycia kodu – fuzzujemy po prostu mądrze, nie tracąc cykli CPU na generowanie bezwartościowego inputu.

Pliki słowników wykorzystywane przez LibFuzzer są kompatybilne z AFL, więc możemy skorzystać z dostarczonych razem z tym fuzzerem. Jeżeli nie znajdziemy tam słownika kompatybilnego z naszym programem, warto poświęcić chwilę i napisać go samemu – ROI jest na prawdę duże 🙂

Dobre słowniki można znaleźć w następujących miejscach:

Leniwi czytelnicy mogą skorzystać z gotowego skryptu pozwalającego na wygenerowanie słownika na podstawie dostarczonej specyfikacji.

Rozmiar korpusu testowego

W większości przypadków im mniejsze pliki tym lepiej (aczkolwiek nie polecam obsesyjnie się tego trzymać). Nie ma sensu fuzzować biblioteki regex za pomocą przypadków testowych o rozmiarze 100KB i więcej. Polecam przetestować empirycznie własny fuzzer pod kątem wielkości pojedyńczego przypadku testowego – przełącznik “-max_len“.

Osobiście, po kilku iteracjach fuzzingu danego projektu, dzielę korpus testowy względem rozmiaru – pliki mniejsze niż 1, 2, 4… KB i sprawdzam jak wygląda pokrycie kodu dla danego rozmiaru. Następnie idę na rozsądny kompromis (przynajmniej tak mi się wydaje) pomiędzy pokryciem a rozmiarem 😉

Im wyższą wartość niepowodującą degradacji szybkości uda się nam znaleźć, tym otrzymamy lepsze pokrycie kodu.

Kod fuzzera

Tutaj pomoże ortodoksyjne trzymanie się zasady KISS (albo po polsku DUPA) 😉

Parę prostych wskazówek:

  • Nie inicjalizujmy za każdym razem (jeżeli nie musimy) mechanizmów fuzzowanej biblioteki
  • Korzystajmy ze stosu zamiast z alokacji na heapie – jest dużo szybszy. Dla przykładu wywołanie funkcji memstet() dla 1 MB zaalkokowanego na heapie to 5x wolniejsza operacja, od takiej samej, tylko, że na stosie
  • Zwalniajmy wszystkie zasoby, które są wykorzystywane podczas iteracji fuzzera. W przeciwnym razie pamięć RAM zostanie “zeżarta” na naszych oczach

Bardzo fajny przykład optymalizacji został przedstawiony na slajdach jednego z modułów warsztatu LibFuzzer Workshopzachęcam do zapoznania się z całym materiałem 🙂

Badanie pokrycia kodu

Przed uruchomieniem fuzzera warto sprawdzić w jakim stopniu nasz korpus testowy pokrywa badany przez nas kod. Niestety do tego będzie potrzebna ponowna kompilacja fuzzera, z innymi przełącznikami (tutaj na przykładzie fuzzera pcre2):


clang++ -std=c++11 pcre2_fuzzer.cc -I pcre/src -Wl,--whole-archive pcre/.libs/*.a -Wl,-no-whole-archive ~/Desktop/libFuzzer.a -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=edge,indirect-calls,8bit-counters,trace-cmp,trace-div,trace-gep -o pcre2_fuzzer_coverage

Uwaga! Przełącznik 8bit-counters nie będzie wspierany. W przypadku chęci wykorzystania innych metod badania pokrycia warto wcześniej rzucić okiem na dokumentację SanCova (sporo metod pozostanie bez wsparcia i nie będą rozwijane).

Następnie odpalamy skompilowaną binarkę:

ASAN_OPTIONS=coverage=1 ./pcre2_fuzzer_coverage corpus_dir/ -runs=0

INFO: Seed: 3765786233
INFO: Loaded 0 modules (0 guards):
Loading corpus dir: regex_min/
Loaded 1024/8815 files from regex_min/
Loaded 2048/8815 files from regex_min/
Loaded 4096/8815 files from regex_min/
Loaded 8192/8815 files from regex_min/
INFO: -max_len is not provided, using 2035
#0    READ units: 8814
#2048    pulse  exec/s: 409 rss: 132Mb
#4096    pulse  exec/s: 178 rss: 143Mb
Slowest unit: 16 s:
artifact_prefix='./'; Test unit written to ./slow-unit-2c9a2da85c6b915818510370f6ff6680c8515d94
Slowest unit: 18 s:
artifact_prefix='./'; Test unit written to ./slow-unit-53e82f19e2337033fcc43d1c9da61da8e5ff90af
#8192    pulse  exec/s: 38 rss: 232Mb
#8814    INITED exec/s: 36 rss: 243Mb
ERROR: no interesting inputs were found. Is the code instrumented for coverage? Exiting.

Nie przejmujemy się komunikatem i dodajemy symbole do pliku .sancov :

sancov -symbolize pcre2_fuzzer_coverage pcre2_fuzzer_cov.18822.sancov > pcre2.symcov

Kolejnym krokiem jest odpalenie raportu w HTMLu dotyczący pokrycia kodu dla poszczególnych plików testowanego projektu:

python coverage-report-server.py --symcov pcre2.symcov --srcpath .

Efekt końcowy:

Wielowątkowość

Kiedy mamy już dobre pokrycie i szybki fuzzer warto odpalić go w kilku instancjach aby wycisnąć ostatnie soki i z fuzzera i z CPU 🙂

Libfuzzer ma, przynajmniej dla mnie, niecodzienny model zarządzania kilkoma instancjami. Składa się z dwóch części:

  • worker – przełącznik -workers, liczba równoległych instancji fuzzera – najczęściej taka sama jak liczba logicznych procesorów. Domyślnie jest to liczba logicznych CPU / 2
  • job – przełącznik -jobs, job jest rozumiany jako proces do znalezienia inputu powodującego awarię (crash, hang, brak pamięci, wyciek pamięci)

Uruchamiając fuzzer w poniższy sposób:

./myfuzzer -workers=4 -jobs=4 corpus_dir/

Znalezienie crasha spowoduje zabicie jednej instancji fuzzera i fuzzing na trzech lub mniejszej liczbie procesorów (w zależności od liczby znalezionych crashy). Czasami może okazać się, że nasze fuzzery przestały całkowicie pracować – dlatego polecam uruchamianie z dużo większą liczbą jobów:

./myfuzzer -workers=4 -jobs=4000 corpus_dir/

Spowoduje to “ciągły” fuzzing, tak jak w przypadku AFLa, który, moim zdaniem, w tej kwestii dużo lepiej podchodzi do problemu.

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 🙂

[CVE-2017-6181] Ruby 2.4.0 – Stack Overflow

Słowo wstępu

Chwila odpoczynku od projektów wykorzystywanych w branży bezpieczeństwa – dzisiejszym targetem jest interpreter języka Ruby, podstawy popularnego frameworka webowego Ruby on Rails – obecnego w kodzie takich serwisów jak GitHub, BaseCamp, AirBnb czy Twitch.

Krótki opis podatności

Błąd znajdował się w wersji 2.4.0, a więc na dzień pisania posta ostatniej stabilnej (Git HEAD: fbd5cda6aad6db01bbca3d893a9970314a1bd52c).

Funkcja parsująca wyrażenie regularne, przed sprawdzeniem głębokości wyrażenia regularnego, nie inicjalizowała zwracanej wartości, tylko tuż za nim (sprawdzeniem). Powodowało to przy odpowiednio “dużym” wyrażeniu regularnym za dużą alokację ilości ramek stosu dla rekurencyjnie wywoływanej funkcji i w konsekwencji jego przepełnienie.

Naprawa tego błędu była bardzo prosta i polegała na przeniesieniu linijki odpowiedzialnej za inicjalizację zmiennej o 4 linie w górę.

Poniżej podatna funkcja z zaznaczonym błędem:

static int
parse_char_class(Node** np, Node** asc_np, OnigToken* tok, UChar** src, UChar* end,
		 ScanEnv* env)
{
  int r, neg, len, fetched, and_start;
  OnigCodePoint v, vs;
  UChar *p;
  Node* node;
  Node* asc_node;
  CClassNode *cc, *prev_cc;
  CClassNode *asc_cc, *asc_prev_cc;
  CClassNode work_cc, asc_work_cc;

  enum CCSTATE state;
  enum CCVALTYPE val_type, in_type;
  int val_israw, in_israw;
 
  env->parse_depth++;
  if (env->parse_depth > ParseDepthLimit)
    return ONIGERR_PARSE_DEPTH_LIMIT_OVER;
  prev_cc = asc_prev_cc = (CClassNode* )NULL;
  *np = *asc_np = NULL_NODE;
  r = fetch_token_in_cc(tok, src, end, env);
  if (r == TK_CHAR && tok->u.c == '^' && tok->escaped == 0) {
    neg = 1;
    r = fetch_token_in_cc(tok, src, end, env);
  }
  else {
    neg = 0;
}

Payload triggerujący tą lukę był bardzo prosty – 4096 razy ‘[‘ w wyrażeniu regularnym Ruby.

Log błędu:


[BUG] Segmentation fault at 0x00006e0000005b
ruby 2.5.0dev (2017-02-18 trunk 57652) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0001 p:0000 s:0003 E:000c70 (none) [FINISH]


-- Machine register context ------------------------------------------------
RIP: 0x0000562fdcb53209 RBP: 0x0000562fdcf929b0 RSP: 0x00007ffea4154b40
RAX: 0x0000000000002431 RBX: 0x0000000000000000 RCX: 0x0000562fdcfa32d0
RDX: 0x00007ffea4294c30 RDI: 0x0000006e0000005b RSI: 0x00007ffea4154c10
R8: 0x0000562fddd7422a  R9: 0x0000562fdcf929b0 R10: 0x00007f977f639ba8
R11: 0x0000000000000001 R12: 0x0000562fdcf929b0 R13: 0x0000006e0000005b
R14: 0x0000562fdccf5aa0 R15: 0xfffffffffffffffc EFL: 0x0000000000010202

-- C level backtrace information -------------------------------------------
/XYZ/ruby/miniruby(rb_vm_bugreport+0x2b7) [0x562fdccbc2f7] vm_dump.c:683
/XYZ/ruby/miniruby(rb_bug_context+0x2e6) [0x562fdc981a86] error.c:520
/XYZ/ruby/miniruby(sigsegv+0x6a) [0x562fdcb842fa] signal.c:907
/lib/x86_64-linux-gnu/libpthread.so.0 [0x7f977fd95390]
/XYZ/ruby/miniruby(onig_node_free+0x79) [0x562fdcb53209] regparse.c:1065
/XYZ/ruby/miniruby(parse_char_class+0x28ae) [0x562fdcb6c08e] regparse.c:4824
/XYZ/ruby/miniruby(parse_char_class+0x2194) [0x562fdcb6b974] regparse.c:4814
=================== SNIP (1015 wierszy) ===================
/XYZ/ruby/miniruby(parse_char_class+0x2194) [0x562fdcb6b974] regparse.c:4814
/XYZ/ruby/miniruby(parse_char_class+0x2194) [0x562fdcb6b974] regparse.c:4814

-- Other runtime information -----------------------------------------------

* Loaded script: id:000000,sig:06,src:000106+002150,op:splice,rep:128

* Loaded features:

0 enumerator.so
1 thread.rb
2 rational.so
3 complex.so

* Process memory map:

562fdc86c000-562fdcd8d000 r-xp 00000000 fc:00 849526
/XYZ/ruby/miniruby
562fdcf8c000-562fdcf92000 r--p 00520000 fc:00 849526
/XYZ/ruby/miniruby
562fdcf92000-562fdcf93000 rw-p 00526000 fc:00 849526
/XYZ/ruby/miniruby
562fdcf93000-562fdcfb4000 rw-p 00000000 00:00 0
562fddc4d000-562fddde4000 rw-p 00000000 00:00 0
[heap]
7f977e13d000-7f977ed88000 r--s 00000000 fc:00 849526
/XYZ/ruby/miniruby
7f977ed88000-7f977ed9e000 r-xp 00000000 fc:00 392981
/lib/x86_64-linux-gnu/libgcc_s.so.1
7f977ed9e000-7f977ef9d000 ---p 00016000 fc:00 392981
/lib/x86_64-linux-gnu/libgcc_s.so.1
7f977ef9d000-7f977ef9e000 rw-p 00015000 fc:00 392981
/lib/x86_64-linux-gnu/libgcc_s.so.1
7f977ef9e000-7f977f276000 r--p 00000000 fc:00 6318
/usr/lib/locale/locale-archive
7f977f276000-7f977f435000 r-xp 00000000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f435000-7f977f635000 ---p 001bf000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f635000-7f977f639000 r--p 001bf000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f639000-7f977f63b000 rw-p 001c3000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f63b000-7f977f63f000 rw-p 00000000 00:00 0
7f977f63f000-7f977f747000 r-xp 00000000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f747000-7f977f946000 ---p 00108000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f946000-7f977f947000 r--p 00107000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f947000-7f977f948000 rw-p 00108000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f948000-7f977f951000 r-xp 00000000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977f951000-7f977fb50000 ---p 00009000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977fb50000-7f977fb51000 r--p 00008000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977fb51000-7f977fb52000 rw-p 00009000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977fb52000-7f977fb80000 rw-p 00000000 00:00 0
7f977fb80000-7f977fb83000 r-xp 00000000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fb83000-7f977fd82000 ---p 00003000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fd82000-7f977fd83000 r--p 00002000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fd83000-7f977fd84000 rw-p 00003000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fd84000-7f977fd9c000 r-xp 00000000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977fd9c000-7f977ff9b000 ---p 00018000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977ff9b000-7f977ff9c000 r--p 00017000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977ff9c000-7f977ff9d000 rw-p 00018000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977ff9d000-7f977ffa1000 rw-p 00000000 00:00 0
7f977ffa1000-7f977ffc7000 r-xp 00000000 fc:00 394124
/lib/x86_64-linux-gnu/ld-2.23.so
7f9780095000-7f97800b7000 r--s 00000000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f97800b7000-7f97801bc000 rw-p 00000000 00:00 0
7f97801c0000-7f97801c1000 ---p 00000000 00:00 0
7f97801c1000-7f97801c6000 rw-p 00000000 00:00 0
7f97801c6000-7f97801c7000 r--p 00025000 fc:00 394124
/lib/x86_64-linux-gnu/ld-2.23.so
7f97801c7000-7f97801c8000 rw-p 00026000 fc:00 394124
/lib/x86_64-linux-gnu/ld-2.23.so
7f97801c8000-7f97801c9000 rw-p 00000000 00:00 0
7ffea3a99000-7ffea4298000 rw-p 00000000 00:00 0
[stack]
7ffea42fa000-7ffea42fc000 r--p 00000000 00:00 0
[vvar]
7ffea42fc000-7ffea42fe000 r-xp 00000000 00:00 0
[vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0
[vsyscall]


[NOTE]
You may have encountered a bug in the Ruby interpreter or extension
libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html

Issue #13234 na Redmine projektu

Commit naprawiający: ea940cc4dcff8d6c345d7015eda0bf06671f87e9

CVE: CVE-2017-6181

[CVE-2017-5924] Yara 3.5.0 – Use after free #2

Słowo wstępu

Kolejny tydzień – kolejne use-after-free w Yarze 🙂

Dla osób które nie znają backgroundu lub chciałyby dowiedzieć się więcej o “moich” błędach w tym projekcie – odsyłam tutaj i tutaj.

Krótki opis podatności

Błąd znajdował się w wersji 3.5.0 (Git HEAD: 890c3f850293176c0e996a602ffa88b315f4e98f) – jest to commit naprawiający poprzednie use-after-free.

Biblioteka LibYara nie sprawdzała czy głębokość wyrażenia w regule Yara jest większa od zera – zakładała zawsze, że tak jest. Powodowało to oczywisty problem wyjścia poza zakres zaalokowanej pamięci w przypadku wartości mniejszych lub równych zero (stary, dobry Integer Underflow).

Problem widać wyraźnie w poniższym fragmencie pliku yara/libyara/grammar.y:

 | _FOR_ for_expression error
      {
        compiler->loop_depth--;
        compiler->loop_identifier[compiler->loop_depth] = NULL;
}

Natomiast do samego użycia zwolnionej pamięci dochodziło w trakcie pracy destruktora w kompilatorze reguł. Za każdą iteracją pętli zwalniana była część pamięci wykraczająca poza obszar zarezerwowany do danego wyrażenia.

W pewnym momencie (w zależności od długości wyrażenia w regule Yara) dochodziło do sytuacji w której pamięć dla następnego wyrażenia została zwolniona, a destruktor próbował odczytać ją jako następny element w liście (zaznaczona poniżej linijka w pliku yara/libyara/compiler.c).

  for (i = 0; i < compiler->file_name_stack_ptr; i++)
    yr_free(compiler->file_name_stack[i]);

  fixup = compiler->fixup_stack_head;

  while (fixup != NULL)
  {
    YR_FIXUP* next_fixup = fixup->next;
    yr_free(fixup);
    fixup = next_fixup;
  }
yr_free(compiler);

Log z ASAN:

==10220==ERROR: AddressSanitizer: heap-use-after-free on address 0x63100003c862 at pc 0x0000004ef7f5 bp 0x7ffc9082a5d0 sp 0x7ffc9082a5c8
READ of size 8 at 0x63100003c862 thread T0
    #0 0x4ef7f4 in yr_compiler_destroy XYZ/yara/libyara/compiler.c:165:35
    #1 0x4ede04 in main XYZ/yara/yara.c:1242:5
    #2 0x7f05302fa82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #3 0x41a408 in _start (/usr/local/bin/yara+0x41a408)

0x63100003c862 is located 98 bytes inside of 65536-byte region [0x63100003c800,0x63100004c800)
freed by thread T0 here:
    #0 0x4b88cb in __interceptor_free /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:47:3
    #1 0x587ab3 in yr_arena_destroy XYZ/yara/libyara/arena.c:300:5
    #2 0x7f05302fa82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

previously allocated by thread T0 here:
    #0 0x4b8c1c in malloc /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64:3
    #1 0x58774f in _yr_arena_new_page XYZ/yara/libyara/arena.c:92:34
    #2 0x58774f in yr_arena_create XYZ/yara/libyara/arena.c:246

SUMMARY: AddressSanitizer: heap-use-after-free XYZ/yara/libyara/compiler.c:165:35 in yr_compiler_destroy
Shadow bytes around the buggy address:
  0x0c627ffff8b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c627ffff900: fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd
  0x0c627ffff910: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff920: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff930: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff940: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff950: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==10220==ABORTING

Issue #593 na GitHubie

Commit naprawiający: 7f02eca670f29c00a1d2c305e96febae6ce5d37b

CVE: CVE-2017-5924

[CVE-2016-10211] Yara 3.5.0 – Use after free #1

Słowo wstępu

Kontyunuując temat niebezpiecznych narzędzi (w ostatniej odsłonie było radare2) dla osób związanych z bezpieczeństwem IT – kolejny już wpis o Yarze. Temat motywacji testów oraz funkcjonalności projektu opisałem we wcześniejszym poście, więc oszczędze przynudzania czytelnikom 🙂

Krótki opis podatności

Błąd znajdował się w wersji 3.5.0 (Git HEAD: 779b9a77aa4377152a5cba031255029afb0c19a5).

LibYara w poszukiwaniu identyfikatorów w regułce, przekracza zakres zaalokowanego na nie miejsca i próbuje odczytać adres z pamięci w którym znajdowały się wcześniej wyrażenia regularne.

Rzucając okiem na commita naprawiającego widać wyraźnie, że parsując gramatyki za pomocą YACC trzeba ostrożnie podchodzić do zarządzania pamięcią 😉

Log z ASAN:

==17551==ERROR: AddressSanitizer: heap-use-after-free on address 0x60200000ed50 at pc 0x000000425ff6 bp 0x7fff57ebe190 sp 0x7fff57ebd920
READ of size 1 at 0x60200000ed50 thread T0
    #0 0x425ff5 in __interceptor_strcmp /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:284:3
    #1 0x5a85cf in yr_parser_lookup_loop_variable XYZ/yara/libyara/parser.c:282:9
    #2 0x57536e in yara_yyparse XYZ/yara/libyara/grammar.y:569:25
    #3 0x50c3d0 in yr_lex_parse_rules_file XYZ/yara/libyara/lexer.l:815:3
    #4 0x4f097e in yr_compiler_add_file XYZ/yara/libyara/compiler.c:357:12
    #5 0x4ee0c4 in main XYZ/yara/yara.c:1124:17
    #6 0x7fe78bf2982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #7 0x41a408 in _start (/usr/local/bin/yara+0x41a408)

0x60200000ed50 is located 0 bytes inside of 2-byte region [0x60200000ed50,0x60200000ed52)
freed by thread T0 here:
    #0 0x4b88cb in __interceptor_free /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:47:3
    #1 0x580ac8 in yydestruct XYZ/yara/libyara/grammar.y:202:9
    #2 0x50c3d0 in yr_lex_parse_rules_file XYZ/yara/libyara/lexer.l:815:3

previously allocated by thread T0 here:
    #0 0x4a59f6 in __strdup /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors.cc:578:3
    #1 0x4ff501 in yara_yylex XYZ/yara/libyara/lexer.l:438:22
    #2 0x573528 in yara_yyparse XYZ/yara/libyara/grammar.c:1573:16
    #3 0x50c3d0 in yr_lex_parse_rules_file XYZ/yara/libyara/lexer.l:815:3

SUMMARY: AddressSanitizer: heap-use-after-free /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:284:3 in __interceptor_strcmp
Shadow bytes around the buggy address:
  0x0c047fff9d50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9d60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9d70: fa fa fa fa fa fa fa fa fa fa 02 fa fa fa 00 fa
  0x0c047fff9d80: fa fa 01 fa fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x0c047fff9d90: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 02 fa
=>0x0c047fff9da0: fa fa 00 00 fa fa fd fa fa fa[fd]fa fa fa fd fa
  0x0c047fff9db0: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x0c047fff9dc0: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa fd fd
  0x0c047fff9dd0: fa fa fd fa fa fa 00 fa fa fa 01 fa fa fa 00 00
  0x0c047fff9de0: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x0c047fff9df0: fa fa 00 00 fa fa fd fa fa fa 00 fa fa fa 00 00
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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==17551==ABORTING

Issue #575 na GitHubie

Commit naprawiający: 890c3f850293176c0e996a602ffa88b315f4e98f

CVE: CVE-2016-10211

radare2 – Use after free

Słowo wstępu

Projekt radare2, z racji moich zainteresowań oraz wykonywanej pracy jest mi dosyć bliski. Narzędzia wykorzystywane do analizy binarnej plików, są w mojej opinii fenomenalnym wektorem ataku na zaawansowanego użytkownika  – rzadko kiedy ktoś zastanawia się nad tym, czy odpalany plik jest potencjalnie niebezpieczny, nie zdając sobie sprawy co dzieje się “pod spodem”, w momencie jego ładowania. 

Znane podatności w tego typu oprogramowaniu mogą zostać wykorzystane przez malware, celem utrudnienia lub uniemożliwienia analizy za pomocą konkretnego narzędzia. Dla dociekliwych dwa linki o tej tematyce od lcamtufa oraz j00ru (polecam obydwa!).

Pomijając wcześniejsze rozważania, sam rzut oka na obsługiwaną ilość formatów zachęca do poszukiwania błędów w ich implementacji (przypomnę tylko, że projekt utrzymuje również wielki zbiór przypadków testowych idealnych do fuzzingu) 😉

r2 is a rewrite from scratch of radare in order to provide a set of libraries and tools to work with binary files.

Radare project started as a forensics tool, a scriptable commandline hexadecimal editor able to open disk files, but later support for analyzing binaries, disassembling code, debugging programs, attaching to remote gdb servers, ..

radare2 is portable.

  • Architectures: * 6502, 8051, CRIS, H8/300, LH5801, T8200, arc, arm, avr, bf, blackfin, xap, dalvik, dcpu16, gameboy, i386, i4004, i8080, m68k, malbolge, mips, msil, msp430, nios II, powerpc, rar, sh, snes, sparc, tms320 (c54x c55x c55+), V810, x86-64, zimg, risc-v.
  • File Formats:
    • bios, CGC, dex, elf, elf64, filesystem, java, fatmach0, mach0, mach0-64, MZ, PE, PE+, TE, COFF, plan9, dyldcache, Commodore VICE emulator, Game Boy (Advance), Nintendo DS ROMs and Nintendo 3DS FIRMs.
  • Operating Systems:
    • Android, GNU/Linux, [Net|Free|Open]BSD, iOS, OSX, QNX, w32, w64, Solaris, Haiku, FirefoxOS
  • Bindings:
    • Vala/Genie, Python (2, 3), NodeJS, LUA, Go, Perl, Guile, php5, newlisp, Ruby, Java, OCaml, …

Po długim wstępie, krótki opis podatności

Błąd znajdował się w wersji 0.9 (Git HEAD: f49448faf03c2faff8c79a99b28872e8a931efb6).

Niewłaściwa obsługa zarządzania pamięcią w module analizującym skompilowane klasy Java powodowała, na etapie późniejszym, odczyt 4 bajtów zwolnionej pamięci w funkcji ustalającej konwencję wywołania funkcji (powtórzenie zamierzone). Dla ciekawskich, błąd wymagał wprowadzenia zmian w siedmiu plikach oraz dopisania dodatkowych 98 linijek kodu.

W obrazowy sposób pokazuje to z jaką dozą ostrożności trzeba podchodzić do “ręcznego” zarządzania pamięcią w językach programowania C oraz C++. Dociekliwym czytelnikom z poprzedniego zdania polecam również przejrzeć commita naprawiającego 🙂

Log z ASAN:

==35187==ERROR: AddressSanitizer: heap-use-after-free on address 0x611000005ce8 at pc 0x7ff97c5f0585 bp 0x7ffc1c0b2f10 sp 0x7ffc1c0b2f08
READ of size 4 at 0x611000005ce8 thread T0
 #0 0x7ff97c5f0584 in fcn_callconv XYZ/radare2/libr/core/anal.c:2001
 #1 0x7ff97c5f0584 in ?? ??:0
 #2 0x7ff97c5f4b5c in r_core_anal_all XYZ/radare2/libr/core/anal.c:2536
 #3 0x7ff97c5f4b5c in ?? ??:0
 #4 0x7ff97c543480 in cmd_anal_all XYZ/radare2/libr/core/./cmd_anal.c:4503
 #5 0x7ff97c543480 in ?? ??:0
 #6 0x7ff97c50173a in cmd_anal XYZ/radare2/libr/core/./cmd_anal.c:4829
 #7 0x7ff97c50173a in ?? ??:0
 #8 0x7ff97c5e6c6c in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:213
 #9 0x7ff97c5e6c6c in ?? ??:0
 #10 0x7ff97c534810 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:1960 (discriminator 4)
 #11 0x7ff97c534810 in ?? ??:0
 #12 0x7ff97c4fe4a3 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1311
 #13 0x7ff97c4fe4a3 in ?? ??:0
 #14 0x7ff97c4fb344 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2477
 #15 0x7ff97c4fb344 in ?? ??:0
 #16 0x5587c9eb7566 in main XYZ/radare2/binr/radare2/radare2.c:961
 #17 0x5587c9eb7566 in ?? ??:0
 #18 0x7ff975fcf2b0 in __libc_start_main ??:?
 #19 0x7ff975fcf2b0 in ?? ??:0
 #20 0x5587c9de5889 in _start ??:?
 #21 0x5587c9de5889 in ?? ??:0

0x611000005ce8 is located 40 bytes inside of 208-byte region [0x611000005cc0,0x611000005d90)
freed by thread T0 here:
 #0 0x5587c9e83cd0 in __interceptor_cfree.localalias.0 asan_malloc_linux.cc.o:?
 #1 0x5587c9e83cd0 in ?? ??:0
 #2 0x7ff976ee4384 in r_hashtable64_free XYZ/radare2/libr/util/./ht.c:177
 #3 0x7ff976ee4384 in ?? ??:0
 #4 0x7ff97a6e5b02 in r_anal_state_free XYZ/radare2/libr/anal/state.c:70
 #5 0x7ff97a6e5b02 in ?? ??:0
 #6 0x7ff97a654b24 in analyze_from_code_buffer XYZ/radare2/libr/..//libr/anal/p/anal_java.c:502
 #7 0x7ff97a654b24 in ?? ??:0
 #8 0x7ff97a654369 in analyze_from_code_attr XYZ/radare2/libr/..//libr/anal/p/anal_java.c:533
 #9 0x7ff97a654369 in ?? ??:0
 #10 0x7ff97a652de4 in java_analyze_fns XYZ/radare2/libr/..//libr/anal/p/anal_java.c:664
 #11 0x7ff97a652de4 in ?? ??:0
 #12 0x7ff97c5ebf80 in r_core_anal_fcn XYZ/radare2/libr/core/anal.c:1234
 #13 0x7ff97c5ebf80 in ?? ??:0
 #14 0x7ff97c5f4670 in r_core_anal_all XYZ/radare2/libr/core/anal.c:2496
 #15 0x7ff97c5f4670 in ?? ??:0
 #16 0x7ff97c543480 in cmd_anal_all XYZ/radare2/libr/core/./cmd_anal.c:4503
 #17 0x7ff97c543480 in ?? ??:0
 #18 0x7ff97c50173a in cmd_anal XYZ/radare2/libr/core/./cmd_anal.c:4829
 #19 0x7ff97c50173a in ?? ??:0
 #20 0x7ff97c5e6c6c in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:213
 #21 0x7ff97c5e6c6c in ?? ??:0
 #22 0x7ff97c534810 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:1960 (discriminator 4)
 #23 0x7ff97c534810 in ?? ??:0
 #24 0x7ff97c4fe4a3 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1311
 #25 0x7ff97c4fe4a3 in ?? ??:0
 #26 0x7ff97c4fb344 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2477
 #27 0x7ff97c4fb344 in ?? ??:0
 #28 0x5587c9eb7566 in main XYZ/radare2/binr/radare2/radare2.c:961
 #29 0x5587c9eb7566 in ?? ??:0
 #30 0x7ff975fcf2b0 in __libc_start_main ??:?
 #31 0x7ff975fcf2b0 in ?? ??:0

previously allocated by thread T0 here:
 #0 0x5587c9e84040 in calloc ??:?
 #1 0x5587c9e84040 in ?? ??:0
 #2 0x7ff97a6c1784 in r_anal_bb_new XYZ/radare2/libr/anal/bb.c:10 (discriminator 1)
 #3 0x7ff97a6c1784 in ?? ??:0
 #4 0x7ff97a6e3e07 in r_anal_ex_get_bb XYZ/radare2/libr/anal/anal_ex.c:174
 #5 0x7ff97a6e3e07 in ?? ??:0
 #6 0x7ff97a6e4996 in r_anal_ex_analysis_driver XYZ/radare2/libr/anal/anal_ex.c:287
 #7 0x7ff97a6e4996 in ?? ??:0
 #8 0x7ff97a654a40 in analyze_from_code_buffer XYZ/radare2/libr/..//libr/anal/p/anal_java.c:491
 #9 0x7ff97a654a40 in ?? ??:0
 #10 0x7ff97a654369 in analyze_from_code_attr XYZ/radare2/libr/..//libr/anal/p/anal_java.c:533
 #11 0x7ff97a654369 in ?? ??:0
 #12 0x7ff97a652de4 in java_analyze_fns XYZ/radare2/libr/..//libr/anal/p/anal_java.c:664
 #13 0x7ff97a652de4 in ?? ??:0
 #14 0x7ff97c5ebf80 in r_core_anal_fcn XYZ/radare2/libr/core/anal.c:1234
 #15 0x7ff97c5ebf80 in ?? ??:0
 #16 0x7ff97c5f4670 in r_core_anal_all XYZ/radare2/libr/core/anal.c:2496
 #17 0x7ff97c5f4670 in ?? ??:0
 #18 0x7ff97c543480 in cmd_anal_all XYZ/radare2/libr/core/./cmd_anal.c:4503
 #19 0x7ff97c543480 in ?? ??:0
 #20 0x7ff97c50173a in cmd_anal XYZ/radare2/libr/core/./cmd_anal.c:4829
 #21 0x7ff97c50173a in ?? ??:0
 #22 0x7ff97c5e6c6c in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:213
 #23 0x7ff97c5e6c6c in ?? ??:0
 #24 0x7ff97c534810 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:1960 (discriminator 4)
 #25 0x7ff97c534810 in ?? ??:0
 #26 0x7ff97c4fe4a3 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1311
 #27 0x7ff97c4fe4a3 in ?? ??:0
 #28 0x7ff97c4fb344 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2477
 #29 0x7ff97c4fb344 in ?? ??:0
 #30 0x5587c9eb7566 in main XYZ/radare2/binr/radare2/radare2.c:961
 #31 0x5587c9eb7566 in ?? ??:0
 #32 0x7ff975fcf2b0 in __libc_start_main ??:?
 #33 0x7ff975fcf2b0 in ?? ??:0

SUMMARY: AddressSanitizer: heap-use-after-free (/usr/local/lib/libr_core.so+0x267584)
Shadow bytes around the buggy address:
 0x0c227fff8b40: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
 0x0c227fff8b50: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8b60: fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa
 0x0c227fff8b70: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8b80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa
=&gt;0x0c227fff8b90: fa fa fa fa fa fa fa fa fd fd fd fd fd[fd]fd fd
 0x0c227fff8ba0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8bb0: fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa fa
 0x0c227fff8bc0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8bd0: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
 0x0c227fff8be0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
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
 Heap right redzone: fb
 Freed heap region: fd
 Stack left redzone: f1
 Stack mid redzone: f2
 Stack right redzone: f3
 Stack partial redzone: f4
 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
==35187==ABORTING

Log z Valgrinda (bardziej czytelny):


==54153== Invalid read of size 4
==54153==    at 0x10013E552: fcn_callconv (anal.c:2001)
==54153==    by 0x100141E1E: r_core_anal_all (anal.c:2536)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==    by 0x100095C6F: cmd_anal (cmd_anal.c:4829)
==54153==    by 0x100138496: r_cmd_call (cmd_api.c:213)
==54153==    by 0x1000C00C8: r_core_cmd_subst_i (cmd.c:1960)
==54153==    by 0x10009336C: r_core_cmd_subst (cmd.c:1311)
==54153==    by 0x100090FF6: r_core_cmd (cmd.c:2477)
==54153==    by 0x100083CB4: r_core_prompt_exec (core.c:1684)
==54153==    by 0x10000436F: main (in /usr/local/bin/r2)
==54153==  Address 0x11020e1c8 is 40 bytes inside a block of size 208 free'd
==54153==    at 0x100074EF7: free (in /usr/local/Cellar/valgrind/3.11.0_1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==54153==    by 0x10046E38F: r_anal_bb_free (bb.c:58)
==54153==    by 0x10166749B: r_hashtable64_free (ht.c:177)
==54153==    by 0x100486425: r_anal_state_free (state.c:70)
==54153==    by 0x1004105BC: analyze_from_code_buffer (anal_java.c:502)
==54153==    by 0x10041011F: analyze_from_code_attr (anal_java.c:533)
==54153==    by 0x10040EFCB: java_analyze_fns (anal_java.c:664)
==54153==    by 0x10013BCFB: r_core_anal_fcn (anal.c:1234)
==54153==    by 0x100141A8C: r_core_anal_all (anal.c:2496)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==    by 0x100095C6F: cmd_anal (cmd_anal.c:4829)
==54153==    by 0x100138496: r_cmd_call (cmd_api.c:213)
==54153==  Block was alloc'd at
==54153==    at 0x1000751B9: calloc (in /usr/local/Cellar/valgrind/3.11.0_1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==54153==    by 0x10046E0FA: r_anal_bb_new (bb.c:10)
==54153==    by 0x100484A4F: r_anal_ex_get_bb (anal_ex.c:174)
==54153==    by 0x1004850C8: r_anal_ex_analysis_driver (anal_ex.c:287)
==54153==    by 0x100484E78: r_anal_ex_perform_analysis (anal_ex.c:230)
==54153==    by 0x10041072C: analyze_method (anal_java.c:571)
==54153==    by 0x1004104B6: analyze_from_code_buffer (anal_java.c:491)
==54153==    by 0x10041011F: analyze_from_code_attr (anal_java.c:533)
==54153==    by 0x10040EFCB: java_analyze_fns (anal_java.c:664)
==54153==    by 0x10013BCFB: r_core_anal_fcn (anal.c:1234)
==54153==    by 0x100141A8C: r_core_anal_all (anal.c:2496)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==
==54153== Invalid read of size 4
==54153==    at 0x10013E568: fcn_callconv (anal.c:2004)
==54153==    by 0x100141E1E: r_core_anal_all (anal.c:2536)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==    by 0x100095C6F: cmd_anal (cmd_anal.c:4829)
==54153==    by 0x100138496: r_cmd_call (cmd_api.c:213)
==54153==    by 0x1000C00C8: r_core_cmd_subst_i (cmd.c:1960)
==54153==    by 0x10009336C: r_core_cmd_subst (cmd.c:1311)
==54153==    by 0x100090FF6: r_core_cmd (cmd.c:2477)
==54153==    by 0x100083CB4: r_core_prompt_exec (core.c:1684)
==54153==    by 0x10000436F: main (in /usr/local/bin/r2)
==54153==  Address 0x11020e1c8 is 40 bytes inside a block of size 208 free'd
==54153==    at 0x100074EF7: free (in /usr/local/Cellar/valgrind/3.11.0_1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==54153==    by 0x10046E38F: r_anal_bb_free (bb.c:58)

Issue #6034 na GitHubie

Commit naprawiający: 5800b23e8feb06df42c7898299b4872572b7b979

CVE: N/A

WebKit JavaScriptCore – Out of bounds read

Szybki wstęp

Projekt WebKit kusi bughunterów przede wszystkim, bardzo dużym zasięgiem występowania błędu – wykorzystywany jest jako core przeglądarki Apple Safari, silnik Apple AppStore i komponent wielu aplikacji w systemach MacOS X oraz Linux.

Między innymi to błędy w WebKitcie były punktem wejścia do jailbreaku iOS i odblokowania urządzeń Apple’a. Myślę, że lepszej motywacji do zajęcia się tym tematem nie trzeba 🙂

Opis podatności

Dziura znajdowała się w rewizji SVN 208042 oraz w Safari Technology Preview Release 18 (i niżej).

Błąd związany był z subtelną różnicą w standardzie ECMAScript w kwestii serializacji JSONów. Poniżej cytat z commita naprawiającego, który dobrze opisuje clue problemu:

When JSON.stringify encounter the undefined value, the result depends
on the context. If it is produced under the object property context, we ignore
that property. On the other hand, if it is produced under the array element
context, we produce “null”.

Skip the property that value is undefined.
JSON.stringify({ value: undefined }); => “{}”

Write “null” when the element is undefined.
JSON.stringify([undefined]); => “[null]”

Jak widać poniżej mój payload “łapał” się na obydwa w/w przypadki:

JSON.parse(new Proxy([undefined],{}))

Następnie, po niewłaściwym określeniu obiektu, jego wartość była przekazywana do funkcji budującej stringa (Webkit posiada własne funkcje służące do operacji na stringach) i nastepował odczyt spod adresu niezdefiniowanego w pamięci, który skutkował SIGSEGV.

Log z ASAN:

==16295==ERROR: AddressSanitizer: SEGV on unknown address 0x6041000021a3 (pc 0x7fa5adf38ee4 bp 0x0000ffffffff sp 0x7ffd4d2f6790 T0)
==16295==The signal is caused by a READ memory access.
    #0 0x7fa5adf38ee3 in WTF::StringBuilder::operator[](unsigned int) const XYZ/webkit/Source/WTF/wtf/text/StringBuilder.h:247:20
    #1 0x7fa5adf38ee3 in JSC::Stringifier::Holder::appendNextProperty(JSC::Stringifier&, WTF::StringBuilder&) XYZ/webkit/Source/JavaScriptCore/runtime/JSONObject.cpp:465
    #2 0x7fa5adf35501 in JSC::Stringifier::appendStringifiedValue(WTF::StringBuilder&, JSC::JSValue, JSC::JSObject*, JSC::PropertyNameForFunctionCall const&) XYZ/webkit/Source/JavaScriptCore/runtime/JSONObject.cpp:384:37
    #3 0x7fa5adf31deb in JSC::Stringifier::stringify(JSC::Handle<JSC::Unknown>) XYZ/webkit/Source/JavaScriptCore/runtime/JSONObject.cpp:262:9
    #4 0x7fa5adf406c4 in JSC::JSONProtoFuncStringify(JSC::ExecState*) XYZ/webkit/Source/JavaScriptCore/runtime/JSONObject.cpp:786:57
    #5 0x7fa565afe027  (<unknown module>)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV XYZ/webkit/Source/WTF/wtf/text/StringBuilder.h:247:20 in WTF::StringBuilder::operator[](unsigned int) const
==16295==ABORTING

Bug 164123 w bugzilli projektu WebKit

Commit naprawiający (SVN Rev: 208123)

CVE: N/A

[CVE-2016-1000295] PCRE2 10.22 – Stack Buffer Overflow

Słowo wstępu

Bardzo podoba mi się opis projektu – poza opisaniem funkcjonalności, odpowiada również na pytanie dlaczego jest to bardzo interesujący cel fuzzingu:

The PCRE library is a set of functions that implement regular expression pattern matching using the same syntax and semantics as Perl 5. PCRE has its own native API, as well as a set of wrapper functions that correspond to the POSIX regular expression API. The PCRE library is free, even for building proprietary software.

PCRE was originally written for the Exim MTA, but is now used by many high-profile open source projects, including Apache, PHP, KDE, Postfix, Analog, and Nmap. PCRE has also found its way into some well known commercial products, like Apple Safari. Some other interesting projects using PCRE include Chicken, Ferite, Onyx, Hypermail, Leafnode, Askemos, Wenlin, and 8th.

Krótki opis podatności

Podatność znajduje się w wersji 10.22 opublikowanej 29. lipca 2016 roku.

Głównym problemem było zachowanie parsera wyrażeń regularnych po wyłączeniu walidacji UTF-8 (wtedy następuje założenie, że regex będzie w UTF-8). Bardzo ciekawe jest, że walidację tę można wyłączyć w payloadzie, co powoduje, że jesteśmy w stanie zawsze “zatriggerować” podatny tryb działania.

Do przepełnienia bufora prowadziło złe obliczenie długości kopiowanych bajtów (niezgodnych z UTF-8) do bufora o nazwie utf_units.

Poniżej podatny fragment kodu w pliku pcre2_compile.c:

if (utf && NOT_FIRSTCU(code[-1])) 
{
    PCRE2_UCHAR *lastchar = code - 1;
    BACKCHAR(lastchar);
    c = (int)(code - lastchar);               /* Length of UTF character */
    memcpy(utf_units, lastchar, CU2BYTES(c)); /* BADUM TSS! */
    c |= UTF_LENGTH;                          /* Flag c as a length */
}

Problem powodowało poniższe 26 bajtów – widać wyraźnie wyłączenie walidacji UTF-8, jak również “oblanie” testu zgodności z tym kodowaniem.

Payload wraz z testem:

# hexdump -C bufover_1_min 
00000000  2f d4 83 a4 9c a4 9c b0  3f 2f 6e 6f 5f 75 74 66  |/.......?/no_utf|
00000010  5f 63 68 65 63 6b 2c 75  74 66                    |_check,utf|
0000001a
# LC_ALL=C  iconv -f utf-8 -t utf-8 < bufover_1_min 
/ԃiconv: illegal input sequence at position 3

Log z ASAN:

==19226==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc935e5fa6 at pc 0x0000004a1da4 bp 0x7ffc935e5e90 sp 0x7ffc935e5640
WRITE of size 7 at 0x7ffc935e5fa6 thread T0
    #0 0x4a1da3 in __asan_memcpy /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors.cc:413:3
    #1 0x7f8dbbadc514 in compile_branch XYZ/pcre2_compile.c:5211:9
    #2 0x7f8dbbad125b in compile_regex XYZ/pcre2_compile.c:7687:8
    #3 0x7f8dbbac9ccb in pcre2_compile_8 XYZ/pcre2_compile.c:8657:7
    #4 0x4f0e2c in process_pattern XYZ/pcre2test.c:4949:1
    #5 0x4e8333 in main XYZ/pcre2test.c:7607:10
    #6 0x7f8dba9c782f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #7 0x41a828 in _start (/usr/local/bin/pcre2test+0x41a828)

Address 0x7ffc935e5fa6 is located in stack of thread T0 at offset 262 in frame
    #0 0x7f8dbbad6a6f in compile_branch XYZ/pcre2_compile.c:3861

  This frame has 28 object(s):
    [32, 36) 'repeat_min'
    [48, 52) 'repeat_max'
    [64, 72) 'length_prevgroup'
    [96, 104) 'tempcode'
    [128, 136) 'ptr'
    [160, 168) 'tempptr'
    [192, 224) 'classbits'
    [256, 262) 'utf_units' <== Memory access at offset 262 overflows this variable
    [288, 296) 'class_uchardata'
    [320, 324) 'ec'
    [336, 340) 'subreqcu'
    [352, 356) 'subfirstcu'
    [368, 372) 'subreqcuflags'
    [384, 388) 'subfirstcuflags'
    [400, 408) 'mcbuffer'
    [432, 464) 'pbits'
    [496, 500) 'negated'
    [512, 516) 'ptype664'
    [528, 532) 'pdata'
    [544, 548) 'd'
    [560, 564) 'count'
    [576, 584) 'arg'
    [608, 616) 'memcode'
    [640, 644) 'set'
    [656, 660) 'unset'
    [672, 676) 'negated3050'
    [688, 692) 'ptype3051'
    [704, 708) 'pdata3052'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors.cc:413:3 in __asan_memcpy
Shadow bytes around the buggy address:
  0x1000126b4ba0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000126b4bb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000126b4bc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000126b4bd0: 00 00 00 00 f1 f1 f1 f1 04 f2 04 f2 00 f2 f2 f2
  0x1000126b4be0: 00 f2 f2 f2 00 f2 f2 f2 00 f2 f2 f2 00 00 00 00
=>0x1000126b4bf0: f2 f2 f2 f2[06]f2 f2 f2 00 f2 f2 f2 04 f2 04 f2
  0x1000126b4c00: 04 f2 04 f2 04 f2 00 f2 f2 f2 00 00 00 00 f2 f2
  0x1000126b4c10: f2 f2 04 f2 04 f2 04 f2 04 f2 04 f2 00 f2 f2 f2
  0x1000126b4c20: 00 f2 f2 f2 04 f2 04 f2 04 f2 04 f2 04 f3 f3 f3
  0x1000126b4c30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000126b4c40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Bug #1889 w bugzilli projektu PCRE

Commit naprawiający (SVN Rev: 555)

CVE: CVE-2016-1000295

LatentBot – modularny i silnie zaciemniony bot

Zachęcam do zapoznania sie z moim wpisem na blogu CERT Polska, tym razem o ciekawym złośniku z aktywnego exploit-kita RigLatentBocie.

Zajawka:

LatentBot jest widoczny w sieci od 2013 roku, a na początku października rozpoczął swoją drugą młodość – dodany został jako payload do bardzo aktywnego exploit-kita Rig i jest serwowany zamiennie z takim złośliwym oprogramowaniem jak: Cerber, CryptFile2, Gootkit czy Vawtrak.

Głównym wektorem infekcji dla użytkowników były dokumenty pakietu Microsoft Office zawierające exploity na podatności CVE-2010-3333, CVE-2012-0158, CVE-2013-3906 oraz CVE-2014-1761. Pliki zbudowane zostały za pomocą pakietu Microsoft Word Intruder / MWISTAT.

Kampanie z tym złośnikiem prowadzone były m.in. w USA, Kanadzie, Brazylii, Singapurze, Korei Południowej, Polsce oraz Zjednoczonych Emiratach Arabskich. Głównym adresatem byli przedstawiciele sektora finansowego oraz ubezpieczeniowego.

LatentBot na tle konkurencyjnego malware znacznie się wyróżnia:

Dynamiczne deszyfrowanie stringów („autorski” algorytm) oraz nazw funkcji WinAPI i usuwanie ich po użyciu
Możliwość nadpisania (uszkodzenia) MBR
Modułowość (zaciemnione moduły przechowywane w rejestrze Windows)
Wykorzystanie malware Pony 2.0 celem wykradania informacji oraz portfeli kryptowalut takich jak BitCoin, LiteCoin, Namecoin i wiele innych.

Zachęcam do zapoznania się z całością wpisu 🙂

Security BSides Warsaw 2016

Po raz kolejny miałem przyjemność być prelegentem na konferencji Security BSides 2016. W tym roku tematem mojej prezentacji było efektywne fuzzowanie za pomocą American Fuzzy Loop.

Podczas wydarzenia zapewniona była profesjonalna obsługa streamingu i nagrywania sesji – każdą z nich można obejrzeć na kanale YT wydarzenia. Dodatkowo na fanpage’u konferencji znajdują się zdjęcia z imprezy. Wszystkich obecnych oraz oglądających zachęcam do wypełnienia ankiety – pomoże to w przyszłym roku rangę wydarzenia 😉

Tutaj znajduje się moja prezentacja, natomiast poniżej możesz obejrzeć nagranie z mojej sesji:

Zgodnie z tradycją udostępniłem stronę ze sporą ilością zasobów na temat fuzzingu i projektów “około” AFLa.

Zachęcam do zapoznania się 🙂