-
Die Erfindung betrifft computer-implementierte Verfahren zum Fuzzing einer Zielsoftware, ein entsprechendes Computerprogramm sowie ein entsprechendes Fuzzing-System.
-
Stand der Technik
-
Fuzzer auf dem neuesten Stand der Technik, wie AFL, Honggfuzz oder Libfuzzer, stellen auf Mutation basierendes, durch Abdeckung geführtes Fuzzing zum Prüfen von Software mit wenig Mühe bereit. Deshalb wird in jeder Runde eine Seed-Eingabe aus dem Eingangscorpus ausgesucht, zufällig mutiert und zum Zielprogramm gesendet. Wenn die neu erzeugte Eingabe ein zuvor nicht gesehenes Verhalten (wie etwa neue ausgeführte Codepfade) ausgelöst hat, wird sie zum Eingangscorpus hinzugefügt. Auf diese Weise kann der Eingangsraum eines Programms mit wenig oder keiner Kenntnis über das Eingangsformat erkundet werden.
-
In
DE 10 2021 207248 wird ein Verfahren zum systematischen Mehrfach-Prozess-Schnittstellen-Fuzzing beschrieben.
DE 10 2019 211037 offenbart ein Verfahren zur effizienten Kombination dynamischer und statischer Analysen von Systemen.
-
Offenbarung der Erfindung
-
Offenbart wird ein computer-implementiertes Verfahren zum Fuzzing einer auf einem Computer laufenden Zielsoftware, das die folgenden Schritte umfasst:
- - Abfangen einer Leseoperation der Zielsoftware an einen geteilten Speicher,
- - Ersetzen von Daten der abgefangenen Leseoperation durch Fuzz-Daten zur Erzeugung von Eingangsdaten für die Zielsoftware,
- - Prüfen der Zielsoftware mit den Eingangsdaten.
-
Bei einer bevorzugten Ausführungsform, die besonders leicht zu implementieren ist, wird das Fuzz-Prüfverfahren mit in einem Emulator laufender Zielanwendung realisiert. Ein Emulator erlaubt vollständige Kontrolle über Speicherverwaltung. Bei einer alternativen Ausführungsform können Leseoperationen an der geteilten Speicherregion durch entsprechendes Präparieren des Programms, wenn es kompiliert wird, manipuliert werden. Es kann derart kompiliert werden, dass für jeden Speicherzugriff geprüft wird, ob Fuzz-Daten eingefügt werden sollen oder nicht.
-
Um die Leseoperation automatisch und zuverlässig zu identifizieren, kann sie durch Abfangen von Systemaufrufen an ein Betriebssystem des Computers detektiert werden.
-
Für ein effizientes Fuzzing werden die zur Erzeugung der Eingangsdaten verwendeten Fuzz-Daten vorzugsweise im Voraus erzeugt, vorabgerufen und in einem Puffer gespeichert. Wenn die Fuzz-Daten erschöpft sind, wird Fuzzing der Zielsoftware vorzugsweise beendet und es kann ein neuer Fuzz-Zyklus gestartet werden. Eine Fuzz-Eingabe für den neuen Fuzz-Zyklus ist vorzugsweise von Rückmeldung aus einem früheren Fuzz-Zyklus abhängig.
-
Um sich auf die Hauptfunktionen einer Software zu konzentrieren und frühe Beendigung des Verfahrens zu vermeiden, können Leseoperationen gemäß einer Initialisierungsroutine ignoriert werden, das heißt, es werden keine Daten für diese ersetzt und keine Fuzz-Prüfung für diese durchgeführt.
-
Für schnelles Fuzz-Prüfen kann das Fuzzing für einen Zwischenzustand der Zielsoftware gestartet werden.
-
Mit dem vorgeschlagenen Verfahren kann effizientes Fuzzing an geteilten Speicherregionen erreicht werden, während ein eventuelles zustandsabhängiges Verhalten des Zielprogramms respektiert wird.
-
Ausführungsformen der Erfindung werden unter Bezugnahme auf die beigefügten Zeichnungen erläutert.
- 1 zeigt schematisch eine beispielhafte Ausführungsform des vorgeschlagenen Speicher-Fuzzing-Verfahrens und -Systems.
- 2 zeigt schematisch ein beispielhaftes Flussdiagramm für eine Ausführungsform der vorgeschlagenen Verfahren.
-
Beschreibung der Ausführungsformen
-
Die vorliegende Offenbarung betrifft das dynamische Software-Prüfverfahren Fuzzing, das auf Anwendungen angewandt wird, die mittels geteilter Speicherregionen kommunizieren. Eine geteilte Speicherregion ist eine Speicherregion, die zwischen mehreren Prozessen geteilt wird, und wird gewöhnlich durch das Betriebssystem verwaltet.
-
Fuzzing oder Fuzz-Prüfung ist eine automatisierte Software-Prüftechnik, bei der ungültige, unerwartete oder zufällige Daten als Eingaben für ein Ziel-Computerprogramm bereitgestellt werden und seine Reaktion überwacht wird, z. B. ob die Software abstürzt oder eingebaute Code-Assertions fehlschlagen.
-
Die Programme, die automatisch Eingaben erzeugen, werden als Fuzzer oder Fuzz-Engines bezeichnet. Sie werden gewöhnlich entwickelt, um beliebige zu prüfende Software generisch zu prüfen. Sie haben gewöhnlich die Fähigkeiten zum Instrumentieren von Code, Erzeugen von Prüffällen und Laufenlassen von zu prüfenden Programmen. Beliebte Beispiele sind afl und Libfuzzer.
-
Das Softwareprogramm oder die Funktion, das bzw. die über Fuzzing geprüft werden soll, wird als Fuzz-Ziel bezeichnet. Eine Haupteigenschaft eines Fuzz-Ziels sollte sein, dass es potentiell nicht vertrauenswürdige Eingaben konsumiert, die durch den Fuzzer während des Fuzzing-Prozesses erzeugt werden. Die Fuzz-Ziele nehmen oft strukturierte Eingaben an, sowie es z. B. in einem Dateiformat spezifiziert wird. Die durch den Fuzzer erzeugte Eingabe sollte deshalb so erzeugt werden, dass sie vom Parser akzeptiert wird, aber immer noch Eckfälle prüft oder zu überraschendem Verhalten des Ziels führt.
-
Die kombinierte Version eines Fuzzers und eines Fuzz-Ziels wird als Fuzz-Prüfung bezeichnet. Ein Fuzz-Ziel kann instrumentierter Code mit einem an seine Eingaben angehängten Fuzzer sein. Eine Fuzz-Prüfung ist ausführbar. Der Fuzzer kann auch mehrere laufende Fuzz-Prüfungen (gewöhnlich hunderte oder tausende pro Sekunde) jeweils mit etwas verschiedener durch den Fuzzer erzeugter Eingabe starten, beobachten und stoppen.
-
Eine spezifische Eingabe und Prüfdurchlauf von einer Fuzz-Prüfung wird als Prüffall bezeichnet. Für Reproduzierbarkeit werden für gewöhnlich interessierende Durchläufe (die neue Codepfade oder Abstürze finden) abgespeichert. Auf diese Weise kann ein spezifischer Testfall mit seiner entsprechenden Eingabe auch an einem Fuzz-Ziel laufengelassen werden, das nicht mit einem Fuzzer verbunden ist, d. h. in seiner Release-Version
-
Es gibt verschiedene Versionen zur Realisierung von effizientem Fuzzing:
- - Anweisungen können in ein Programm eingefügt werden, um Rückmeldungen aus der Ausführung zu erzeugen (statische Instrumentation). Dies kann durch den Compiler realisiert werden und kann zum Beispiel die erreichten Codeblöcke während Ausführung beschreiben.
- - Die Ausführung eines Programms kann während der Laufzeit gesteuert werden, um Rückmeldung aus der Ausführung zu erzeugen (dynamische Instrumentation). Dies wird hauptsächliche durch Betriebssystemfunktionalitäten oder durch Verwendung von Emulatoren realisiert.
- - Codeabdeckungsinformationen können als Rückmeldung während des Fuzzing verwendet werden, um zu detektieren, ob eine Eingabe die Ausführung neuer Codepfade/Blöcke verursacht hat (durch Abdeckung geführtes Fuzzing).
- - Neue Eingaben können erzeugt werden, indem man eine Menge bekannter Eingaben (Corpus) verwendet und zufällig Mutationen auf diese anwendet (auf Mutation basierendes Fuzzing).
- - Neue Eingaben können von Grund auf erzeugt werden, indem man zum Beispiel Eingangsmodelle oder Eingangsgrammatiken verwendet (auf Generation basierendes Fuzzing).
-
Der Eingangsraum einer geteilten Speicherregion kann durch {0,1}(nm)xT beschrieben werden, wobei n eine natürliche Zahl ist und die Größe der geteilten Speicherregion repräsentiert, m eine natürliche Zahl ist und die Wortlänge des Speichers repräsentiert (gewöhnlich 8, 16, 32 oder 64 Bit) und das T die Menge von Zeitschritten des ausgeführten Zielprogramms repräsentiert. Zum Beispiel kann ein spezifischer Zeitschritt in T als die Anzahl zuvor ausgeführter Anweisungen codiert sein. Diese Konstruktion erlaubt es, dass nach jeder Maschinenanweisung die gesamte geteilte Speicherregion durch einen Attackiererprozess modifiziert werden kann und als Abstraktion verschiedener geteilter Speichermechanismen aus verschiedenen Betriebssystemen dient.
-
Da geteilte Speicherregionen relativ groß sein können und die Eingangsräume sogar noch größer werden, je länger das Zielprogramm läuft, ist naives Fuzzing an der geteilten Speicherregion ineffektiv. Diese Ineffektivität entsteht nicht nur aus der Größe der geteilten Speicherregion, sondern auch aus der fehlenden Kenntnis, wo und wann Lesevorgänge aus der geteilten Speicherregion auftreten. Es ist deshalb relativ wahrscheinlich, dass eine zufällig platzierte Fuzzing-Eingabe vom Ziel nicht gelesen wird.
-
Außerdem könnte, wenn Fuzzing-Daten kontinuierlich in eine geteilte Speicherregion einer einzelnen Instanz geschrieben werden, der Zustand der Zielanwendung während des Fuzzing geändert werden, was bezüglich Eingabeerzeugung und Rückmeldungssammeln in Betracht gezogen werden muss.
-
1 zeigt eine beispielhafte Ausführungsform des vorgeschlagenen Verfahrens und Systems zum Fuzzing geteilten Speichers. Der tatsächliche Fuzzer 2 ist dabei austauschbar, und es ist ihm nicht einmal bewusst, dass Fuzzing an einer Schnittstelle geteilten Speichers auftritt. Er erzeugt und liefert Fuzz-Daten (3) und kann Abdeckungsrückmeldung (4) empfangen. Der Fuzzer 2 kann Zugang zu einem Corpus 1 von Seed-Eingaben haben, z. B. für auf Mutation basierendes Fuzzing. Um geteilte Speicherregionen zu detektieren, werden entsprechende Syscalls 146 durch die Detektionseinheit 16 geteilten Speichers abgefangen. Speicheroperationen 145 werden durch die MMU (Speicherverwaltungseinheit) 15 abgefangen und auf Leseoperationen aus einer geteilten Speicherregion 141 hin untersucht. Wenn dies der Fall ist, wird ein Stück aus dem Fuzz-Datenpuffer 11 in die geteilte Speicherregion 141 geschrieben (Fuzz-Datenstückinjektion 13) und die Ausführung der Zielanwendung 14 wird fortgesetzt.
-
2 zeigt schematisch ein beispielhaftes Flussdiagramm für eine Ausführungsform der vorgeschlagenen Verfahren:
- - Starten des Verfahrens durch Abrufen von Fuzz-Daten in einem Puffer und starten der Zielanwendung in einem Emulator (Schritt 31).
- - Detektieren der geteilten Speicherregionen des Zielprogramms (Schritt 32).
- - Abfangen von Leseoperationen an geteilten Speicherregionen (Schritt 33).
- - Ersetzen der Daten an abgefangenen Leseoperationen durch Injizieren von Teilen von Fuzz-Daten aus dem Puffer am Leseort (Schritt 34). Dieser Schritt kann wiederholt werden, bis immer noch Fuzz-Daten im Puffer verfügbar sind.
- - Wenn die Fuzz-Daten im Puffer erschöpft sind, Beenden des Prozesses, z. B. durch Beenden der Zielanwendung (Schritt 35). Danach kann der Prozess neu gestartet werden.
-
Bei einer bevorzugten Ausführungsform können eventuelle Initialisierungsroutinen der Zielanwendung ignoriert werden.
-
Optionale Ausführungsformen erlauben Fuzzing aus Zwischenzuständen, ohne die gesamte Anwendung neu zu starten, z. B. unter Verwendung von Fork-Mechanismen.
-
Eine vorteilhafte Implementierung würde folgendermaßen aussehen. Wenn die Zielanwendung in einem Emulator läuft, ist eine präzise Steuerung aller Speicheroperationen möglich. Um Leseoperationen am geteilten Speicher zu steuern, müssen zuerst die geteilten Speicherregionen der Zielanwendung erkannt werden. Deshalb werden die entsprechenden Syscalls an das Betriebssystem abgefangen. Wenn eine Leseoperation an einer erkannten geteilten Speicherregion auftritt, ersetzt der Emulator den relevanten Ort im Speicher mit einem entsprechenden Stück aus den zuvor abgerufenen Fuzz-Daten. Dieser Prozess wird wiederholt, bis der Puffer mit den vorabgerufenen Fuzz-Daten erschöpft ist. Wenn die Fuzz-Daten erschöpft sind, wird die Zielanwendung gekillt, der Emulator wird heruntergefahren und der Zyklus wird vom Anfang an neu gestartet. Nach jeder Ausführung wird dem Fuzzer Abdeckungsrückmeldung bereitgestellt, wodurch Erkennung von Eingangssequenzen (codiert im Eingangspuffer) ermöglicht wird, die neue Codepfade auslösen. Die vorliegende Erfindung kann somit leicht in existierende Fuzzing-Einrichtungen und Fuzzing-Kampagnen integriert werden und kann auch zusätzlich mit Ensemble-Fuzzing verwendet werden.
-
ZITATE ENTHALTEN IN DER BESCHREIBUNG
-
Diese Liste der vom Anmelder aufgeführten Dokumente wurde automatisiert erzeugt und ist ausschließlich zur besseren Information des Lesers aufgenommen. Die Liste ist nicht Bestandteil der deutschen Patent- bzw. Gebrauchsmusteranmeldung. Das DPMA übernimmt keinerlei Haftung für etwaige Fehler oder Auslassungen.
-
Zitierte Patentliteratur
-
- DE 102021207248 [0003]
- DE 102019211037 [0003]