\[ \definecolor{data}{RGB}{18,110,213} \definecolor{unknown}{RGB}{217,86,16} \definecolor{learned}{RGB}{175,114,176} \]

How to fuzz?

Jak efektywnie psuć software...

Kamil Frankowicz

$whoami

Agenda

  • Krótkie przypomnienie czym jest fuzzing
  • Problemy
  • Dwa slajdy o fuzzerach
  • Ogólny flow pracy
  • Kilka luk znalezionych za pomocą fuzzingu

Przypomnienie

Kto nie wie czym jest fuzzing? - ręka w górę!

Fuzzing - w oczekwaniu na crasha

Koncepcja stara jak świat - zaczęło się od aplikacji "The Monkey" na MacOS (Steve Capps) w 1983 roku.

Polega ona na automatycznym wprowadzeniu do programu niepoprawnych, nieoczekiwanych lub losowych danych, a następnie na automatycznym obserwowaniu zachowania programu w oczekiwaniu na awarię.

Seems easy....

Źródło: pl.wikipedia.org

Fuzzing - like it's 1983+

  • Dumb fuzzing - wrzucamy do aplikacji cokolwiek, mieszamy jakkolwiek
  • Template-based fuzzing - dużo lepsze podejście: fuzzery dla określonych formatów danych wejściowych
  • Jakoś działa ;)
  • Zzuf, Peach Fuzz

Template-based fuzzing - przykład błędu

Stack buffer overflow w routerach UPC (2014)

http://192.168.0.1/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Dump rejestrów:

r28/gp =817b2e80 r29/sp =8280a8c0 r30/fp =8280ab38 r31/ra =41414141
PC : 0x41414141 error addr: 0x41414140

Do dzisiaj jeden z nich jest podatny...

Fuzzing - 2013+

  • Instrumentacja kodu
  • Przypadki testowe maksymalizujące pokrycie kodu
  • Ścieżki w kodzie jako informacja zwrotna
  • Wykorzystanie dodatkowych narzędzi w procesie fuzzingu np. ASAN, MSAN, UBSAN

Fuzzing - 2013+ : Problemy

  • Skąd weźmiemy dane testowe?
  • W jaki sposób będziemy „mieszać” dane (z uwzględnieniem np. składni języka lub formatu)?
  • Jak pokryjemy największą ilość przypadków w kodzie?
  • Jak zrobić to efektywnie i w skończonym czasie?
  • W jaki sposób wyłapiemy błędy, które nie skutkują awarią programu?

American Fuzzy Loop (AFL)

Kto nie słyszał o AFL? - Nie wierzę...

American Fuzzy Loop (AFL)

AFL - szybko dla niekumatych

  • Wyznacza branżowe "standardy" w kwesti fuzzerów
  • Prawdopodobnie fuzzer z największą ilością znalezionych błędów na koncie
  • Aby uzyskać "moc" wystarczy skompilować źródła z afl-gcc \ afl-clang \ afl-clang-fast
  • Jest dosyć uniwersalny, ale wszystkiego nim nie sfuzzujemy

Nie tylko AFL! - LibFuzzer

  • Podejście takie samo jak AFL
  • Część LLVM / CLANG
  • Wsparcie: ASAN, MSAN, UBSAN
  • Różnica: LibFuzzer fuzzuje funkcje i ma większy próg wejścia - musimy napisać nieco kodu
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    ASN1_STRING *out = 0;
    ASN1_mbstring_copy(&out, Data, Size, MBSTRING_BMP, 0);
    if (out!=0) ASN1_STRING_free(out);
    return 0;
}

Warto pamiętać!

Fuzzery nie znajdą nam wszystkiego - świetnym przykładem jest błąd w 7zipie (CVE-2016-2334, obsługa HFS+ z kompresją) o którym mówił Marcin Noga (Icewall) w piątek.

https://www.youtube.com/watch?v=iB_KQ4-eQ9E

Fuzzing time!

Krok #0 - Dobre przypadki testowe

  • Małe (poniżej 1 KB, serio...) - usuwajmy niepotrzebne dane
  • Funkcjonalnie różne od siebie
  • Wykorzystajmy poprzednie podatności
  • Wykorzystajmy kolejkę z poprzedniej iteracji - po minimalizacji :)
  • Czasem warto wykorzystać dane testowe z innych programów, jeżeli testujemy podobne komponenty

Pamiętamy - małe pliki, to ważne ;)

Krok #1 - Kompilacja ze źródeł z afl-clang-fast

# Na pobranych źródłach z gita / strony projektu / apt-get source 
CC=afl-clang-fast CXX=afl-clang-fast++ ./configure
# O ASAN + AFL za chwilę
AFL_USE_ASAN=1 make 

Krok #2 - Minimalizacja tego co udało nam się nazbierać

  • afl-showmap - informacja zwrotna o ilości ścieżek osiągniętych przez dany przypadek testowy
  • afl-analyze - szybki podgląd w jaki sposób traktowane są bajty w pliku przez np. parser
  • afl-cmin - wybór najlepszych plików pod względem pokrycia kodu z całego korpusu
  • afl-tmin - usuwanie niewykorzystywanych bajtów w pliku podczas fuzzingu

Krok #2 - Minimalizacja tego co udało nam się nazbierać

corpus minimization tool for afl-fuzz by lcamtuf@google.com

[*] Testing the target binary...
[+] OK, 13284 tuples recorded.
[*] Obtaining traces for input files in 'tshark_tc/'...
    Processing file 311/311... 
[*] Sorting trace sets (this may take a while)...
[+] Found 82845 unique tuples across 311 files.
[*] Finding best candidates for each tuple...
    Processing file 311/311... 
[*] Sorting candidate list (be patient)...
[*] Processing candidates and writing output files...
    Processing tuple 82845/82845... 
[+] Narrowed down to 239 files, saved in 'out_tc/'.

Krok #2,5 - Słowniki

W przypadku fuzzowania parserów - wymagających zachowania składni lub struktury pliku (np. XML, PDF) warto skorzystać ze słownika, który odpowiednio zmanipuluje input, tak aby "łapać sie" na podstawowe walidacje.

Testując ze słownikiem AFL nie ogranicza się tylko niego - testowany jest również input bez słownika.

Krok #3 - Fuzzer fired!

afl-fuzz -i [input_dir] -o [output_dir] -x [dict_dir] -m [memory] \ 
-t [timeout] strings @@
  • @@ - input fuzzera (jeżeli program czyta ze stdin to nie stosujemy)
  • Przełączniki -x, -m, -t są opcjonalne

Hmm.. Coś poszło nie tak...

Krok #4 - Początkowe zderzenie ze ścianą

  • Mała liczba osiąganych ścieżek
  • Duży korpus testowy
  • Olbrzymie test case'y
  • Powolne działanie testowanych binarek
  • Crashe podczas wczytywania do AFL
  • Zapchany /tmp

Krok #5 - Jeszcze więcej mocy!

  • Parallel Fuzzing
  • Persistent Mode
  • AddressSanitizer
  • MemorySanitizer
  • disfuzz-afl

#5 Jeszcze więcej mocy! - Persistent Mode

  • In-process fuzzing
  • Pominięcie narzutu związanego z uruchomieniem procesu
while(__AFL_LOOP(2000)){
    data_read()
    data_parse()
    data_cleanup()
}
return 0;

W przykładach zawsze jest 1000 iteracji - ja zawsze daje 2-3x tyle :)

#5 Jeszcze więcej mocy! - ASAN

  • Wyłapywanie "krzywego" dostępu do pamięci:
    • Heap \ Stack Buffer Overflow
    • Use-After-Free
  • Nie ma róży bez kolców - około 2-krotne spowolnienie fuzzowania
  • Podczas kompilacji modyfikacja zmiennej środowiskowej AFL_USE_ASAN=1
  • Nie próbujcie tego na x64! (bez ograniczenia pamięci)

#5 Jeszcze więcej mocy! - ASAN

Krok #6 - Mam crasha, co dalej?

  • Sprawdzenie, czy aplikacja w normalnym trybie też się crashuje
  • Minimalizacja danych powodujących awarię (afl-tmin)
  • Identyfikacja awarii
  • Peruvian Rabbit Thing

#6 Mam crasha, co dalej? - Peruvian Rabbit Thing

  • Podejście odwrotne - przypadkami testowymi są crashe
  • Z crashy generujemy jeszcze więcej crashy!
  • Jednak zachowane są ścieżki z poprzedniego fuzzingu.
  • Wczoraj w ten sposób znalazłem kolejny heap overflow w PCRE2 :)

#6 Mam crasha, co dalej? - Peruvian Rabbit Thing

#6 Mam crasha, co dalej? - Identyfikacja crasha

  • IDA Pro + HexRays
  • Valgrind
  • GDB
    • Exploitable Plugin
  • ASAN, MSAN, UBSAN
  • [AFL_DIR]/experimental/crash_triage/triage_crashes.sh

Parę bugów z ostatniego miesiąca

PCRE2 - Stack Buffer Overflow #1

  • Payload: /()(?<=(?0))/
  • Błąd spowodowany złym policzeniem długości "gałęzi" podczas rozkładu wyrażenia regularnego
  • Efekt: Brak alokacji pamięci na heapie i za mały bufor na stosie

PCRE2 - Stack Buffer Overflow #1

Address 0x7ffc02393370 is located in stack of thread T0 at offset 5840 in frame
  This frame has 16 object(s):
    [32, 36) 'has_lookbehind'
    [48, 344) 'cb'
    [416, 424) 'code'
    [448, 456) 'pptr'
    [480, 488) 'length'
    [512, 516) 'firstcuflags'
    [528, 532) 'reqcuflags'
    [544, 548) 'firstcu'
    [560, 564) 'reqcu'
    [576, 580) 'errorcode'
    [592, 1616) 'stack_groupinfo'
    [1744, 5840) 'stack_parsed_pattern' <== Memory access at offset 5840 overflows 
    this variable
    [5968, 6288) 'named_groups'
    [6352, 14544) 'c16workspace'
    [14800, 14928) 'rc'
    [14960, 14964) 'count'

PCRE2 - Stack Buffer Overflow #2

  • Przepełnienie następowało po przekazaniu niewłaściwego inputu z wyłączoną walidacją UTF-8
  • Walidację UTF-8 można wyłączyć w payloadzie
  • Bajty niezgodne z UTF-8 powodowały złe obliczenie kopiowanego bufora
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 */
}

Yara - Integer Underflow

  • Payload: import "\x00"
  • Niewłaściwa długość stringa zwracana z funkcji:
YR_API void* yr_hash_table_lookup(
    YR_HASH_TABLE* table,
    const char* key,
    const char* ns)
{
  return yr_hash_table_lookup_raw_key(
      table,
      (void*) key,
      strlen(key),
      ns);
}

Yara - Integer Underflow

  • Zmienna len = 0
  • Następnie wykorzystywana w pętli poszukującej wartość w tablicy:
uint32_t hash(uint32_t seed, const void* buffer, size_t len)
{
  const uint8_t* b = (uint8_t*) buffer;
  uint32_t result = seed;
  size_t i;

  for (i = len - 1; i > 0; i--)
  {
    result ^= ROTATE_INT32(byte_to_int32[*b], i);
    b++;
  }
  // [SNIP]
}

Zbiór materiałów dt. fuzzingu



frankowicz.me/bsides2016

Web cert.pl
Web frankowicz.me
Mail kamil.frankowicz@cert.pl
Twitter @fumfel