Der Tag, an dem ich das Fuzzing geliebt habe

Ursprünglicher Autor: Chris Wellons
  • Übersetzung
2007 habe ich ein paar Modding- Tools für den Freelancer Space Simulator geschrieben . Die Spielressourcen werden im Binär-INI- oder BINI-Format gespeichert. Wahrscheinlich wurde das binäre Format aus Gründen der Leistung gewählt: Der Download und das Lesen solcher Dateien ist schneller als der beliebige Text im INI-Format.

Die meisten Spielinhalte können direkt aus diesen Dateien bearbeitet werden, indem sie Namen, Preise von Waren, Statistiken von Raumschiffen ändern oder sogar neue Schiffe hinzufügen. Binäre Dateien lassen sich nur schwer direkt ändern. Daher werden sie normalerweise in Text-INI konvertiert, in einem Texteditor geändert und dann wieder in das BINI-Format konvertiert und die Dateien im Spieleverzeichnis ersetzt.

Ich habe das BINI-Format nicht analysiert, und ich bin nicht der Erste, der lernt, wie man sie bearbeitet. Aber mir gefielen die vorhandenen Werkzeuge nicht, und ich hatte meine eigene Vision, wie sie funktionieren sollten. Ich bevorzuge die Oberfläche im Unix-Stil, obwohl das Spiel selbst unter Windows läuft.

Zu dieser Zeit wurde ich gerade mit den Tools yacc (eigentlich Bison ) und Lex (eigentlich Flex ) sowie mit Autoconf vertraut gemacht, also habe ich sie verwendet. Es war interessant, diese Tools in der Branche auszuprobieren, obwohl ich andere Open Source-Projekte sklavisch nachgeahmt hatte, ohne zu verstehen, warum alles so gemacht wurde. Aufgrund der Verwendung von yacc / lex und der Erstellung von Konfigurationsskripten war ein vollwertiges Unix-ähnliches System erforderlich. Das ist alles in gesehenOriginalversion der Programme .

Das Projekt erwies sich als sehr erfolgreich: Ich selbst habe diese Werkzeuge erfolgreich eingesetzt, und sie waren in verschiedenen Kollektionen für Freelancer-Modding erschienen.

Umgestaltung


Mitte 2018 kehrte ich zu diesem Projekt zurück. Haben Sie sich Ihren alten Code schon einmal mit dem Gedanken angeschaut: Woran haben Sie überhaupt gedacht? Mein INI-Format erwies sich als viel härter und strenger als nötig, die Aufnahme der Binaries war zweifelhaft und der Build funktionierte nicht einmal einwandfrei.

Dank zehn Jahren zusätzlicher Erfahrung wusste ich mit Sicherheit, dass ich diese Werkzeuge jetzt viel besser schreiben werde. Und ich habe es in ein paar Tagen getan und sie von Grund auf neu geschrieben. In der Master-Niederlassung auf Github liegt nun dieser neue Code.

Ich mag es, alles so einfach wie möglich zu machen , deshalb habe ich autoconf zugunsten eines einfacheren und tragbaren Makefiles aufgegeben. Es gibt kein Yacc oder Lex mehr, und der Parser wird von Hand geschrieben. Es wird nur das entsprechende tragbare C verwendet. Das Ergebnis ist so einfach, dass ich ein Projekt mit einem kurzen Befehl aus Visual Studio zusammenstelle , sodass das Makefile nicht so notwendig ist. Wenn Sie ersetzen stdint.h auf typedef, können Sie auch erstellen und ausführen binitools unter DOS .

Die neue Version ist schneller, kompakter, sauberer und einfacher. In Bezug auf die INI-Eingabe ist es viel flexibler, sodass es einfacher zu verwenden ist. Aber ist es wirklich richtig?

Fuzzing


Ich habe mich seit vielen Jahren für Fuzzing interessiert , besonders für afl (amerikanischer Fuzzy-Lop). Aber er beherrschte es nicht, obwohl er einige Werkzeuge testete, die ich regelmäßig verwende. Fuzzing fand aber nichts Bemerkenswertes, zumindest bevor ich aufgab. Ich habe meine JSON-Bibliothek getestet und aus irgendeinem Grund auch nichts gefunden. Es ist klar, dass mein JSON-Parser nicht so zuverlässig sein kann, richtig? Aber das Fuzzing zeigte nichts. (Wie sich herausstellte, ist meine JSON-Bibliothek immer noch recht zuverlässig, hauptsächlich dank der Bemühungen der Community!)

Jetzt habe ich einen relativ neuen INI-Parser. Obwohl er den ursprünglichen Satz von BINI-Dateien im Spiel erfolgreich analysieren und korrekt zusammenstellen kann, ist seine Funktionalität wahrlichnicht getestet Sicherlich wird hier Fuzzing etwas finden. Um afl mit diesem Code auszuführen, müssen Sie außerdem keine einzige Zeile schreiben. Die Standardwerkzeuge arbeiten mit Standardeingaben, was ideal ist.

Angenommen, Sie haben die erforderlichen Tools installiert (make, gcc, afl). So einfach laufen die Fuzzing-Binitools:

$ make CC=afl-gcc
$ mkdir in out
$ echo '[x]' > in/empty
$ afl-fuzz -i in -o out -- ./bini

Das Dienstprogramm biniakzeptiert INI-Eingaben und gibt eine BINI aus. Daher ist es wesentlich interessanter, sie zu prüfen als das umgekehrte Verfahren unbini. Da unbinider Fuzzer relativ einfache Binärdaten analysiert, hat er (wahrscheinlich) nichts zu suchen. Ich habe es aber trotzdem überprüft.



In diesem Beispiel habe ich den Standard-Compiler für afl ( CC=afl-gcc) in die GCC-Shell geändert . Hier ruft afl GCC im Hintergrund auf, fügt jedoch der Binärdatei ein eigenes Toolkit hinzu. Beim Fuzzing afl-fuzzwird dieses Programm zur Überwachung des Programmausführungspfads verwendet. Die afl-Dokumentation erläutert die technischen Details.

Ich habe auch Eingabe- und Ausgabeverzeichnisse erstellt, indem ich ein minimales Arbeitsbeispiel in das Eingabeverzeichnis eingefügt habe, das afl einen Startpunkt gibt. Beim Start mutiert er die Eingabewarteschlange und überwacht die Änderungen während der Programmausführung. Das Ausgabeverzeichnis enthält die Ergebnisse und vor allem den Korpus der Eingabedaten, die eindeutige Ausführungspfade verursachen. Mit anderen Worten, am Ausgang eines Fuzzers werden viele Eingaben getestet, wobei viele verschiedene Grenzszenarien getestet werden.

Das interessanteste und schrecklichste Ergebnis - ein völliger Misserfolg des Programms. Als ich Fuzzer für Binitools zum ersten Mal auf den Markt brachte, binifand ich viele solcher Fehler. Innerhalb weniger Minuten entdeckte afl einige subtile und interessante Fehler in meinem Programm, die unglaublich nützlich waren. Fuzzer fand selbst das unwahrscheinlichveralteter Zeigerfehler durch Überprüfen der unterschiedlichen Reihenfolge der verschiedenen Speicherzuordnungen. Dieser spezielle Fehler war ein Wendepunkt, durch den ich den Wert von Fuzzing erkennen konnte.

Nicht alle gefundenen Fehler führten zu Abstürzen. Ich untersuchte das Problem und untersuchte, welche Eingaben zu einem erfolgreichen Ergebnis führten und welche nicht, und sah zu, wie das Programm verschiedene Extremfälle behandelte. Sie wies einige Eingaben zurück, von denen ich dachte, dass sie damit umgehen würden. Umgekehrt verarbeitete sie einige Daten, die ich für falsch hielt, und interpretierte sie auf unerwartete Weise. Selbst nachdem ich Fehler mit Programmabstürzen behoben hatte, änderte ich die Parser-Einstellungen, um jeden dieser unangenehmen Vorfälle zu beheben.

Erstellen einer Testsuite


Sobald ich alle vom Fuzzer erkannten Fehler korrigiert und den Parser so eingestellt habe, dass er in allen Grenzsituationen funktioniert, habe ich eine Reihe von Tests aus dem Fuzzer-Datenfall durchgeführt - allerdings nicht direkt.

Zuerst habe ich einen Fuzzer parallel laufen lassen - dieser Vorgang wird in der afl-Dokumentation erklärt -, also habe ich viele redundante Eingangsdaten erhalten. Mit Redundanz meine ich, dass die Eingabedaten unterschiedlich sind, aber denselben Ausführungspfad haben. Glücklicherweise hat afl ein Werkzeug, um damit umzugehen: das afl-cminMinimierungswerkzeug für Körper. Die zusätzlichen Eingänge entfallen.

Zweitens waren viele dieser Eingaben länger als nötig, um ihren eindeutigen Ausführungspfad aufzurufen. Es half afl-tmin, das Minimieren von Testfällen, was den Testfall reduzierte.

Ich habe gültige und ungültige Eingaben getrennt - und im Repository geprüft. Schau dir all diese dummen Eingänge an, die der Fuzzer erfunden hat , basierend auf einem einzigen Mindesteintrag:


Hier ist der Parser tatsächlich in einem Zustand eingefroren, und die Testsuite stellt sicher, dass sich ein bestimmter Build auf eine ganz bestimmte Weise verhält . Dies ist besonders hilfreich, um sicherzustellen, dass Builds, die von anderen Compilern auf anderen Plattformen erstellt wurden, sich in ihrer Ausgabe tatsächlich ähnlich verhalten. Meine Testsuite hat sogar einen Fehler in der dietlibc-Bibliothek aufgedeckt, weil binitools die Tests nach dem Binden nicht bestanden hat. Wenn am Parser nicht unerhebliche Änderungen vorgenommen werden müssten, müsste ich tatsächlich die aktuelle Testsuite aufgeben und noch einmal von vorne beginnen, so dass afl ein ganz neues Paket für den neuen Parser generiert.

Natürlich hat sich Fuzzing als kraftvolle Technik bewährt. Er fand eine Reihe von Fehlern, die ich niemals alleine entdecken konnte. Seitdem bin ich kompetenter geworden, um andere Programme zu testen - nicht nur meine eigenen - und habe viele neue Fehler gefunden. Jetzt hat fuzzer einen festen Platz unter den Tools in meinem Entwickler-Kit eingenommen.

Jetzt auch beliebt: