Taggat med: GDB

Så identifierar du säkerhetsbrister med AFL Fuzzer

AFL står för American fuzzy lop och är en fuzzing-mjukvara utvecklat av övermänniskan Michał Zalewski (lcamtuf) som jobbar på Google. Om Er organisation inte använder fuzz-tester i dagsläget som en del av säkerhetsgranskning eller kvalitétsprocess (QA) så är det läge att börja nu.

AFL är en instrumentell fuzzer och har hittat tusentals olika säkerhetsrelaterade buggar i program och bibliotek såsom libpng, PHP, OpenSSL, putty, Bind, QEMU, curl och procmail för att nämna några.

AFL är öppen källkod och går att köra på x86 Linux, OpenBSD, FreeBSD, och NetBSD. Även går afl på Solaris och macOS och tredjepartskod som körs på windows: winafl.

Jag har skrivit en hel del fuzzers i mina dagar och använt andras ramverk och det första verktyget jag använde var SPIKE runt 2002 som är utvecklat av Dave Aitel. Men sedan dess har det hänt en hel del när det gäller fuzzers och afl är en av de bästa enligt mig.

Nedan följer en guide hur du installerar och kommer igång med AFL:

Installation av afl

Att ladda hem och kompilera afl är relativt enkelt, men jag använder vanligtvis en mac och gillar att jobba med Vagrant (kräver även VirtualBox eller VMware).

Och det finns så klart en färdig Vagrant för just afl:

$ git clone https://github.com/JamieH/vagrant-afl
$ cd vagrant-afl
$ vagrant up
$ vagrant ssh

Det var ju enkelt. Nu kommer det lite krångligare, och det är att få afl att fungera tillsammans med mjukvaran vi vill testa/fuzza.

Kompilera koden med afl

Eftersom afl använder sig av instrumentering så bör du kompilera din kod med afl-gcc eller afl-g++ (afl-clang samt afl-clang++ finns också). Det kan du göra genom att göra följande efter det att du laddat hem källkoden:

$ CC=/usr/local/bin/afl-gcc CCX=/usr/local/bin/afl-g++ ./configure --disable-shared

Detta gör du i mappen där källkoden ligger som du vill fuzza. Just ovan exempel förutsätter även att configure finns i mappen (autoconf). afl stödjer även emulatorn Qemu om du inte skulle ha tillgång till källkoden (blackbox fuzzing).

Skicka in data med afl

Först och främst så måste afl skicka in genererad data i det program vi ska fuzza. Det går att göra på två sätt:

  • Via stdin (consolen)
  • Via en fil som argument

Och det blir genast problem med ovan två punkter om du exempelvis ska fuzza en mjukvara såsom webbserver som tar in data över nätverket. Även så måste mjukvaran stänga ner efter varje förfrågan.

Det finns så klart lösningar på ovan problem, antingen får vi patcha (skriva om koden) för att ta in data från stdin eller använda ett hjälpmedel såsom preeny. Med hjälp av preeny så kan vi fulpatcha anrop mot nätverksfunktionerna socket(), bind(), listen(), och accept() genom följande kommando. Då styrs nätverksanropen om till stdin/stdout:

$ LD_PRELOAD=x86_64-linux-gnu/desock.so afl-fuzz ..osv

Visa vägen

Men innan vi sätter igång med fuzzandet så är det två saker till vi måste lösa. Den första är att kontrollera huruvida instrumenteringen fungerar. Och det gör du med afl-showmap kommandot. Showmap har i stort sett samma argument som afl-fuzz.

Den programvara som jag testar att fuzza är Cyrus imapd och då måste vi även skicka in ett imap-kommando som i detta fall är A LOGOUT samt två miljövariabler:

$ CYRUS_ID=1 CYRUS_SERVICE=foo afl-showmap -m 100 -o /dev/null cyrus-imapd/imap/imapd -X < <(echo "A LOGOUT")
afl-showmap 2.35b by <[email protected]>
[*] Executing 'cyrus-imapd/imap/imapd'...

-- Program output begins --
* OK [CAPABILITY IMAP4rev1 LITERAL+ ID ENABLE AUTH=CRAM-MD5 SASL-IR] vagrant-ubuntu-trusty-64 Cyrus IMAP 3.0.0-beta6-3-gf721e5b server ready
* BYE LOGOUT received
A OK Completed
-- Program output ends --
[+] Captured 1535 tuples in '/dev/null'.
$

Toppen. 1535 tuples (basic blocks) identifierades, det ser bra ut. Om du får ett felmeddelande likt detta:

No instrumentation detected eller failed to map segment from shared object: Cannot allocate memory. Så testa ändra -m flaggan upp till exempelvis 200 som bestämmer hur mycket minne som får förbrukas.

Om jag startar mjukvaran felaktigt eller skickar in ett kommando som inte finns så får jag ingen output och betydligt mindre tuples, se här:

..klipp..

-- Program output begins --
-- Program output ends --
[+] Captured 464 tuples in '/dev/null'.
$

Skapa testfall för fuzzning

Även om afl-fuzz är relativt smart så måste vi föda in något som afl kan jobba vidare med. Och i fallet med imapd som jag testar så är det så klart lämpligt att skicka in olika typer av imap-kommandon. Med lite grep och awk-magi så plockar jag ut alla kommandon som Cyrus imapd stödjer direkt från källkoden:

$ grep strcmp cyrus-imapd/imap/imapd.c|grep cmd.s|awk -F'"' '{print $2}'|sort|uniq > afl/testcases/imap.txt

Och många av de imap-kommandon som finns tar ett eller två argument som vi måste lägga på så att varje testfall hamnar i en egen fil som sedan läses av afl-fuzz:

for a in `cat ../../testcases/imap.txt`; do echo "A $a A A" > $a-A-A.txt; done

Så för att repetera: ett testfall i varje fil i en katalog. Likt detta:

$ cat afl/imap/testcases/Logout.txt
A Logout

Starta fuzzningen med afl-fuzz

Såja, nu vet vi att mjukvaran startar och kan ta input från stdin. Vi har även skapat ett antal testfall och nu återstår bara att starta afl-fuzz.

För att starta fuzzningen kör:

$ CYRUS_ID=1 CYRUS_SERVICE=foo afl-fuzz -m 100 -o afl/imap/findings/ -i - cyrus-imapd/imap/imapd -X

Och då bör några tester genomföras och statusfönstret dyker upp:

AFL fuzzing

Och har vi tur och mjukvaran gått i några timmar/dagar/veckor så dyker några uniq crashes upp. Hangs kan så klart också vara intressant och betyda blockeringsattack (DoS).

Resultaten hamnar i katalogen findings/crashes/ där vi kan analysera vidare vad det är som orsakar en krasch med gdb. Har vi tur så kan vi även skriva en exploit som utnyttjar bristen.

Lop är även en fluffig kaninras, därav bilden av en kanin längst upp i denna guide.

Länkar och vidare läsning

Följande länkar är bra att ha och jag rekommenderar dig att läsa vidare: