Fuzzer

From
Jump to navigation Jump to search

Fuzzer sind Programme die ein automatisiertes Testen ermöglichen. Im Gegensatz zu herkömmlicher Testsoftware, werden beim fuzzen zufällige Testcases generiert. In erster Linie soll damit getesten werden ob Progamme für nichtspezifizierten Input abstürzen oder hängen bleiben.

Einsatzgebiete

Primär wird Fuzzing zum Suchen von Schwachstellen und Sicherheitslücken benützt. Sicherheitslücken entstehen meist daraus, dass Nutzereingaben nicht hinreichend genau überprüft werden. Beispiele sind z.B. Bufferüberläufe, Formatstingattacken, SQL Injections oder Cross Site Scripting. Durch Fuzzing können Eingaben gefunden werden, die das entsprechende Programm zum absturz bringen. Im Falle von Diensten die über das Netzwerk erreichbar sind, kann dies schon ausreichen um Denial of Service Attacken durchzuführen. In Schwerwiegenderen Fällen kann die Schwachstelle dafür sorgen, dass Fremde Zugriff auf das System erhalten. Eine prominente Lücke, die lange Zeit nicht entdeckt wurde ist Heartbleed. Diese hätte durch Fuzzing entdeckt werden können. https://blog.hboeck.de/archives/868-How-Heartbleed-couldve-been-found.html



AFL (American Fuzzy Lop)

AFL von Michal Zalewski ist ein offener leicht zu bedienender Fuzzer. Es kann unter http://lcamtuf.coredump.cx/afl/ heruntergeladen werden. Für die Benutzung ist fast keine Konfiguration nötig. Es werden lediglich ein Testcase und das zu Testende Programm benötigt. AFL ändert die gegebenen Testcases zufällig ab und testet das Programm auf Abstürze und Ausführgeschwindigkeit.


Installation

In einigen Linux Distributionen ist AFL vorkompiliert enthalten und kann direkt über den jeweiligen Paketmanager heruntergeladen werden. Ist AFL nicht vorhanden, so kann es wie folgt leicht selbst kompiliert werden:

  wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
  tar -xvf afl-latest.tgz 
  cd afl-*
  make

Mit wget wird die letzte Version von afl runtergeladen und dann mit tar entpackt. Dann wird in den afl-* Ordner gewechselt. Hier kann man nun entscheiden ob man afl richtig installiernen möchte (make install) oder die c-Dateien nur übersetzten möchte (make).


Konfiguration

AFL benötigt eigentlich keinerlei Konfiguration. Beim start von afl-fuzz kann es aber sein, dass afl auf Einstellungen im System hinweist, die negativen Einfluss auf afl haben und die dementsprechend umgestellt werden sollten. Hiervon nun zwei Beispiele vorgestellt, die bei uns oft aufgetretten sind.

  [-] Hmm, your system is configured to send core dump notifications to an
      external utility. This will cause issues due to an extended delay
      between the fuzzed binary malfunctioning and this information being
      eventually relayed to the fuzzer via the standard waitpid() API.
  
      To avoid having crashes misinterpreted as hangs, please log in as root
      and temporarily modify /proc/sys/kernel/core_pattern, like so:
  
      echo core >/proc/sys/kernel/core_pattern
  
  [-] PROGRAM ABORT : Pipe at the beginning of 'core_pattern'
           Location : check_crash_handling(), afl-fuzz.c:6926


  [-] Whoops, your system uses on-demand CPU frequency scaling, adjusted
      between 781 and 3710 MHz. Unfortunately, the scaling algorithm in the
      kernel is imperfect and can miss the short-lived processes spawned by
      afl-fuzz. To keep things moving, run these commands as root:
  
      cd /sys/devices/system/cpu
      echo performance | tee cpu*/cpufreq/scaling_governor
  
      You can later go back to the original state by replacing 'performance' with
      'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ
      to make afl-fuzz skip this check - but expect some performance drop.
  
  [-] PROGRAM ABORT : Suboptimal CPU scaling governor
           Location : check_cpu_governor(), afl-fuzz.c:6988

Die Fehlermeldungen sind selbsterklärend und können mit den angegebenen Hinweisen leicht behoben werden. Die Einstellungen sind nur temporär und müssen nach einem neustart des Systems wiederholt werden.

Testbeispiele

Beispiel1.c:

  #include <stdio.h>
  #include <string.h>
  
  int main (int argc, char **argv) {
    char test[10];
    gets(test);
    printf("Eingabe: %s\n",test);
    return 0;
  }

Beispiel1.c hat ein offensichtliches Problem mit Buffer Overflows. Dies soll der Fuzzer für uns herausfinden. Erstmal übersetzten wir Beispiel1.c mit "gcc -o Beispiel1 Beispiel1.c". Dann setzten wir den Fuzzer auf Beispiel1 an mit: "afl-fuzz -n -i testcases/other/text/ -o out Beispiel1". Mit -n läuft afl als "dumb" fuzzer. Das heißt er hat keine nähere Informationen über die interne Strucktur und den internen Aufbau von Beispiel1 und kann diese somit auch nicht nutzen. Mit -i wird ein Ordner angegeben der die Testcases enthält die als Input für das Programm verwendet werden sollen. In testcases/other/text/ ist nur die hello_world.txt von hello drin steht. Das heißt mit dem Word hello fängt der fuzzer als eingabe an und verändert dies danach auf unterschiedliche weise. Der Ausgabeordner wo die Ergebnisse und anderes gespeichert werden wird mit -o angegeben und muss gegebenenfalls erst erstellt werden. Beispiel1 ist unser Programm was wir fuzzen wollen. Die Eingaben die zu einen Crash oder Hang geführt haben werden im Ausgabeordner unter Crashes oder Hangs gespeichert. Crashes sind mit hoher Warscheinlichkeit richtige Crashes. Bei Hangs muss man durchprüfen ob die Eingaben wirklich zu einen Hang geführt haben, hier gibt es viele False Positives. Also zählen letztendlich nur Crashes. Testen können wir diese mit "cat out/crashes/id:000000* | Beispiel1". Mit "cat out/.cur_input" kann man sich die gerade genutzte Eingabe ansehen.

Beispiel2.c:

  #include <stdio.h>
  #include <stdlib.h>
  
  int main (int argc, char **argv) {
    char buf [128];
    fgets(buf,128,stdin);
    printf(buf);
    printf("Hello\n");
    return 0;
  }

Beispiel2.c hat ein offensichtliches Problem mit Formatstringangriffen. Dies soll der Fuzzer für uns herausfinden. Erstmal übersetzten wir Beispiel2.c mit "gcc -o Beispiel2 Beispiel2.c". Zusätzlich fügen wir einen weiteren testcase unter testcases/other/text/ hinzu, indem wir eine Datei formatstring.txt erzeugen und dort %s reinschreiben. Um von hello mittels mutation auf irgendetwas mit %s%s%s zu kommen, benötigt der Fuzzer circa eine Stunde. Gibt man aber einen zusätlichen testcase an, der %s enthält, dauert dies nur wenige Minuten. Dann setzten wir den Fuzzer auf Beispiel2 an mit: "afl-fuzz -n -i testcases/other/text/ -o out Beispiel2".

Diese Art des Fuzzens mit -n (dumb Fuzzer) sollte aber inbedingt vermieden werden. Da es intelligentere und damit wesentlich effizientere Arten des Fuzzens gibt. Für die oben gezeigten Minibeispiele reicht es noch aus, aber längere Codebeispiele sollte man damit nicht fuzzen.


Instrumentation

Um effektiv zu funktionieren, benötigt AFL Informationen über den Programmablauf des zu testenden Programmes. AFL kann somit die Eingaben geschickter variieren um neue Pfade im Programm zu finden. Im naiven Ansatz ist es AFL nicht möglich herauszufinden ob das Programm immer an der selben Stelle terminiert. Durch die Instrumentation kann die Codeabdeckung analysiert werden. Generell ist wes wahrscheinlicher einen Fehler zu finden, je mehr Code des Testprogrammes durchlaufen wird.

Für die Instrumentation bieten sich dem Anwender zwei verschiedene Möglichkeiten:

  1. Compilieren des Testprogramms: Hierfür wird der Quellcode des Programms benötigt. AFL liefert die nötigen Compiler afl-gcc, afl-g++ und afl-clang mit. Diese Vorgehensweise bietet die beste Performance. Die Compiler fügen bei bedingten Sprüngen zusätzliche Sprungbefehle in AFL Bibliotheken ein. Dadurch erhällt AFL die nötigen Informationen über den Programmablauf.
  2. Benutzung des Emulators QEMU: QEMU emuliert den Programmablauf und kann somit die nötigen Informationen über den Ablauf an AFL weiterleiten. Der Vorteil von QEMU ist die einfache Benutzung, es wird beim Start von afl-fuzz mit -Q aktiviert. Das Testprogramm muss vorher nicht compiliert werden, was eine Analyse von Programmen ermöglicht, deren Quellcode nicht offen liegt. Weiterhin können auch Systemfremde Binaries getestet werden (Bsp: ARM auf x86). Nachteil ist die schlechte Performance. Die Geschwindigkeit erhöht sich um den Faktor 2 bis 5. Desweiteren wird darauf hingewiesen, dass es einige Bugs geben kann, die durch die Benutzung von QEMU auftreten.

Dictionaries

Dictionaries sind mit den Testcases die einzige Möglichkeit auf den Ablauf von AFL einfluss zu nehmen. Dies sind einfache Textdateien mit Key-Value Paaren.

Auszug eines Dictionary für HTML:

  tag_a="<a>"
  tag_abbr=""
  tag_acronym="<acronym>"
  tag_address="<address>"
  tag_annotation_xml="<annotation-xml>"
  tag_applet="<applet>"
  tag_area="<area>"
  tag_article="<article>"
  tag_aside="<aside>"
  tag_audio="<audio>"
  tag_base="<base>"
  tag_basefont="<basefont>"
  tag_bdi=""
  tag_bdo=""
  ...

Dictionaries ermöglichen es Wörter anzugeben die häufig in dem Input für das Testprogramm vorkommen. Allein durch zufällige Mutation würde es sonst zu lange dauern, bis der Input die bekannten Schlüsselwörter enthällt oder die erwartete Syntax aufweist. AFL liefert einige Dictionaries für bekannte Dateiformate wie HTML, XML, PNG, JPEG, PDF, SQL, JavaScript usw. mit.

Erwartet das Testprogramm Input in dem feste Wörter vorkommen empfiehlt es sich vorher eine solche Datei zu erstellen und diese beim Start des Fuzzers mit -x [pfad] anzugeben.

Syntax

AFL bietet keine Unterstützung für Syntaxtemplates. Erwartet das Testprogramm Input mit einer bestimmten Syntax so bieten Dictionaries die einzige Möglichkeit diese zu konstruieren. Die Syntax wird dabei nicht explizit angegeben, sonder ergibt sich implizit aus den angegebenen Wörtern und dem Feedback, welches AFL durch die Instrumentation erhällt.

Address Sanitizer

TODO

Parallelisierung

TODO

Sulley