9:01
24/11/2015

Ostatnio przyszło mi do głowy, że wypadałoby napisać ciut o moim najnowszym obiekcie fascynacji — projekcie American Fuzzy Lop i tym, jak postrzegam jego rolę w świecie open source. Pochwalę się też moim najnowszym projektem o nazwie afl-sid i wspomnę o tym, jak techniki sztucznej inteligencji mogą poprawić aktualny stan globalnego bezpieczeństwa informatycznego.

Autorem poniższego wpisu jest Jacek Wielemborek, jeden z programistów projektu Nmap, a od niedawna fascynat fuzzingu. Poza security interesują go też duże zbiory danych, takie jak na przykład Internet Census 2012. Jeśli chciałbyś opublikować na Niebezpieczniku swój artykuł, daj nam znać.

Dlaczego testowanie oprogramowania jest ważne

Nieważne jak bardzo będziemy się starać, trzy rzeczy są nieuniknione: śmierć, podatki i błędy w oprogramowaniu. Niektóre z tych ostatnich są mniej istotne — to, że na któryś z mniej odwiedzanych ekranów gdzieś wkradła się literówka zwykle nie wyrządzi tyle samo szkody co błąd w implementacji szyfrowania. Z drugiej strony, błąd z pozoru łagodny może okazać się poważnym. W niektórych przypadkach, błędy bezpieczeństwa potrafią być naprawdę subtelne i pozostać niewykryte przez lata. Co gorsze, mogą one być znalezione, ale nie zostać nigdzie zgłoszone, stając się “cyberbronią” stworzoną przeciwko nam samym. To nie science fiction – istnieją dowody, że rządy robią to już dziś, szkodząc społeczeństwu które rzekomo chcą chronić.

Chociażby z tego powodu powinniśmy zwracać uwagę na jakość oprogramowania, którego używamy. Problemem jest to, że często po prostu używanie programu zgodnie z przeznaczeniem nie wystarczy, żeby znaleźć w nim błędy bezpieczeństwa. Potrzebujemy hakerów aby w kreatywny sposób znajdowali dziury w oprogramowaniu i łatali je zanim zostaną wykorzystane przez innych. Idealnie byłoby, gdyby ten proces mógł przebiegać automatycznie.

Problem ten przynajmniej częściowo rozwiązał Michał “lcamtuf” Zalewski – światowej sławy polski haker, autor bestsellerowych książek i wysoko postawiony pracownik działu bezpieczeństwa w Google. Stworzył on American Fuzzy Lop (w skrócie AFL) – narzędzie, które znajduje błędy w oprogramowaniu korzystając z autorskiego algorytmu genetycznego. Jego projekt szybko odniósł spektakularny sukces – póki co liczba znalezionych dzięki niemu bugów idzie już przynajmniej w tysiące. Bez tego programu, wiele z nich mogłoby nigdy nie zostać odkryte. Przy okazji ujawniono masę błędów bezpieczeństwa, dlatego uważam, że dziś każdy projekt powinien w miarę możliwości przejść przez testy AFLa. Tak sformułowany cel bardzo trudno byłoby osiągnąć – duża część społeczności programistów (moim zdaniem niesłusznie) nie interesuje się bezpieczeństwem informatycznym i nie słyszała o fuzz testingu. Z tego powodu postanowiłem pokazać, na czym polega ten proces i trochę go uprościć.

Fuzzowanie programów – “Witaj, świecie”

W tej części zakładam, że znasz już jakieś podstawy programowania w C. W ramach przykładu stworzyłem program, który prosi użytkownika o wpisanie tekstu. Jeśli dane wejściowe zaczną się od cyfry “2“, zostanie wyświetlony błąd. Oto jego kod:

#include <stdio.h>
#include <stdlib.h>

int main() {
printf("Witaj, swiecie! Prosze cos wpisac i nacisnac enter: ");
if (getchar() == '2') {
abort();
}
return 0;
}

W tym prostym przykładzie można łatwo wyczytać z kodu źródłowego jak powinien wyglądać tekst, który doprowadzi do awarii programu. Załóżmy jednak, że tego nie wiemy i chcielibyśmy sprawdzić, czy American Fuzzy Lop dojdzie do tego sam. Zobaczmy więc jak to zrobić.
AFL radzi sobie najlepiej gdy na podstawie danych wejściowych może wnioskować o tym, jaki kod został wykonany. W tym przypadku, jeśli wpiszemy “2”, program przeskoczy do instrukcji “abort();” — chcielibyśmy, żeby fuzzer to zauważył.

Aby to osiągnąć, powinniśmy pozwolić AFLowi dodać trochę pomocniczego kodu do naszego programu. Jest to dość proste: wystarczy użyć afl-gcc – kompilatora który zachowuje się tak samo jak GCC, ale dodaje też kilka instrukcji od siebie. Powiedzmy, że plik z kodem powyżej nazwaliśmy test.c. Zakładając że AFL jest już zainstalowany (proces ten został opisany TUTAJ), plik ten możemy skompilować wykonując komendę:

afl-gcc test.c -o test

Program będzie zachowywał się zgodnie z kodem źródłowym, ale teraz AFL może użyć swoich zaawansowanych algorytmów do przeprowadzenia testów.

Po przekompilowaniu przykładu należy wyjaśnić American Fuzzy Lopowi jak go uruchomić. Projekt Zalewskiego potrzebuje do pracy przykładowego pliku wejściowego oraz podania komendy, przy pomocy której go uruchomi. Jako że nasz przykład po prostu czyta dane z terminala, pod Linuksem ta komenda to po prostu “./test“. Wiemy też, że każdy tekst nie zaczynający się od cyfry “2” nie powoduje awarii – otwórzmy więc edytor tekstowy i stwórzmy plik z dowolnym takim napisem, po czym zapiszmy go gdzieś. Następnie należy przenieść go do oddzielnego katalogu, który nazwiemy katalog_wejsciowy. Chcemy także, żeby AFL gdzieś zapisał swój wynik działania – jeśli chcemy, zeby katalog nazywał się na przykład “wynik_AFLa“, cała komenda będzie brzmieć:

afl-fuzz -i katalog_wejsciowy -o wynik_AFLa -m none — ./test

Przełącznik “-m none” mówi AFLowi, że nie chcemy testowanemu programowi ograniczać dostępu do pamięci – bez niego początek pracy z narzędziem bywa denerwujący. Jeśli wszystko poszło dobrze, zobaczymy ekran podobny do poniższego:

American_fuzzy_lop's_afl-fuzz_running_on_a_test_program

Czerwony napis “uniq crashes” oznacza, że AFLowi udało się coś znaleźć i skończyliśmy pracę. Możesz wcisnąć CTR+C i zerknąć do katalogu “wynik_AFLa”. Znajdziesz tam katalog “crashes” z plikiem, którego nazwa zaczyna się od “id:”. Łatwo się przekonać, że jego pierwszy bajt to cyfra “2” i faktycznie crashuje program. Wygląda na to, że się udało. Skoro już rozumiesz jak uruchomić AFLa na prostym programie, zajmijmy się czymś dużo większym.

afl-sid – uproszczona rekompilacja pod AFLa

Nasz program “Witaj, swiecie” można łatwo przekompilować pod afl-gcc, ale każdy kto próbował skompilować coś dużego pod Linuksem rozumie, że czasem to naprawdę trudne zadanie. Często musimy doinstalować wiele bibliotek i przestrzegać skomplikowanej procedury budowania programu. Samo zadanie staje się jeszcze trudniejsze kiedy używamy kompilatora innego niż domyślny – dlatego postanowiłem skorzystać z dobrodziejstw systemu automatycznego budowania paczek do Debiana i stworzyć w Dockerze środowisko, który uprości cały proces. Jeśli nie rozumiesz, co to znaczy, śpieszę z wyjaśnieniem.

Ci, którzy nie mieli do czynienia z Dockerem muszą tylko wiedzieć, że uruchomimy coś w rodzaju oddzielnego systemu operacyjnego, gdzie możemy bezpiecznie eksperymentować. Aby zacząć pracę, zainstaluj Dockera i uruchom

sudo docker run -ti d33tah/afl-sid bash

Docker pobierze kopię mojego projektu, po czym uruchomi sesję terminala. Przy okazji, jeśli szukasz kodu źródłowego tego projektu, znajdziesz go TUTAJ.

Budowanie większości projektów ze wstawkami AFLa jest tu od razu połączone z innym przydatnym narzędziem zwanym ASAN (Address Sanitizer). Aby przygotować wybrany projekt do pracy, wykonaj “aflize NAZWAPROJEKTU‘, gdzie jako nazwę projektu należy podać nazwę interesującej nas paczki Debiana.

Dla przykładu, dla projektu Gnuplot będzie to “aflize gnuplot5-qt“, a jeśli zamiast tego chcesz sfuzzować GNU Bison, będzie to po prostu “aflize bison“. Potem możesz pójść na kawę i wrócić kiedy kompilacja się skończy. Jeśli wszystko poszło dobrze, możesz użyć paczek wygenerowanych przez afl-sida uruchamiając

dpkg -i /root/pkgs/* ; apt-get -f install -y

Teraz afl-sid ma ten projekt zainstalowany w wersji działającej pod AFLem i możemy przejść do fuzzowania. Ponownie potrzebujemy stworzyć katalog i umieścić w nim przykładowy plik z danymi wejściowymi.

AFL często potrafi wywnioskować bardzo dużo o poprawnym formacie pliku wejściowego nawet zaczynając od nieprawidłowego przykładu, więc zwykle po prostu tworzę plik z napisem “hej” i patrzę gdzie to prowadzi. Po utworzeniu takiego pliku, uruchom komendę

afl-fuzz -i katalog_wejsciowy -o wynik_AFLa -m none -- gnuplot

Zauważ, że nie wpisałem “./gnuplot”, bo po instalacji paczek wygenerowanych przez aflize program dostępny jest już nie tylko z aktualnego katalogu; zamiast tego jest już w $PATH. “-m none” jest już też praktycznie wymagane, bo program został od razu zbudowany z Address Sanitizer, który pozwoli nam zauważyć bardziej subtelne bugi. Jeśli wszystko poszło dobrze, AFL pokaże ekran z poprzedniego screenshota, na którym licznik “total paths” będzie rósł z czasem. Dobrze jest pozwolić fuzzerowi popracować przez kilka godzin, dni lub nawet tygodni – zależnie od złożoności fuzzowanego projektu i prędkości naszego komputera. Kiedy zdecydujesz skończyć fuzzowanie, wciśnij CTR+C i znów przejrzyj katalog “wynik_AFLa”. Cały proces możesz zobaczyć na screencaście przedstawiającym fuzzing GNU Bison.

W razie pytań, przeczytaj dokumentację załączoną do kodu źródłowego AFLa – zawiera bardzo dużo przydatnych informacji. Możesz też zadać pytanie w dziale “komentarze” poniżej lub skontaktować się ze mną osobiście. Nie obiecuję, że odpowiem na wszystkie pytania, ale mogę spróbować. Nie zapomnij też zgłosić znalezionych bugów autorom projektów, które fuzzowałeś! W ten sposób pomożesz nam wszystkim.

OK, co teraz?

Wyjaśniłem czemu fuzzowanie jest ważne i jak można je zastosować poświęcając względnie niewiele swojego czasu. Co możesz zrobić z tą wiedzą? Jeśli obchodzi Cię bezpieczeństwo informatyczne, zdecydowanie powinieneś zgłosić znalezione błędy autorom fuzzowanych projektów. Możesz też namawiać ich, żeby sami je fuzzowali, używając plików które pokrywają jak najwięcej kodu. Możesz też dołączyć się do społeczności American Fuzzy Lopa i wspólnie pracować nad ulepszeniem jego i związanych z nim projektów. W tym celu zasubskrybuj listę mailingową afl-users, powiedz coś o sobie i o tym jak możesz się przydać. Przy odrobinie szczęścia sprawisz, że świat Wolnego Oprogramowania będzie odrobinę bezpieczniejszy.

Autorem poniższego wpisu jest Jacek Wielemborek, jeden z programistów projektu Nmap, a od niedawna fascynat fuzzingu. Poza security interesują go też duże zbiory danych, takie jak na przykład Internet Census 2012. Jeśli chciałbyś opublikować na Niebezpieczniku swój artykuł, daj nam znać.

Przeczytaj także:

34 komentarzy

Dodaj komentarz
  1. Full thumbs up!

  2. A skąd wiadomo, że ten kompilator AFL nie umieszcza własnych “ciemnych wstawek”?
    Z drugiej strony, poprawiony program można skompilować na zwykłym kompilatorze…
    Czy w tym zakresie należy być podejrzliwym?

    • nie nalezy byc podejrzliwym. to jest Icamtuf, zywa legenda niczym j00ru czy Gynvael :) poza tym jak napisales – na produkcje kompilujesz sobie poprawione źródła bez wstawek z AFL.

    • Nie potrzebujesz (a właściwie nawet nie powinieneś) używać binarek skompilowanych przy pomocy afl-gcc w środowisku produkcyjnym. Założenie jest takie, że budujesz je tylko na potrzeby fuzzowania. Co do zaufania do kodu generowanego przez AFL – kod źródłowy jest dostępny i wcale nie ma go tak dużo, polecam lekturę a w razie potrzeby chętnie dam wskazówki jeśli zapytasz na afl-users ;)

      Poprawiony program owszem można skompilować pod zwykłym GCC i tak naprawdę afl-gcc jest tylko wrapperem, który do wygenerowanego kodu assemblera dodaje komunikację przez SHM o odwiedzonych adresach pamięci kodu.

    • Nie trzeba. Fuzzujesz patrzysz gdzie był bląd poprawiasz i kompilujesz normalnie.
      …albo znów fuzzujesz, poprawiasz itd…

    • Myślę, że sam sobie odpowiedziałeś na to pytanie ;)
      “Z drugiej strony, poprawiony program można skompilować na zwykłym kompilatorze”

    • Po pierwsze, to jest open source. Możesz sam sprawdzić, co robi.

      Po drugie, to raczej oczywiste, że afl-gcc używa się do testów, nie produkcji…

    • @As: naprawdę napisałeś “icamtuf”? L, chłopie, L.

  3. Czy autor zna alternatywy skierowane dla Windowsowych aplikacji niekonsolowych, czyli z fuzzowaniem wejścia myszy/klawiatury? Czy w ogóle coś takiego istnieje?

    • Nie słyszałem o żadnym podobnym do AFLa narzędziu które bez problemu fuzzowałoby aplikacje z GUI. Problem jest tak naprawdę podwójny: 1) AFL miałby aktualnie problem z programami, które nie wyłączają się po przetworzeniu swojego wejścia, 2) trzebaby wynaleźć jakiś sposób, żeby wejście z klawiatury i myszki zamienić na ciąg znaków i przekazać Xorgowi. Ten drugi pewnie dałoby się jeszcze rozwiązać, ale na dzień dzisiejszy o czymś takim nie słyszałem.

      Może Cię za to zainteresować, że z AFLem dołączany jest kawałek kodu do fuzzowania wiersza poleceń programu ;)

  4. “trzy rzeczy są nieuniknione: śmierć, podatki i błędy w oprogramowaniu”
    O, pozwolę sobie wykreślić podatki z tej listy, te są jak płot, tygrys przeskoczy, szczur się prześlizgnie, a bydło zostanie.

  5. Hehe, a ta technika fuzzy nie jest oparta na Polskim rough sets? czy odwtronie, ciekawe. Fuzzy Something.

    • *polskim

    • Wydaje mi się, że nie – w kodzie źródłowym nie ma o tym wzmianki.

    • Fuzzy Sets i Rough Sets są pokrewne ale wymyślone przez roznych ludzi. Pawlak wymyslił Rough Sets, Zadeh wymyslił Fuzzy Sets. Zadeh był pierwszy – ponad 30 lat przed Pawlakiem.

  6. Rozumiem, że program pozwala wykryć sytuacje w których program się zawiesza lub wyłącza. W jaki sposób pomaga on znaleźć błędy bezpieczeństwa?

    • Często zawieszenie się programu lub jego awaria same w sobie świadczą o problemach z bezpieczeństwem – należy obawiać się błędów typu denial of service.

      Poza tym, crashe mają szczególne znaczenie w przypadku programów napisanych w C – w tym języku istnieje bardzo duże prawdopodobieństwo, że błąd który popełnimy pisząc program doprowadzi do naruszenia ochrony pamięci. Polecam lekturę najnowszego wydania książki “Hacking: Art of Exploitation” (po polsku “Hacking: Sztuka Penetracji”), gdzie opisano to ze szczegółami.

  7. Brakuje flagi “-it” przy poleceniu “docker run …”

    • Dzięki za zgłoszenie! Wygląda na to, że została już dodana.

  8. Czym ta technika różni się od zwykłej analizy statycznej kodu?

    • Analiza statyczna operuje na konkretnych wzorcach podczas gdy AFL przekazuje dane wejściowe z losowymi mutacjami i obserwuje jego działanie. Statyczna analiza nie wykryje na przykład błędów logicznych w kodzie, a program Lcamtufa może je sprowokować i zauważyć.

  9. Ja wolę Selenium, Specflow a w ostatecznośći UFT.

  10. @d33tah powiedz prosze, czy mozna AFLem fuzzowac programy skompilowane (bez dostepnego zrodla) jesli tak to jak najlepiej?

    • AFL ma “Qemu mode”, który może Ci w takiej sytuacji pomóc. Według dokumentacji, będzie działał 2-5x wolniej, ale z grubsza w ten sam sposób. Zerknij do README oraz katalogu qemu_mode.

    • Z tego, co pamiętam, AFL może fuzzować skompilowane programy. W takim wypadku jednak działa on (siłą rzeczy) jedynie jak blind fuzzer.

    • @wiktor: właśnie, że nie ;) Działa normalnie, bo tak naprawdę emuluje wykonywanie tego programu przy pomocy Qemu i aktualizuje mapę SHM przy jumpach. Według dokumentacji (https://github.com/d33tah/afl-fuzz-releases/blob/master/qemu_mode/README.qemu#L87) jedyne co wiadomo, że nie działa to sanitizery (np MSAN/ASAN).

    • Bez “Qemu mode” również można fuzzować skompilowane aplikacje. Jednak wtedy jest to blind fuzzer i dziala.

  11. Pytanie z innej beczki – co to za font w screencaście? ;-)

    • Cholerka, nie wiem mimo że to mój terminal :D Konsole (terminal z KDE4) pod Fedorą 20, jeśli to coś pomoże. Bodajże defaultowy.

    • html inspector mówi:
      Consolas,Menlo,”Bitstream Vera Sans Mono”,monospace,”Powerline Symbols”

  12. Jako osoba, która zajmuje się na co dzień testowaniem złożonych systemów nie bardzo widzę realne zastosowanie tego narządka – generowanie losowych znaków na wejściu i łapanie abortów to są bardzo podstawowe techniki testowania, które sprawdzają się w amatorskich projektach.

    Sensem automatyzacji testów jest nie tylko automatyczne łapanie abortów, ale przede wszystkim skracanie czasu niezbędnego dla identyfikacji, ANALIZY i usunięcia błędu, przy okazji pokrywając znaczny obszar funkcjonalności systemu.

    W żaden sposób to narzędzie nie spełnia tych założeń, a w szczególności w zakresie bezpieczeństwa – może to działa dla prostych błędów typu buffer overflow, ale z tym to radzimy sobie na poziomie unit testów i statycznej analizy kodu, chociażby.

    Bottom-line: dopóki maszyna nie będzie w stanie zrozumieć logiki systemu – bazując na wymaganiach funkcjonalnych, systemów, etc., tak jak to robią ludzie – dopóty będzie tylko i wyłącznie prymitywnym multiplierem prostych, z góry określonych checków do wykonania w jednostce czasu.

    AFL jest zatem niczym więcej. Dobrze jednak, że trwa rozkminka tematu.

    Pozdr.

    • Wydaje mi się, że nie zapoznałeś się z narzędziem przed dokonaniem jego oceny. AFL nie jest zaledwie “ślepym” fuzzerem, ale takim, który przy pomocy algorytmów genetycznych zachowuje się jakby wnioskował o strukturze danych wejściowych, przez co prowokuje przypadki, których standardowe testy nie znajdą. To nie są wyłącznie losowe dane – AFL patrzy na to, ile kodu wzbudza dany przypadek testowy i mutuje go tak, żeby coverage wzrósł. Zerknij na ten URL, powtórz ten eksperyment i sam sprawdź czy ciągle uważasz że to tylko “narządko” – https://lcamtuf.blogspot.com/2014/11/pulling-jpegs-out-of-thin-air.html

      W innym komentarzu już wyjaśniłem, czemu statyczna analiza nie wystarczy – unit testy mają ten sam problem. To po prostu nie są wystarczające narzędzia do kompleksowego testowania programów, które operują na skomplikowanym wejściu.

      Co do analizy – AFL jest całkiem zgodny z filozofią Uniksową, która mówi, że programy powinny robić jedną rzecz i robić ją dobrze. AFL świetnie wykrywa dane, które powodują crashe albo wieszają fuzzowany program. Do analizy efektu działania tych danych istnieją inne narzędzia – na przykład crashwalk (https://github.com/bnagy/crashwalk), który jest przez Zalewskiego do tego celu polecany.

      BTW, to “narządko” w samym ffmpeg znalazło setki unikalnych bugów, w tym wiele z nich dotyczy security. Znasz drugi program, który tak małym nakładem pracy potrafi w choćby minimalnie zbliżonym stopniu osiągnąć takie rezultaty?

  13. To lobber

  14. szczerze to prościej mi skompilować programy z repozytoriów debiana za pomocą afl-gcc niż przy pomocy twojego programu, może dlatego że się nie znam dobrze na dockerze, ale co poradze sobie z jednym błędem to pojawia się następny. Napisz dobry poradnik na temat tego to może zacznę korzystać

Twój komentarz

Zamieszczając komentarz akceptujesz regulamin dodawania komentarzy. Przez moderację nie przejdą: wycieczki osobiste, komentarze nie na temat, wulgaryzmy.