
american fuzzy lop - ein sicherheitsorientierter Fuzzer
Ursprünglich entwickelt von Michal Zalewski [email protected].
Siehe QuickStartGuide.txt, wenn du keine Zeit hast, diese Datei zu lesen.
Fuzzing ist eine der leistungsfähigsten und bewährtesten Strategien zur Identifizierung von Sicherheitsproblemen in realer Software; es ist verantwortlich für die überwiegende Mehrheit der bisher gefundenen Fehler bei Remote-Codeausführung und Privilegieneskalation in sicherheitskritischer Software.
Leider ist Fuzzing auch relativ oberflächlich; blinde, zufällige Mutationen machen es sehr unwahrscheinlich, bestimmte Codepfade im getesteten Code zu erreichen, sodass einige Schwachstellen eindeutig außerhalb der Reichweite dieser Technik liegen.
Es gab zahlreiche Versuche, dieses Problem zu lösen. Einer der frühen Ansätze – von Tavis Ormandy entwickelt – ist die Korpus-Destillation. Die Methode stützt sich auf Abdeckungssignale, um aus einem massiven, hochwertigen Korpus von Kandidatendateien eine Teilmenge interessanter Seeds auszuwählen, und fuzzt diese anschließend mit traditionellen Mitteln. Der Ansatz funktioniert außergewöhnlich gut, erfordert jedoch, dass ein solcher Korpus leicht verfügbar ist. Darüber hinaus liefern Blockabdeckungsmessungen nur ein sehr vereinfachtes Verständnis des Programmzustands und sind auf lange Sicht weniger nützlich, um die Fuzzing-Bemühungen zu lenken.
Andere, ausgefeiltere Forschung hat sich auf Techniken wie Programmflussanalyse („concolic execution"), symbolische Ausführung oder statische Analyse konzentriert. Alle diese Methoden sind in experimentellen Umgebungen äußerst vielversprechend, leiden jedoch in der praktischen Anwendung tendenziell unter Zuverlässigkeits- und Leistungsproblemen – und bieten derzeit keine tragfähige Alternative zu „dummen" Fuzzing-Techniken.
American Fuzzy Lop ist ein Brute-Force-Fuzzer, gekoppelt mit einem äußerst einfachen, aber felsenfesten instrumentierungsgesteuerten genetischen Algorithmus. Er verwendet eine modifizierte Form der Kantenabdeckung, um mühelos subtile, lokale Änderungen im Kontrollfluss des Programms zu erfassen.
Vereinfacht ausgedrückt lässt sich der Gesamtalgorithmus wie folgt zusammenfassen:
Benutzerbereitgestellte anfängliche Testfälle in die Warteschlange laden,
Nächste Eingabedatei aus der Warteschlange nehmen,
Versuchen, den Testfall auf die kleinste Größe zu reduzieren, die das gemessene Verhalten des Programms nicht verändert,
Die Datei wiederholt mit einer ausgewogenen und gut recherchierten Vielfalt traditioneller Fuzzing-Strategien mutieren,
Wenn eine der erzeugten Mutationen zu einem neuen, durch die Instrumentierung aufgezeichneten Zustandsübergang führte, die mutierte Ausgabe als neuen Eintrag zur Warteschlange hinzufügen.
Weiter zu 2.
Die entdeckten Testfälle werden außerdem regelmäßig ausgedünnt, um solche zu eliminieren, die durch neuere Funde mit höherer Abdeckung überholt wurden; und sie durchlaufen mehrere weitere instrumentierungsgesteuerte Aufwandsminimierungsschritte.
Als Nebenergebnis des Fuzzing-Prozesses erstellt das Werkzeug ein kleines, in sich geschlossenes Korpus interessanter Testfälle. Diese sind äußerst nützlich, um andere arbeits- oder ressourcenintensive Testverfahren zu befüttern – zum Beispiel zum Belastungstesten von Browsern, Office-Anwendungen, Grafikpaketen oder Closed-Source-Werkzeugen.
Der Fuzzer ist gründlich getestet, um sofort eine Leistung zu liefern, die blindem Fuzzing oder reinen Abdeckungswerkzeugen weit überlegen ist.
Wenn Quellcode verfügbar ist, kann die Instrumentierung durch ein Begleitwerkzeug injiziert werden, das als direkter Ersatz für gcc oder clang in jedem Standard-Build-Prozess für Drittanbieter-Code fungiert.
Die Instrumentierung hat eine recht geringe Leistungsbeeinträchtigung; in Verbindung mit anderen von afl-fuzz implementierten Optimierungen können die meisten Programme so schnell oder sogar schneller gefuzzt werden, als es mit traditionellen Werkzeugen möglich wäre.
Der korrekte Weg, das Zielprogramm neu zu kompilieren, kann je nach den Besonderheiten des Build-Prozesses variieren, aber ein nahezu universeller Ansatz wäre:```shell $ CC=/path/to/afl/afl-gcc ./configure $ make clean all
Für C++-Programme solltest du auch `CXX=/path/to/afl/afl-g++` setzen.
Die Clang-Wrapper (`afl-clang` und `afl-clang++`) können auf dieselbe Weise verwendet werden;
Clang-Nutzer können optional auch einen leistungsfähigeren Instrumentierungsmodus nutzen,
wie in llvm_mode/README.llvm beschrieben.
Beim Testen von Bibliotheken musst du ein einfaches Programm finden oder schreiben, das
Daten von stdin oder aus einer Datei liest und sie an die getestete Bibliothek übergibt. In einem solchen
Fall ist es wichtig, diese ausführbare Datei gegen eine statische Version der
instrumentierten Bibliothek zu linken oder sicherzustellen, dass die richtige .so-Datei zur
Laufzeit geladen wird (normalerweise durch Setzen von `LD_LIBRARY_PATH`). Die einfachste Option ist ein statischer
Build, der normalerweise wie folgt möglich ist:```shell
$ CC=/path/to/afl/afl-gcc ./configure --disable-shared
Wenn Sie beim Aufruf von 'make' AFL_HARDEN=1 setzen, veranlasst das den CC-Wrapper,
automatisch Code-Härtungsoptionen zu aktivieren, die es erleichtern, einfache
Speicherfehler zu erkennen. Libdislocator, eine in AFL enthaltene Hilfsbibliothek (siehe
libdislocator/README.dislocator), kann ebenfalls dabei helfen, Heap-Korruptionsprobleme aufzudecken.
PS. ASAN-Benutzern wird empfohlen, die Datei notes_for_asan.txt auf wichtige Einschränkungen hin zu überprüfen.
Wenn der Quellcode NICHT verfügbar ist, bietet der Fuzzer experimentelle Unterstützung für schnelle, On-the-fly-Instrumentierung von Black-Box-Binärdateien. Dies wird erreicht mit einer Version von QEMU, die im weniger bekannten Modus "user space emulation" läuft.
QEMU ist ein von AFL unabhängiges Projekt, aber Sie können die Funktion bequem erstellen, indem Sie Folgendes tun:```shell $ cd qemu_mode $ ./build_qemu_support.sh
Weitere Hinweise und Anleitungen findest du in qemu_mode/README.qemu.
Der Modus ist etwa 2-5x langsamer als Compile-Time-Instrumentierung, eignet
sich weniger gut für die Parallelisierung und kann einige andere Eigenheiten
aufweisen.
## 5) Auswahl der ersten Testfälle
Um korrekt zu funktionieren, benötigt der Fuzzer eine oder mehrere Startdateien,
die ein gutes Beispiel der Eingabedaten enthalten, die von der Zielanwendung
normalerweise erwartet werden. Es gibt zwei Grundregeln:
- Halte die Dateien klein. Unter 1 kB ist ideal, wenn auch nicht zwingend
erforderlich. Eine Diskussion darüber, warum die Größe wichtig ist, findest
du in [perf_tips.txt](https://github.com/google/afl/blob/master/docs/perf_tips.txt).
- Verwende mehrere Testfälle nur dann, wenn sie sich funktional voneinander
unterscheiden. Es hat keinen Sinn, fünfzig verschiedene Urlaubsfotos zu
verwenden, um eine Bildbibliothek zu fuzzen.
Viele gute Beispiele für Startdateien findest du im Unterverzeichnis testcases/,
das mit diesem Tool geliefert wird.
PS. Wenn ein großer Datenkorpus für das Screening verfügbar ist, möchtest du
vielleicht das Dienstprogramm afl-cmin verwenden, um eine Teilmenge funktional
unterschiedlicher Dateien zu identifizieren, die verschiedene Codepfade im
Zielbinary durchlaufen.
## 6) Fuzzing von Binaries