Skip to main content
  1. Posts/

Fuzzing Schutzmaßnahmen

Dieser Blogeintrag ist eine Seminararbeit, die ich im Sommersemester 2020 im Rahmen des Seminars Schwachstellensuche am KIT geschrieben habe. Ich habe das LaTeX einmal durch pandoc gejagt, sodass ich es hier auf meinen Blog schmeißen kann. Möglicherweise leidet die Formatierung darunter.

Das Seminar selbst war wie einen Konferenz gestaltet, inklusive Peer-Reviews von anderen Teilnehmern des Seminars mit HotCRP. Dabei habe ich über das Inhaltliche hinaus noch viel über den Prozess des Peer-Review gelernt.

Abstract #

Fuzzing ist eine Technik zur automatisierten Schwachstellensuche in Software. Im Vergleich zu einem menschlichen Analysten ermöglicht Fuzzing kostengünstige Schwachstellensuche im großen Maßstab. Aktuelle Forschung versucht Schutzmaßnahmen zu entwickeln, die Binärprogramme vor Schwachstellensuche durch Fuzzing schützen. Damit sollen kritische Anwendungen vor bösartigen Angreifern geschützt werden, die Fuzzing anwenden.

Diese Arbeit gibt einen breiten Überblick über aktuelle Ansätze Fuzzing zu erschweren, beschreibt deren Wirkungsweise und Anwendbarkeit. Außerdem werden für viele der Schutzmaßnahmen Möglichkeiten zur Entfernung, Umgehung oder Abschwächung genannt.

Alternatives Abstract (kein Teil der Seminararbeit) #

Falls man die Idee auf die Idee kommt sich vor Fuzzern „schützen“ zu wollen, ist der erst Teil der Seminararbeit die perfekte Anleitung dafür. Im zweiten Teil wird dann gezeigt, dass von den Schutzmaßnahmen viel eher zur Kategorie Security-Snakeoil gehört, die einen Angreifer vielleicht etwas nerven aber sicherlich nicht abhalten können. Trotzdem ist es akademisch spanned sich damit zu beschäftigen was eine Worst-Case Eingabe für einen Fuzzer sein könnte. In der Praxis würde ich Fuzzing-Schutzmaßnahmen allerhöchstens für CTF-Challenges empfehlen. Wenn jemand Fuzzing-Schutzmaßnahmen „in-the-wild“ findet, würde ich mich freuen davon zu hören und mir das Programm mal genauer anzuschauen.

Einleitung

Die Fuzzing zugrunde liegende Idee besteht darin, ein Programm auf einer großen Menge von automatisch generierten Eingaben auszuführen und sein Verhalten dabei zu beobachten. Stürzt das Programm auf einer Eingabe beispielsweise ab oder wird ein Speicherzugriff in einen nicht allozierten Bereich beobachtet, so wurde ein Bug gefunden. Dieser Bug dient dann als Grundlage für die Suche nach einer ausnutzbaren Schwachstelle. Die erste Implementierung eines Fuzzers stammt bereits aus den frühen Neunzigerjahren (Miller, Fredriksen, and So 1990; Sutton, Greene, and Amini 2007). Mit seitdem entwickelten neuen Fuzzern, die sich vor allem auch die zunehmende Rechenleistung von heutigen Computern zunutze machen, wurden bereits zahlreiche Schwachstellen gefunden. Alleine ClusterFuzz, eines von mehren Fuzzing-Projekten von Google, hat Stand Februar 2019 mehr als 27000 Bugs gefunden (Arya et al. 2019). Aktuell gibt es Bestrebungen, den Prozess des Fuzzings weiter zu automatisieren (Chang et al. 2017). Gefundene Bugs können automatisiert, bezüglich ihres Potenzials als Schwachstelle ausnutzbar zu sein, kategorisiert werden (Tonder, Kotheimer, and Goues 2018). Es gibt außerdem erste Ansätze, Fuzzing auf eine große Menge von unbekannten Programmen und Bibliotheken anzuwenden, ohne manuelle Anpassungen oder Konfiguration durch Menschen zu erfordern (Ispoglou et al. 2020).

Für einige kritische Programme gibt es das Bestreben, Schwachstellensuche nur autorisierten Parteien zu ermöglichen. Dritten Parteien, die nur über eine Binärversion des Programms verfügen, soll die Schwachstellensuche möglichst erschwert werden. In der Vergangenheit lag der Fokus beim Schutz gegen Schwachstellensuche hauptsächlich auf Obfuscation-Techniken, die Schutz vor menschlichen Analysten bieten (Behera and Bhaskari 2015). Bisher überprüfte Obfuscation-Techniken bieten keinen umfangreichen Schutz gegen Fuzzing (Güler et al. 2019b; Jung et al. 2019b; Banescu et al. 2016). Genau wie Obfuscation machen es Schutzmaßnahmen gegen Fuzzing nicht unmöglich, Schwachstellen durch Fuzzing zu finden, sondern sie vergrößern nur die Kosten und Mühen, die ein Angreifer aufwenden muss. Idealerweise so stark, dass andere Techniken der Schwachstellensuche erfolgversprechender werden (Banescu et al. 2016). Es ist zu erwarten, dass in Zukunft neue Ansätze publiziert werden, die Fuzzing-Schutzmaßnahmen vorschlagen, und andere, die diese wieder umgehen. Hier sind Analogien zu der Entwicklung von Obfuscation-Techniken oder auch zum Schutz gegen automatisierte Schwachstellensuche im Internet möglich. In keinem der Fälle werden bestehende Schwachstellen behoben, sondern nur deren Auffinden erschwert.

Das Ziel dieser Seminararbeit ist es, Ansätze zu beschreiben, die aktuelle Schutzmaßnahmen gegen Fuzzing umgehen oder abschwächen. Hierbei wird in einigen Teilen der Arbeit die Perspektive eines Angreifers eingenommen, der sich mit einem geschützten Programm konfrontiert sieht. Dies hat zum Zweck, Fuzzing-Schutzmaßnahmen in einem realistischen Szenario zu betrachten. Es wird ein Angreifer angenommen, der mit möglichst geringem Aufwand viele Schwachstellen finden möchte. Der Angreifer hat lediglich Zugriff auf das mit Fuzzing-Schutzmaßnahmen versehene Binärprogramm. Weiter wird angenommen, dass der Angreifer Kenntnis von den Fuzzing-Schutzmaßnahmen besitzt. Diese Annahme ist aus zwei Gründen gerechtfertigt. Zum einen, da die zugrundeliegenden Schutzmaßnahmen teilweise zusammen mit Implementierungen veröffentlicht sind (Güler et al. 2019b; Jung et al. 2019b; Edholm and Göransson 2016). Zum anderen da, wie in Kapitel 4 beschrieben, Fuzzing-Schutzmaßnahmen zusätzliches Potenzial für Schwachstellen bieten und eigene Implementierungen im Vergleich zu Standard-Open-Source-Implementierungen ein höheres Risiko dafür bergen, dass solche Schwachstellen über längere Zeit bestehen bleiben.

Kapitel 2 gibt einen Überblick über Kategorien von Fuzzern. Kapitel 3 nennt verwandte Arbeiten im Themenfeld der Fuzzing-Schutzmaßnahmen. Kapitel 4 beschreibt bestehende Arten von Fuzzing-Schutzmaßnahmen. Für die beschriebenen Schutzmaßnahmen werden in Kapitel 5 Möglichkeiten beschrieben, diese in Binärprogrammen zu erkennen. Erkannte Schutzmaßnahmen können dann entfernt, umgangen oder, falls das nicht möglich ist, zumindest berücksichtigt werden.

Hintergrund

Fuzzing ist weit mehr als nur zufällige Eingaben an ein Programm zu geben und zu hoffen, dass es abstürzt. Es gibt verschiedene Arten von Fuzzern, die mit unterschiedlichen Strategien Eingaben erzeugen und deren Auswirkung auf unterschiedliche Arten messen und berücksichtigen. Um Fuzzing-Schutzmaßnahmen verstehen und bewerten zu können, ist zuerst ein Überblick über existierende Arten von Fuzzern notwendig.

Die konzeptionell einfachsten Fuzzer sind sogenannte Black-Box-Fuzzer, auch blinde Fuzzer genannt. Diese erzeugen Eingaben gemäß einer Mutationsengine und nehmen Ausgaben oder einen sichtbaren Absturz des Programms als einziges Feedback vom Programm entgegen. Die Mutationsengine verändert die Eingaben nach bestimmten Mustern. Solche Muster können beispielsweise die Änderung von Zahlen, das Vertauschen von Bytes oder das Einfügen unüblicher Zeichen beinhalten. Ein Beispiel hierfür ist der Fuzzer Radamsa (“Radamsa” n.d.). Es gibt auch Fuzzer, welche die Ausgaben zweier Programme bei gleicher Eingabe miteinander vergleichen, um bei einer Abweichung auf einen Bug in einem der beidem Programme schließen zu können, zum Beispiel zum Testen von Numerik-Bibliotheken. Solche Fuzzer sind im Weiteren nicht Gegenstand der Betrachtung. Diese Arbeit ist außerdem auf das Fuzzen von lokal ausführbaren Programmen fokussiert, wobei die genannten Beispiele auf C-Programmen in einer Linux-Umgebung basieren. Viele der genannten Methoden lassen sich auch in anderen Kontexten und für andere Sprachen anwenden. Black-Box-Fuzzer benötigen einen umfangreichen Korpus an Programmeingaben, um daraus zielführende neue Eingaben generieren zu können. Typischerweise beinhaltet ein initialer Eingabekorpus eine Mischung aus gültiger und ungültiger Programmeingaben, gemäß der (möglicherweise informellen) Programmspezifikation. Andere Fuzzer arbeiten mit einer Grammatik des Eingabeformats, um Eingaben zu erzeugen. Beide Arten Eingaben zu erzeugen setzen Wissen über das Eingabeformt voraus. Demgegenüber stehen White-Box-Fuzzer und Gray-Box-Fuzzer. Diese instrumentieren das Programm auf verschiedene Arten, um die Auswirkungen von Eingaben innerhalb des Programms beobachten zu können. Eine verbreitete Art neue Eingaben zu erzeugen ist, die Programmabdeckung zu messen, die durch eine Eingabe erziehlt wurde. Darauf basierend werden neue Eingaben erzeugt, die neue Teile des Programms abdecken. Abdeckung kann auf verschiedene Arten gemessen werden, zum Beispiel durch abgedeckte Knoten oder abgedeckte Kanten im Kontrollflussgraph des Programms. Dabei muss der Kontrollflussgraph nicht initial vorliegen, sondern kann auch iterativ erstellt und erweitert werden. Fuzzer die Programmabdeckung verwenden, nennt man abdeckungsgesteuert. Eine weitere Art von Fuzzern benutzt symbolische Ausführung, um zielgerichteter Zusammenhänge zwischen dem internen Verhalten des Programms und der Eingabe herstellen zu können. Hierfür werden, zum Beispiel mithilfe von SMT-Lösern, Bedingungen im Kontrollfluss aufgelöst. Außerdem gibt es Fuzzer, die zuerst eine statische Analyse des Quelltexts durchführen und basierend darauf gezielt verdächtige Stellen des Programms fuzzen (Shastry et al. 2017). Neben diesen verbreiteten Ansätzen hat die aktuelle Forschung eine Reihe weiterer Klassen von Fuzzern entwickelt, die verschiedene Herausforderungen der klassischen Fuzzer adressieren. Beispiele hierfür sind redqueen, ein Fuzzer, der Prüfsummen-Vergleiche entfernen kann (Aschermann et al. 2019), T-Fuzz ein Fuzzer, der durch Programmtransformationen irrelevante Programmteile entfernen kann (Peng, Shoshitaishvili, and Payer 2018), IJON, ein durch von Experten eingefügten Annotationen gesteuerter Fuzzer (Aschermann et al. 2020) oder NEUZZ, ein durch maschinelles Lernen gesteuerte Fuzzer (She et al. 2019). Fuzzer können durch verschiedene Beobachtungen auf einen Bug im Programm schließen. Eine Klasse von Bugs sind solche, die zu Programmabstürzen füren. Diese können durch die Rückgabe des Programms, Signale oder Zeitlimits (keine Reaktion vom Programm) festgestellt werden. Zusätzlich kann im Fuzzing-Prozess Sanitation, zum Beispiel Address-Sanitation, eingesetzt werden, um Bugs zu finden, die nicht unmittelbar in einem Absturz resultieren. Address-Sanitizer detektieren unzulässige Speicherzugriffe, zum Beispiel Pufferüberläufe.

Eine weitere Unterscheidung von Fuzzern ist die Form des zu testenden Programms. Da sich diese Arbeit auf lokal ausführbare C-Programme bezieht, sind mögliche Formen der Programmquelltext oder ausschließlich das Binärprogramm. Quelltext bietet mehr Möglichkeiten für Fuzzer. Insbesondere Fuzzer, die umfangreiche Instrumentierung vornehmen oder statisch im Quellcode nach interessanten Stellen suchen benötigen den Programmquelltext. Um Binärprogramme zu fuzzen, wird typischerweise der Prozessor emuliert. Hierfür gibt es beispielsweise QEMU, das zusammen mit dem verbreiteten Fuzzer AFL benutzt werden kann. Bis vor Kurzem war auch Address-Sanitation in Binärprogrammen schwer zu erzielen. Mit RetroWrite gibt es heute jedoch eine automatisierte Methode dazu (Dinesh et al. 2020).

Aktuelle Forschung zu Fuzzing-Schutzmaßnahmen

Ansätze, die Fuzzing erschweren, sind nicht neu. Ein Blogartikel von Whitehouse (Whitehouse 2014) aus dem Jahre 2014 beschreibt oberflächlich eine Reihe von Indikatoren, die einem Programm andeuten, dass es gefuzzt wird. Für den Fall, dass ein Fuzzer erkannt wird, schlägt der Autor verschiedene Maßnahmen vor, die Fuzzer fehlleiten. Dabei werden Maßnahmen für verschiedene Plattformen (Betriebssysteme, Android-Apps, Web-Anwendungen und Binärprogramme) aufgezählt. Ähnliche Methoden wurden schon im Jahre 2010 in einer nicht publizierten Arbeit von C. Miller beschrieben (Miller 2010).

Edholm et al. (Edholm and Göransson 2016) beschreiben Maßnahmen, die die etablierten Fuzzer AFL und Honggfuzz erkennen und fehlleiten sollen. Dabei sind die beschriebenen Maßnahmen auf die spezifischen Implementierungen von AFL und Honggfuzz angepasst und zu großen Teilen nicht für andere Fuzzer generalisierbar.

Eine Arbeit von Güler et al. (Güler et al. 2019b) identifiziert Fuzzing-Schutzmaßnahmen, die für große Klassen von Fuzzern gültig sind. Dazu werden grundlegende Annahmen identifiziert, die Fuzzer treffen, um erfolgreiche Tests des Programms durchführen zu können. Die Annahmen sind so generell, dass alle aktuellen Fuzzer auf mindestens einer der Annahmen beruhen, und die Autoren davon ausgehen, dass dies auch für zukünftige Fuzzer der Fall sein wird. Um die Schutzmaßnahmen zu evaluieren, die sich nur auf Binärprogramme beziehen, und dabei zukünftige Verbesserungen von Fuzzern zu berücksichtigen, werden Fuzzer verwendet, die den Quelltext des Programms benötigen. Die in (Güler et al. 2019b) gefundenen Annahmen sind:

  1. [antifuzz_one] Die von einem Fuzzer erreichte Programmabdeckung steht im Verhältnis zu der getesteten Funktionalität des Programms.

  2. [antifuzz_two]Der Fuzzer kann Abstürze des Zielprogramms erkennen.

  3. [antifuzz_three] Der Fuzzer kann eine große Anzahl von Ausführungen pro Sekunde des Zielprogramms erreichen. (Aktuelle Fuzzer können das Zielprogramm hunderte bis tausende Male pro Sekunde ausführen.)

  4. [antifuzz_four] Kombinationen von Sprungbedingungen können gelöst werden. (Damit Fuzzer symbolische Ausführung anwenden können, müssen die Sprungbedingungen, beziehungsweise Kombinationen aus diesen, mit überschaubarem Aufwand, zum Beispiel von SMT-Lösern, gelöst werden können.)

Auf Basis dieser Annahmen, beschreiben die Autoren Schutzmaßnahmen und implementieren diese. Die Implementierung wird als eine Bibliothek mit dem Namen AntiFuzz bereitgestellt (Güler et al. 2019a). AntiFuzz stellt Entwicklern eines zu schützenden Programms Funktionen bereit, die an geeigneten Positionen im Programm aufzurufen sind, zum Beispiel bei Gleichheitsvergleichen oder zu Beginn des Programms. Die Implementierung beinhaltet, wie in Kapitel 5 näher beschrieben, keinen Schutz gegen das Entfernen der Schutzmaßnahmen.

Ein zeitgleich erschienene Arbeit von Jung et al. (Jung et al. 2019b) hat ebenfalls zum Ziel, Schutzmaßnahmen gegen aktuelle Fuzzer zu implementieren. Dabei gehen die Autoren noch einen Schritt weiter, indem sie die Schutzmaßnahmen automatisch beim Kompilierungsvorgang anwenden. Dazu haben sie eine Implementierung mit dem Namen Fuzzification veröffentlicht (Jung et al. 2019a). Fuzzification benötigt eine Menge gültiger Programmeingaben sowie eine Menge ungültiger Programmeingaben, um automatisiert Schutzmaßnahmen anwenden zu können.

Arten von Fuzzing-Schutzmaßnahmen

Da noch keine allgemeine Definition für Fuzzing-Schutzmaßnahmen in der Literatur zu finden ist, wird für diese Arbeit die formale Definition eines Obfuscators von Barak et al. (Barak et al. 2001) mit den Annahmen über Fuzzer von Güler et al. (Güler et al. 2019b) kombiniert. Daraus ergeben sich folgende Eigenschaften für eine Fuzzing-Schutzmaßnahme. (Es können mehrere Fuzzing-Schutzmaßnahmen kombiniert werden, indem sie nacheinander angewendet werden.)

  1. [prop_one] Das geschützte Programm muss funktional äquivalent zum ungeschützten Programm sein, also gleiche Eingaben müssen in gleichen Ausgaben resultieren.

  2. [prop_two] Das geschützte Programm darf, relativ zum ungeschützten Programm, höchstens polynomiell größer und langsamer sein.

  3. [prop_three] Die Fuzzing-Schutzmaßnahme bricht mindestens eine der über Fuzzer in (Güler et al. 2019b) getroffenen Annahmen.

Wobei Fuzzing-Annahme [antifuzz_two] (Eigenschaft [prop_three]) generalisiert werden muss zu: Der Fuzzer kann Fehlverhalten des Zielprogramms erkennen. Diese Generalisierung ist notwendig, da es Fuzzer gibt, die nicht nur Bugs finden, die zu einem Absturz führen, sondern, beispielsweise durch Address-Sanitation, auch andere Klassen von Bugs. Insbesondere da mittlerweile durch (Dinesh et al. 2020) Address-Sanitation auch automatisch auf Binärprogramme (falls Address Space Layout Randomization verwendet wird) angewendet werden kann, ist diese Generalisierung auch für das beschriebene Angreifer-Modell relevant. Keine der im Folgenden vorgestellten Schutzmaßnahmen bricht diese generalisierte Annahme.

Die Definition zeigt, dass eine Fuzzing-Schutzmaßnahme nicht gegen jeden Fuzzer erfolgreich sein muss, es muss aber eine grundlegende Annahme einer Klasse von Fuzzern gebrochen werden. Um ein Programm allgemein vor Fuzzing zu schützen, ist also eine Kombination von Maßnahmen nötig, sodass alle Annahmen gebrochen werden.

Erkennung von Fuzzern

Um Schwachstellensuche durch Fuzzing zu verhindern, kann das Programm versuchen, zu detektieren, ob es gefuzzt wird. Falls ein Fuzzer erkannt wird, können die in Abschnitt 4.2 beschriebenen Schutzmaßnahmen aktiviert oder verstärkt werden oder das Programm zeigt ein ganz anderes Verhalten, indem es ausschließlich einen trivialen bugfreien Teil des Programms ausführt.

Einige Fuzzer nutzen ptrace zur Absturzerkennung oder zur Triage von Bugs (Edholm and Göransson 2016; Güler et al. 2019b). Außer von Fuzzern wird ptrace direkt von menschlichen Analysten oder von Debuggern verwendet. Programme können detektieren, ob sie mit ptrace beobachtet werden, indem sie versuchen ptrace auf sich selbst aufzurufen. Ein Prozess kann nicht gleichzeitig von mehreren anderen Prozessen mit ptrace beobachtet werden. Wenn das Programm sich also nicht selbst mit ptrace beobachten kann, so wird das Programm gerade analysiert, möglicherweise durch einen Fuzzer.

Ein hinreichendes Kriterium zur Fuzzer-Erkennung ist das Vorhandensein bestimmter Umgebungsvariablen, die typisch für konkrete Fuzzer sind (Edholm and Göransson 2016; Huet 2017). Dieses Kriterium ist zwar hinreichend, aber natürlich nicht notwendig, es kann nur bekannte Fuzzer abdecken und durch einfache Änderungen versagt dieses Kriterium. Weitere spezifische Erkennungskriterien, wie die Überprüfung auf das Vorhandensein bestimmter Dateien, sind vorstellbar.

Fuzzer benötigen meist ein initiales Wörterbuch, um darauf aufbauend neue Eingaben generieren zu können. Eine verbreitete Methode, an dieses initiale Wörterbuch zu gelangen, ist es alle Zeichenketten in einem Programm zu extrahieren (Shastry et al. 2017). Dies ist ohne Wissen über das Eingabeformat möglich. Somit können auch Magic-Values gefunden und in das Wörterbuch aufgenommen werden. Als Detektionsmaßnahme wird eine Zeichenkette als Honeypot im Programm platziert. Der Honeypot wird als eine für einen normalen Nutzer sehr unwahrscheinliche Zeichenkette gewählt. Der Fuzzer hingegen wird die extrahierte Zeichenkette verwenden. Bekommt das Programm den Honeypot als Eingabe, so wurde Fuzzing oder eine andere Art der Schwachstellensuche erkannt. Diese Maßnahme funktioniert am besten, wenn das Programm über eine Möglichkeit verfügt, Daten über mehrere Ausführungen hinweg zu speichern, da nicht bei jeder Ausführung mit einem Honeypot oder einer einfach zu erkennenden Mutation davon zu rechnen ist. Diese Maßnahme ist inspiriert von Juels et al. (Juels and Rivest 2013). Sie haben eine ähnliche Methode im Kontext von Passworthashes auf Servern vorgeschlagen. Dort werden unter die echten Nutzerpasswörter Hashes von Honeypots, sogenannten Honeywords, gemischt. Wird die Passwortdatenbank gestohlen und invertiert und damit versucht in das System einzudringen, kann der Angriff erkannt werden, sobald der Angreifer die Honeywords als Passwörter ausprobiert. Voris et al. (Voris et al. 2013) beschreiben Honeywords in einem größeren Zusammenhang sowie eine Methode diese zu generieren, sodass sie schwer von anderen Zeichenketten unterscheidbar sind. Ihre Arbeit hat einen Menschen, beispielsweise einen Mitarbeiter im eigenen Unternehmen, als Angreifermodell.

Behindern von Fuzzern

Fuzzer-Erkennung funktioniert nicht zuverlässig gegen viele verschiedene, teils unbekannte, Fuzzer. Ein anderer Ansatz ist deshalb, keine explizite Unterscheidung zwischen normaler Ausführung und der Ausführung durch einen Fuzzer zu machen. Stattdessen werden die Annahmen über Fuzzer aus (Güler et al. 2019b) durch den Aufbau des Programms gebrochen. Dabei soll die normale Programmausführung nicht außerhalb der Eigenschaften [prop_one] und [prop_two] einer Fuzzing-Schutzmaßnahme beeinflusst werden.

Initialer fork-Aufruf

Fuzzer wie AFL nehmen eine Eingabe als einen Bug auslösend in Betracht, wenn die Rückgabe des Programms von null verschieden ist und ein Signal im Zielprogramm beobachtet wurde. Folgende Schutzmaßnahme kann demnach dafür verwendet werden die Absturzerkennung von Fuzzern fehlzuleiten: Bei einem Programm wird initial fork() ausgerufen und das eigentliche Programm als Kind ausgeführt (Edholm and Göransson 2016). Der Elternprozess wartet dann bis der Kindsprozess beendet wurde. Für den Fall, dass der Kindsprozess von einem Signal beendet wurde, beendet sich der Elternprozess mit Rückgabewert 0. Somit betrachtet der Fuzzer eine uninteressante Hülle statt dem eigentlichen Prozess.

Signale Verbergen

Um Prozessen Fehlverhalten anzuzeigen, sendet das Betriebssystem Signale. Beispiele für Fehlverhalten sind Schutzverletzungen (segmentation faults), Zugriffe auf physikalisch nicht adressierbare Speicherstellen (Bus-Fehler, englisch bus errors) oder ungültige Instruktionen. In diesen Fällen sendet das Betriebssystem die Signale SIGSEGV, SIGBUS beziehungsweise SIGILL. Alle diese Fehlerkonditionen sind Resultat eines Bugs im Programm. Programme haben die Möglichkeit auf Signale zu reagieren oder sie zu ignorieren. Standardmäßig führen diese in ANSI-C definierten Signale zum Abbruch des Programms. Um Fuzzing zu erschweren, kann nun ein spezieller Signalhandler eingerichtet werden, wie in Listing [hide]. Normalerweise werden Signalhandler für solch schwerwiegende Fehler dazu verwendet, wichtige Daten zu sichern und im Anschluss das Programm mit einem Fehlercode, EXIT_FAILURE, zu beenden. Stattdessen wird nun das Programm mit dem Rückgabewert EXIT_SUCCESS, also 0 beendet, um den Fehler vor dem Fuzzer zu verbergen (Edholm and Göransson 2016). Diese Maßnahme erfüllt die Definition einer Fuzzing-Schutzmaßnahme, insbesondere wird Annahme [antifuzz_two] gebrochen, die besagt, dass Abstürze detektiert werden können.

void hide(int sig) {
  printf("Everything went well.");
  exit(EXIT_SUCCESS);
}
int main(void) {
  signal(SIGSEGV,hide);
  signal(SIGBUS,hide);
  signal(SIGILL,hide);
  return run_programm();
}

Das dauerhafte Ignorieren oder Blockieren von Signalen, die schwerwiegende Fehler anzeigen, ist keine gute Lösung. Zum einen, da des Betriebssystem die Möglichkeit hat Prozesse, die sich falsch verhalten und nicht auf andere Signale reagieren, sofort zu beenden. Bei Unix-Betriebssystemen geschieht dies durch das Signal SIGKILL. Prozesse können SIGKILL weder ignorieren noch behandeln, wodurch der Fuzzer den Absturz erkennen kann. Zum anderen ist ersichtlich welche Prozesse welche Signale ignorieren. Unter Linux pro Prozess durch Bitmasken in /proc/$PID/status. Das Ignorieren der oben genannten Signale ist wohl kein legitimer Anwendungsfall und wäre ein starkes Indiz für das Vorhandensein von Fuzzing-Schutzmaßnahmen.

Vorgetäuschte Abstürze

Einige Fuzzer instrumentieren das Zielprogramm, indem sie selbst die Signalhandler überschreiben, um Abstürze zu detektieren. In diesen Fällen hilft die im vorherigen Absatz beschriebene Methode nicht. In (Güler et al. 2019b) werden auch Signale verborgen wie im vorherigen Absatz beschrieben, zusätzlich wird aber bei jedem Programmstart absichtlich eine Schutzverletzung ausgelöst, die der Signalhandler ignorieren soll. Somit ergeben sich zwei Fälle. Überschreibt der Fuzzer also Signalhandler, detektiert er bei jeder Programmausführung einen Absturz. Überschreibt der Fuzzer nicht die Signalhandler, so ist die Methode Signale zu verbergen möglich, was in Kombination mit der vorherigen Maßnahme, Fuzzer täuscht, die basierend auf dem Rückgabewert des Programms nach Bugs im Programm suchen.

Schon Whitehouse (Whitehouse 2014) beschreibt eine Methode, um Abstürze vorzutäuschen. Dort allerdings nicht bei jedem Programmstart, um Fuzzer grundsätzlich zu täuschen, sondern verstreut über das Programm. An beliebigen Stellen im Programm kann folgendes Muster angewendet werden.

  1. Ein Signalhandler wird eingerichtet, zum Beispiel für SIGSEGV. Dieser tut nichts, sodass die Programmausführung weiter normal fortgesetzt werden kann.

  2. Es wird absichtlich ein Fehler ausgelöst, zum Beispiel eine Schutzverletzung durch Schreiben an Adresse 0x0, was das Signal SIGSEGV auslöst.

  3. Der zuvor eingerichtete Signalhandler wird wieder entfernt.

Dies ist natürlich kein allgemeiner Schutz gegen Fuzzer. Es trägt aber, durch viele falsch-positive Ergebnisse, dazu bei, die Schwachstellensuche insgesamt zu erschweren – unabhängig ob durch einen menschlichen Analysten oder in einem automatisierten Prozess. Annahme [antifuzz_two] wird nur dann gebrochen, wenn der Absturz, wie in (Güler et al. 2019b), initial passiert und somit der Fuzzer jede Eingabe als Absturz ansieht und danach zur nächsten Eingabe geht.

Einfügen nicht ausnutzbarer Schwachstellen

In (Güler et al. 2019b) und (Jung et al. 2019b) wurde angedeutet, aber nicht implementiert, dass auch das Einfügen von typischen Mustern von Schwachstellen in ein Programm, Fuzzer fehlleiten könnte. In einer Arbeit von Hu et al. (hu, hu, and dolan-gavitt 2018) wird das Werkzeug Chaff Bugs präsentiert, das automatisch Programme durch typischen Muster von Schwachstellen ergänzt, dabei aber garantiert, dass diese während einer normalen Ausführung nicht ausnutzbar sind. Fuzzer finden zunächst einmal nur Bugs in einem Programm. Wie bereits beschrieben, hauptsächlich Programmabstürze. Einige Fuzzer können zusätzlich eine Triage eines gefunden Bugs durchführen, die eine Einschätzung der Ausnutzbarkeit eines Bugs liefert.Angreifer suchen, möglicherweise basierend auf einer automatischen Triage, nach Wegen, den Bug auszunutzen, also einen Exploit zu schreiben. Chaff Bugs kann scheinbare Schwachstellen in ein Programm einfügen, die von Fuzzern gefunden werden und von automatischen Triagewerkzeugen als ausnutzbar klassifiziert werden, aber tatsächlich beweisbar nicht ausnutzbar sind (hu, hu, and dolan-gavitt 2018). Chaff Bugs wurde in (hu, hu, and dolan-gavitt 2018) mit AFL und dem Triagewerkzeug exloitabe, einem Plugin für gdb, getestet. Diese Art der Schutzmaßnahme ist nicht nur auf Fuzzer beschränkt, sondern soll jede Art der Schwachstellensuche behindern. Die Anwendung von Chaff Bugs erfüllt die Definition einer Fuzzing-Schutzmaßnahme aus zwei Gründen nicht. Erstens wird keine der Fuzzing-Annahmen gebrochen. Zweitens wird Eigenschaft [prop_one] einer Fuzzing-Schutzmaßnahme nicht erfüllt, da, in Abgrenzung zum letzten Abschnitt 4.2.3, tatsächliche Abstürze eingefügt werden. Das Programm ist also nicht mehr funktional äquivalent zum ungeschützten Programm.

Die eingefügten Abstürze sind zwar nicht ausnutzbar, es ist aber abhängig von der zu schützenden Anwendung, ob diese Maßnahme tatsächlich geeignet ist. Insbesondere darf der Absturz eines Programms selbst noch keine (relevante) Schwachstelle darstellen (DoS-Angriff). Die Autoren von (hu, hu, and dolan-gavitt 2018) argumentieren, dass ihre Schutzmaßnahme für viele Klassen von Programmen angewendet werden könne und führen dafür die heute verbreiteten Microservice-Architekturen, welche für Ausfälle entworfen würden, als Beispiel an. Tatsächlich sind viele Microservice-Architekturen so entworfen, dass der Ausfall einer Instanz die Verfügbarkeit des Gesamtsystems nicht beeinflusst. Netflix lässt sogar absichtlich immer wieder Microservices abstürzen, um die Ausfallsicherheit ihres Systems in Produktion zu testen (Basiri et al. 2019). Außerdem ist die Wahrscheinlichkeit, dass ein normaler Nutzer des Systems versehentlich einen Absturz auslöst sehr gering. Allerdings kann die Möglichkeit eine Microservice-Instanz gezielt abstürzen zu lassen durchaus für die Verfügbarkeit eines Gesamtsystems kritisch werden, wenn der Angriff gleichzeitig auf mehrere Microservice-Instanzen durchgeführt wird.

In Fällen, in denen DoS-Angriffe tatsächlich kein Problem darstellen, kann diese Schutzmaßnahme die Schwachstellensuche stark behindern. Ähnlich zu Abschnitt 4.2.3 entsteht eine große Zahl von falsch positiven Ergebnissen, die nur schwer als absichtlich eingebaut zu identifizieren sind. Außerdem werden durch diese Maßnahme auch Fuzzer fehlgeleitet, die eine statische Programmanalyse durchführen um, Programmteile zu finden, die typische Muster von Schwachstellen aufweisen (Shastry et al. 2017) und den Fuzzingprozess in diese Teile leiten.

Hashwert-Vergleiche

Durch symbolische oder konkolische Ausführung (im Folgenden als symbolische Ausführung zusammengefasst) können Fuzzer systematisch die Pfade eines Programms abdecken. Dafür wird der Einfluss von Eingaben auf das Ergebnis von Sprungbedingungen analysiert. Obwohl symbolische Ausführung mittlerweile auch kompliziertere Bedingungen auflösen kann und Fuzzer in Kombination mit anderen Methoden gute Ergebnisse erzielen (Stephens et al. 2016; Yun et al. 2018), gibt es Grenzen der symbolischen Ausführung. Insbesondere Prüfsummen, welche in vielen Dateiformaten zu finden sind, stellen ein Hindernis dar. Fuzzing solcher Dateiformate benötigt in der Praxis das manuelle oder automatische Entfernen von Prüfsummen u.ä. (Liu et al. 2018), Kenntnisse über das Eingabeformat oder optimistische Annahmen über das Verhalten des Programms (Peng, Shoshitaishvili, and Payer 2018; Aschermann et al. 2019). Dieses Hindernis kann für Fuzzing-Schutzmaßnahmen ausgenutzt werden, indem alle Vergleiche auf Gleichheit durch Hashwert-Vergleiche ersetzt werden. Dies ist ein wirksamer Schutz gegen symbolische Ausführung, da ein erfolgreiches Auflösen eines Vergleichs (ohne Vorwissen über den Klartext) gleichbedeutend mit dem Brechen der Kollisionsresistenz-Eigenschaft der verwendeten Hashfunktion ist. Dies hat natürlich einige Limitationen. Hashwert-Vergleiche lassen sich nur bei Vergleichen auf Gleichheit anwenden, sind also für viele Eingabeformate und deren Interpretationsroutinen nicht anwendbar, da sich dort oftmals Vergleiche mit regulären Ausdrücken oder Vergleiche innerhalb einer Ordungsrelation finden. Das Verwenden von Hashfunktionen induziert einen höheren Berechnungsaufwand, auch da nicht alle Prozessoren Hardware-Unterstützung für Hashwertberechungen haben. Die Entwickler des zu schützenden Programms verlassen sich, durch Anwenden dieser Maßnahme, auf die Einwegeigenschaft der verwendeten Hashfunktion und verletzten Eigenschaft [prop_one] der Definition von Fuzzing-Schutzmaßnahme, da die Programme nicht mehr äquivalent sind. Unsichere Hashfunktionen wie md5 und SHA-1 (Leurent and Peyrin 2020; Stevens 2013), sind als Fuzzing-Schutzmaßnahme in diesem Sinne ungeeignet. Barak et al. (Barak et al. 2001) definieren eine abgeschwächte approximative Form von Eigenschaft [prop_one], bei der die Eigenschaft nur mit hoher Wahrscheinlichkeit halten muss. Diese ist bei sicheren Hashfunkionen erfüllt. Trotzdem ist es auch dort sinnvoll die Hashwertberechungen zu salzen, da die Zeichenketten in realen Programmen nicht als gleichverteilt anzunehmen sind. Angreifer mit vorberechneten Hashwerten, für gängige Zeichenketten in Programmen können sonst deutlich schneller Urbilder für diese finden. Salzen bedeutet: Für jede Zeichenkette einen zufälligen Wert mit in die Hashfunktion zu geben und diesen dann neben der Zeichenkette zu speichern. Somit muss der Angreifer auch für gängige Zeichenketten viele Hashwerte berechnen. Keiner der aktuellen Ansätze salzt die Hashwerte.

Angreifer könnten bei Verwendung einer unsicheren Hashfunktion gegebenenfalls ein unterschiedliches Verhalten des geschützten Programms im Vergleich mit dem ungeschützten Programm auslösen, was schwerwiegende Auswirkungen haben könnte. In (Güler et al. 2019b) wird diesem Problem dadurch begegnet, dass die Hashwert-Vergleiche durch die Entwickler selektiv angewendet werden. Somit kann der Berechnungsmehraufwand gering gehalten werden. Außerdem wird SHA-512, eine als sicher angenommene Hashfunktion, verwendet. In (Jung et al. 2019b) hingegen werden die Vergleiche automatisch ersetzt. Um hier den Berechnungsmehraufwand gering zu halten, wird auf eine starke Hashfunktion verzichtet und stattdessen CRC32 eingesetzt (Jung et al. 2019a, 2019b). CRC32 ist ein Algorithmus, um effizient Prüfsummen berechnen zu können, weist aber keine Kollisionsresistenz auf. Es können trivial Kollisionen zu einem gegebenen Wert gefunden werden können, beispielsweise durch Vertauschen von 4-Byte-Gruppen, so haben die Zeichenketten "BBBBAAAA" und "AAAABBBB" den gleichen Hashwert. Hier ist also Eigenschaft [prop_one] nicht erfüllt.

//original code: if (value == 12345)
if (CRC_LOOP(value) == OUTPUT_CRC) {...}

Auch als Prüfsumme verwendet, stellt CRC nicht für alle Fuzzer mit symbolischer Ausführung ein Hindernis dar.
Sharma et al. (Sharma et al. 2020) zeigen für die CRC32-Implementierung von (Jung et al. 2019b), dass diese mit symbolischer Ausführung effizient gelöst werden kann. Das zeigt, dass auch Eigenschaft [prop_three] nicht erfüllt ist.

Fuzzification hat zudem Schwächen, Vergleiche im Quelltext zu identifizieren und zu ersetzen. Einfache Vergleiche, zum Beispiel solche, bei denen die strcmp-Funktion nicht unmittelbar in einem if steht (siehe Listing [bad_one]), werden nicht ersetzt.

/* Wird ersetzt: */
if(strcmp(strA, "hello")) {...}
/* Wird nicht ersetzt */
int c = strcmp(strA, "hello");
if(c) {...}

Zudem ist es möglich, dass Fuzzification invaliden C-Code generiert, falls an an einer Stelle eine Variable mit dem Namen „newvar_1“ verwendet wird. Es kann sogar dazu kommen, dass der Code syntaktisch korrekt bleibt, aber funktional verändert wird. Falls wie in Listing [bad_two] ein Vergleich in einer festen Zeichenkette steht, so wird die Zeichenkette verändert.

strcpy(strA, "if(strcmp(strB,strA))");

Es ist also möglich, dass durch eine bessere Implementierung zu deutlicheren Ergebnissen in der Auswertung der AntiHybrid-Technik geführt hätte. Zusammen mit der Abhängigkeit von guten Testfällen ist Fuzzification insgesamt nicht breit als Fuzzing-Schutzmaßnahme anwendbar. Die Evaluation in (Jung et al. 2019b) mit AFL-QEMU zeigt auch, dass Fuzzification nicht gegen jedes der getesteten Programme die Anzahl der gefundenen Pfade reduzieren konnte. In Abbildung 1 ist zu sehen, dass Fuzzification (alle Schutzmaßnahmen aktiviert) bei libpng erfolgreich ist (gefundene Pfade um ca. 77% reduziert), bei libjpeg hingegen nicht (gefundene Pfade um ca. 11% reduziert). Eine von Wang et al. (Wang et al. 2020) durchgeführte Evaluation von Fuzzification unter gleichen Bedingungen, misst außerdem deutlich abweichende Werte für objdump. (Jung et al. 2019b) misst eine Reduktion der Pfade um ca. 80%, (Wang et al. 2020) hingegen eine Reduktion um nur 7,6%.

Ausschnitt der Evaluation von Fuzzification. Auf Basis von (Jung et al. 2019b), Abbildung 9.

Das Einfügen von Hashwert-Vergleichen schützt nicht nur gegen symbolische Ausführung, sondern hat auch den Nebeneffekt, eine bestimmte Art der Programmtransformation zu verhindern. Da Fuzzer Programmabdeckung als Metrik verwenden, teilen sie Bedingungen in mehrere Teile, sodass die Bedingungen schrittweise erfüllt werden können, statt diese direkt erfüllen zu müssen. Eine gängige Transformation ist, falls das möglich ist, das Aufteilen von Zeichenkettenvergleichen wie in Listing [transformation] dargestellt (Autor 2016).

/* originaler Code */
if(strcmp(x,"vln")) {...}
/* transformierter Code */
if(strlen(x) == 3)
if(x[0] == 'v')
    if(x[1] == 'l')
        if(x[2] == 'n') {...}

Somit ergibt jeder gefundene längere Präfix einer Zeichenkette mehr Abdeckung und hilft Fuzzern, auf die Annahme [antifuzz_one] aus (Güler et al. 2019b) zutrifft. Da Hashwert-Vergleiche nicht in der dargestellten Weise aufteilbar sind, muss der Fuzzer nun direkt eine gültige Eingabe für einen Vergleich erraten.

Eine Alternative zu Hashfunktionen, bei der nicht das Problem der Kollisionen besteht, ist es Verschlüsselung zu verwenden. (Güler et al. 2019b) nutzt hierfür die symmetrische Verschlüsselung AES. Der Schlüssel muss jedoch mit im Programm enthalten sein. Obfuscation-Techniken können beim Verstecken des Schlüssels helfen.

Einfügen vieler Pfade

Außer Hashwert-Vergleichen gibt es noch weitere Methoden, die nicht oder nur sehr schwer durch symbolische Ausführung aufzulösen sind. Von Banescu et al. (Banescu et al. 2016) werden dafür Code-Obfuscation-Techniken mit aktuellen Werkzeugen zur symbolischen Ausführung evaluiert und basierend darauf neue wirksamere Methoden vorgeschlagen. Als Fuzzing-Schutzmaßnahme ist das Unterteilen von Eingaben in mehrere logisch äquivalente Pfade am geeignetsten. Ein Elementarblock, der von mindestens einer Eingabe abhängt, wird anhand der Eingabe in (maximale Kardinalität der Wertemenge) viele Pfade unterteilt. Jeder der Pfade enthält äquivalenten, aber durch bestehende Obfuscation-Techniken unterschiedlich formulierten Code. Diese Obfuscation der Pfade ist notwendig, damit der Compiler diesen Code nicht entfernt oder es für Angreifer trivial wird diese Schutzmaßnahme zu erkennen und zu entfernen. Die Größe des Programms wächst dadurch asymptotisch linear, während die Analyse durch symbolische Ausführung asymptotisch exponentiellen Mehraufwand bedeutet, sobald diese Maßnahme in nicht trivialen Programmen mit Schleifen und Bedingungen angewendet wird (Banescu et al. 2016). Damit existiert eine Schutzmaßnahme gegen symbolische Ausführung, die die vorausgesetzten Eigenschaften für eine Fuzzing-Schutzmaßnahme erfüllt. Diese Art von Schutzmaßnahme bricht nicht nur Annahme [antifuzz_four], auf der Fuzzer mit symbolischer Ausführung beruhen, sondern auch Annahme [antifuzz_one], da nun getestete Funktionalität nicht mehr (oder deutlich weniger) im Verhältnis zur erreichten Abdeckung ist.

Es ist anzumerken, dass sowohl das Einführen von Hashwert-Vergleichen, wie auch die hier beschriebene Schutzmaßnahme nicht bei allen Programmen gleich gut funktioniert. Hashwert-Vergleiche benötigen, wie oben beschrieben, Vergleiche auf Gleichheit und diese Schutzmaßnahme profitiert deutlich von vielen Elementarblöcken in Schleifen (Banescu et al. 2016).

Güler et al. (Güler et al. 2019b) und Jung et al. (Jung et al. 2019b) erhöhen auch die Pfade im Programm (englisch branch explosion) als Schutzmaßnahme, allerdings duplizieren sie dafür nicht im Programm vorhandene Funktionalität, sondern fügen funktionslose neue Elementarblöcke und Pfade ein. Es werden pseudozufällig zur Kompilierungszeit Bedingungen generiert, die zu unterschiedlichen Elementarblöcken führen. Jedes Byte der Eingabe ist in mehreren Bedingungen involviert. Damit kann der Fuzzer keine Zusammenhänge mehr zwischen Eingabe und beobachteter Abdeckung herstellen, da kleine Unterschiede in den Eingaben zu deutlich abweichendem Verhalten führt. Konkret wird dadurch die Datenstruktur, die diesen Zusammenhang speichert, bei AFL eine Bitmap, überschwemmt. Einige der generierten Bedingungen sind einfach zu erfüllen, sodass ein abdeckungsgesteuerter Fuzzer schnell Eingaben generiert, die ihn in den funktionslosen Teil des Programmes fehlleiten. Andere Bedingungen lassen sich schwerer erfüllen, sodass der Fuzzer lange Zeit im funktionslosen Teil des Programms verbringt, wenn er versucht diese zu lösen.

Durch dieses Vergrößern des Kontrollflussgraphen entsteht Mehraufwand, das Binärprogramm wird größer und die Laufzeit wird länger. In (Jung et al. 2019b) wird angegeben, dass das Einfügen von hunderttausend Pfaden, was die standardmäßig von AFL verwendete Bitmap zu 90% füllt, in ihrer Implementierung das Binärprogramm um 4,6MB vergrößert. Die Anwendung von (Güler et al. 2019b) vergrößert das Binärprogramm nach den Angaben der Autoren um circa 25MB. Ich selbst konnte auch bei Anwendung aller Schutzmaßnahmen in der auf GitHub veröffentlichten Implementierung (Güler et al. 2019a) eine maximale Vergrößerung von 6,8MB messen. Der Laufzeitmehraufwand war weder bei (Jung et al. 2019b), noch bei (Güler et al. 2019b) messbar. Während die Vergrößerung des Binärprogramms in vielen Umgebungen, wie Servern oder Heimcomputern, kein Problem darstellten sollte, gibt es Umgebungen, wie IoT-Geräte mit wenig Speicher, für die möglicherweise die Anzahl der eingefügten Pfade individuell konfiguriert werden muss. Eigenschaft [prop_two] einer Fuzzing-Schutzmaßnahme ist, wegen des nur konstanten Mehraufwands erfüllt. In eigenen Tests war es mit manchen Fuzzern auf einem System mit 8GB Arbeitsspeicher nicht möglich das mit (Güler et al. 2019b) geschützte Programm zu instrumentieren. Zum Beispiel wurden AFL-QEMU und IJON (Um den Fuzzer durch eine Annotation zu einer echten Codestelle zu leiten.) erfolglos getestet. Wird ein mit (Güler et al. 2019b) geschütztes Programm für 3 Stunden mit AFL (LLVM-fast-Instrumentierung auf 2 3.6GHz Kernen parallel) gefuzzt, ist die Bitmap bereits zu 26% gefüllt.

Verzögerungen für seltene Pfade

Fuzzer benötigen viele Programmausführungen, um Bugs zu finden (Annahme [antifuzz_three]). Dies gilt weniger für White-Box-Fuzzer, die bei jeder Programmausführungen umfangreiche Analysen durchführen. Die Methoden in (Güler et al. 2019b) und (Jung et al. 2019b) brechen diese Annahme durch das Einfügen von Verzögerungen im Programm. Verzögerungen verlangsamen sowohl Fuzzing, wie auch normale Ausführung. Während eine Verzögerung von beispielsweise 100ms vielen Nutzern nicht auffallen würde, so wird dadurch der Fuzzingprozess deutlich gebremst, da Annahme [antifuzz_three] von hunderten bis tausenden Ausführungen pro Sekunde ausgeht. In vielen Fällen ist eine solche Verzögerung auch für die normale Benutzung eines Programms inakzeptabel. Die Methode in (Güler et al. 2019b) beruht deshalb darauf, dass die Verzögerungen von den Entwicklern des Programms an unkritischen Stellen eingefügt werden, beispielsweise bei ungültigen Eingaben. Fuzzer werden in der Regel deutlich mehr ungültige Eingaben erzeugen als normale Nutzer. Bei dieser Art von Schutzmaßnahme hängt es von der Position der Verzögerungen ab, ob Eigenschaft [prop_two] erfüllt ist. Die Methode in (Jung et al. 2019b) benötigt Testfälle für das Programm, um von normaler Ausführung selten besuchte Pfade zu finden. Auf gefunden seltenen Pfaden wird dann eine Verzögerung eingefügt. Dieser Ansatz verlässt sich auf eine gute Abdeckung durch die Testfälle der Entwickler. Ist in Testfällen ein Programmpfad nicht enthalten, der von einer Nutzergruppe aber häufig verwendet wird, erleben diese Nutzer eine Verlangsamung des Programms. Das Erstellen von Verzögerungen funktioniert iterativ.

  1. Es werden selten besuchte Pfade auf Basis der Testfälle identifiziert.

  2. [speediter] Es werden probabilistisch Verzögerungen auf seltenen Pfaden eingefügt.

  3. Die Testfälle werden auf dem geschützten Programm ausgeführt und die Verlangsamung insgesamt gemessen.

  4. Ist die gemessene Verlangsamung nicht innerhalb eines vorgegeben Zeitbudgets, wird Schritt [speediter] mit weniger/kürzeren Verzögerungen ausgeführt.

Die von (Jung et al. 2019b) eingefügten Verzögerungen sind keine reinen Aufrufe der sleep-Funktion, sondern sehen aus wie normaler Programmcode. Es wird CSmith (Yang et al. 2011) verwendet, um Programmcode zu generieren, der beispielsweise globale Variablen verändert oder arithmetische Operationen ausführt, um zu verhindern, dass die Schutzmaßnahme leicht zu erkennen und zu entfernen ist. In den für diese Arbeit mit (Güler et al. 2019b) durchgeführten Tests hat AFL-QEMU bei einem einfachen Programm (Güler et al. 2019a) (ähnlich zu dem transformierten Programm in Listing [transformation], wobei in {...} der Absturz ausgelöst wird) auch nach 3 Stunden (gleiche Bedingungen wie oben) keinen Absturz finden können. AFL-QEMU zeigt bei verlangsamten Programmen eine Warnmeldung an und vermutet eine Fehlkonfiguration des Fuzzers. Im ungeschützten Programm kann innerhalb von unter einer Minute ein Absturz gefunden werden.

Automatisches Einfügen von Verzögerungen wie in (Jung et al. 2019b) ist zwar aus Entwicklersicht einfacher, sollte aber mit Vorsicht verwendet werden. Ein Angreifer könnte aus der Verteilung der Verzögerungen im Programm auf die Testfallabdeckung der Entwickler schließen und dann an Stellen mit wenig Testfallabdeckung auf Schwachstellensuche gehen. Beim Entwerfen zukünftiger Fuzzing-Schutzmaßnahmen dieser Art muss darauf geachtet werden, nichts über die Testfallabdeckung zu verraten. Außerdem ist diese Schutzmaßnahme nicht für alle Anwendungen geeignet. Es gibt beispielsweise Anwendungen, für die (fast) jede Eingabe eine gültige Eingabe ist. Die Annahme, dass normale Nutzer häufiger gültig Eingaben verwenden als Fuzzer, trifft dann natürlich nicht mehr zu. Bei Anwendungen, für die DoS-Angriffe eine Gefahr darstellen, ist zu beachten, dass Angreifer absichtlich ungültige Eingabe senden können, um somit die Anwendung zu überlasten.

Hashkollisionen für AFL

Viele heutige Fuzzer basieren auf AFL und nutzten somit auch die gleiche Datenstruktur wie AFL, eine Bitmap, standardmäßig 64KB groß. In dieser wird die erreichte Programmabdeckung gespeichert. Jedem Elementarblock wird bei der Instrumentierung des Programms eine Zahl z zugewiesen. Wird beim Fuzzing ein Elementarblock zn abgedeckt, so wird die Information zusammen mit der Informationen über den letzten Elementarblock za an der Stelle zn ⊕ za der Bitmap gespeichert. Hat AFL den Quelltext des Programms zur Verfügung, so kann afl-gcc jeweils eine gute Zufallszahl für z wählen. Liegt nur das Binärprogramm vor, so ist das, aufgrund der geringeren Möglichkeiten der Instrumentierung, nicht einfach möglich. AFL-QEMU nutzt deshalb einen einfachen Hash aus den Adressen der Elementarblöcke. Die Nummern der Elementarblöcke sind also immer gleich. Kang Li (Kang Li 2018) stellt auf der Black Hat USA 2018 eine Möglichkeit vor, durch Einfügen von funktionslosen neuen Blöcken Hashkollisionen für echte Pfade zu erzeugen. AFL kann dann für viele Eingaben nicht mehr entscheiden, ob sie zu neuer Abdeckung geführt haben. Diese Schutzmaßnahme ist allerdings auf AFL-QEMU beschränkt.

Bugs in Fuzzern

Eine weitere, in (Jung et al. 2019b) erwähnte Möglichkeit, den Fuzzing-Prozess zu behindern, ist das absichtliche Auslösen von Bugs in Fuzzern selbst. Es gibt Bugs in Fuzzern, die von einem Programm dazu genutzt werden können den Fuzzer abstürzen zu lassen. Beispielsweise ist (Bruening 2014) ein seit 2014 bekannter Absturz in einem von vielen Fuzzern verwendeten Werkzeug zur Programmtransformation. Natürlich ist diese Art der Schutzmaßnahme auf wenige bestimmte Versionen von Fuzzern beschränkt, für die Bugs bekannt sind.

Umgehen von Fuzzing-Schutzmaßnahmen

Fuzzing-Schutzmaßnahmen haben das Ziel die Schwachstellensuche durch Fuzzing so schwer wie möglich zu gestalten, sodass ein Angreifer entweder seine Ressourcen verschwendet oder aufgibt, da keine Bugs gefunden wurden. In diesem Kapitel soll die Perspektive eines Angreifers eingenommen werden, der Fuzzing verwenden möchte, um Schwachstellen zu finden. Zuerst muss erkannt werden, dass ein Programm Fuzzing-Schutzmaßnahmen einsetzt, um zu verhindern, dass Ressourcen verschwendet werden. Dann müssen die Arten der Schutzmaßnahmen identifiziert werden, um schließlich die Schutzmaßnahmen zu entfernen oder eine alternative Fuzzing-Technik anzuwenden, die von den Fuzzing-Schutzmaßnahmen nicht betroffen ist.

Erkennen von Fuzzing-Schutzmaßnahmen

Es sind verschiedene Ansätze denkbar, um Fuzzing-Schutzmaßnahmen zu erkennen. Nicht alle davon sind sichere Kriterien, sondern oftmals eher Indizien. Die nachfolgend beschriebenen Ansätze können kombiniert werden, um aussagekräftige Ergebnisse zu erreichen. Es ist sogar denkbar, die Ansätze zur Merkmalsextraktion für eine Klassifikation durch maschinelles Lernen zu verwenden.

Nutzt ein Programm Fuzzer-Erkennung, dann ist es möglich, das Verhalten des Programms in verschiedenen Umgebungen zu vergleichen. Verhält sich das Programm in einer normalen Umgebung, zum Beispiel einer neu aufgesetzten virtuellen Maschine, anders als in einer Umgebung in der alles auf einen Fuzzer hindeutet, zum Beispiel Umgebungsvariablen von AFL, dann verwendet das Programm Fuzzer-Erkennung. Auch Honeywords als Schutzmaßnahme können erkannt werden, indem dem Programm zuerst extrahierte Zeichenketten als Eingaben gegeben werden und es dann mit einem neu gestarteten Programm verglichen wird. Hier sind nicht nur funktionale Eigenschaften des Programms zu vergleichen, sondern auch nicht-funktionale Eigenschaften, um Verzögerungen zu erkennen. Es ist auch möglich einen Fuzzer zu konstruieren, der die Rückgaben des gefuzzten Programms mit den Rückgaben des gleichen ungefuzzten Programms vergleicht. Dieser Vergleich kann auch asynchron stichprobenartig geschehen, um den Fuzzing-Prozess durch teure Aufrufe an eine virtuelle Maschine oder einen Container nicht zu verlangsamen.

Um Fuzzing-Schutzmaßnahmen zu erkennen, kann das Verhalten des Programms während einer normalen Ausführung oder während des Fuzzing-Prozesses betrachtet werden. Für einen initialen fork-Aufruf, um Signale zu verbergen oder um Abstürze vorzutäuschen sind eine Reihe von typischen Systemaufrufe nötig, die mit strace beobachtet werden können. Listing [straceout] zeigt die Systemaufrufe, wenn AntiFuzz einen Absturz vortäuscht. Auch der Schutz gegen eine Beobachtung durch ptrace, kann durch einen entsprechenden Systemaufruf mit strace erkannt werden. Bei einem strace eines mit (Güler et al. 2019b) geschützten Programms ist ein vorgetäuschter Absturz klar zu erkennen.

rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [], 8) = 0
getpid()                             = 15967
gettid()                             = 15967
tgkill(15967, 15967, SIGABRT)            = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
---SIGABRT {si_signo=SIGABRT,si_code=SI_TKILL,si_pid=15967,si_uid=1000}---

Mustererkennung mit strace und ltrace (das ptrace verwendet) ist nicht hinreichend zur Erkennung eines initialen fork-Aufrufs oder dem Verbergen von Signalen als Fuzzing-Schutzmaßnahme, auch ungeschützte Programme können ähnliche Muster aufweisen. Zur weiteren Untersuchung kann das Programm ausgeführt und diesem, beziehungsweise seinem Kindsprozess, verschiedene Signale gesendet werden. Beendet sich das Programm, bei einem der in Abschnitt 4.2.2 beschriebenen Signale, mit Rückgabewert 0, so ist von einer Fuzzing-Schutzmaßnahme auszugehen.

Absichtlich eingefügte nicht ausnutzbare Schwachstellen sind dagegen deutlich schwerer zu erkennen. Hier können statistische Analysen über die Anzahl und Art der durch Fuzzing gefundenen Bugs zusammen mit automatischen Triageergbnissen der gefunden Bugs einen Anhaltspunkt geben. Statische Analyse des Programms könnte bei Kenntnis der Art der eingefügten Bugs weitere Anhaltspunkte liefern.

Um Hashwert-Vergleiche zu erkennen, können entweder Zeichenketten exportiert und auf die Häufigkeit von bekannten Hashfunktionen beziehungsweise Entropie analysiert werden. Es können auch dynamische Methoden zur Erkennung von Hashfunktionen verwendet werden. Hier muss beim Schützen des Programms abgewogen werden zwischen Sicherheit der Hashfunktion oder Verschlüsselung und deren Detektierbarkeit, da sicherere Verfahren aufwändiger zu implementieren sind und eine Reihe typischer arithmetischer Operationen verwenden. Wegen der einfachen Möglichkeit, Hashfunktionen und Verschlüsselung zu erkennen, ist es im Bereich der Schadsoftware-Obfuscation verbreitet, auf kryptographisch starke Verfahren zu verzichten (Wressnegger, Boldewin, and Rieck 2013).

Entfernen von Fuzzing-Schutzmaßnahmen

Letztlich kann jede der Schutzmaßnahmen durch manuelle statische Analyse entfernt werden. Da das aber mit hohem menschlichen Aufwand verbunden ist, werden im Folgenden nur Methoden präsentiert, die Schutzmaßnahmen mit vergleichsweise geringem Aufwand entfernen oder umgehen.

Die ptrace-Erkennung kann vergleichsweise einfach getäuscht werden. In der LD_PRELOAD-Umgebungsvariable können eigene geteilte Bibliotheken angegeben werden, die dann vom Programm verwendet werden. Gibt man dort eine eigene Implementierung der ptrace-Funktion an, die immer 0 zurückgibt, so funktioniert die ptrace-Erkennung von (Edholm and Göransson 2016) und von (Güler et al. 2019b) nicht mehr. Wie bei allen in dieser Arbeit beschriebenen Schutzmaßnahmen kann eine lange Kette von Gegenmaßnahmen gegen Gegenmaßnahmen beschrieben werden – ein Wettrüsten, das den Aufwand auf beiden Seiten erhöht. Komplexere ptrace-Erkennungen würden hier ein komplexeres Vorgehen erfordern. Der weitere Teil des Kapitels geht daher von der in Kapitel 4 beschriebenen Form der Schutzmaßnahmen aus.

Initiale fork-Aufrufe können durch statische Analyse einfach gefunden werden. Hier gibt es mehrere Möglichkeiten das Programm trotzdem erfolgreich zu fuzzen. Es kann das Binärprogramm gepatcht werden. Statt dem fork-Aufruf wird dann die echte main-Methode aufgerufen. Das Warten auf den Prozess wird mit NOPs überschrieben. Damit kann die Schutzmaßnahme, wie in (Edholm and Göransson 2016) in Listing 6 beschrieben, entfernt werden. Der Fuzzer kann statt den Elternprozess, alternativ dessen Kindsprozess beobachten, dazu ist allerdings eine Anpassung des Fuzzers selbst notwendig.

Eine weitere Änderung des Fuzzers kann darin bestehen, die Systemaufrufe des Programms zu beobachten. Wird ein Muster wie in Listing [straceout] beobachtet, so ignoriert der Fuzzer den dadurch ausgelösten Absturz und führt den Fuzzingprozess fort.

Benutzt ein Programm Honeywords als Fuzzer-Erkennung, kann das zwar erkannt, aber nicht trivial entfernt werden. Honeywords werden so gewählt, dass sie schwer von anderen Zeichenketten in einem Programm unterscheidbar sind (Voris et al. 2013). Ein anderer Ansatz ist es, die Stelle zu finden, an der das Programm sich über mehrere Ausführungen merkt, dass Fuzzing erkannt wurde. Wenn die Implementierung der Schutzmaßnahme bekannt ist, ist das einfach möglich. Wird allerdings eine unbekannte Implementierung verwendet, ist das je nach Umgebung sehr aufwändig, da nur ein einzelnes Bit gespeichert werden muss und es dafür viele unterschiedliche Möglichkeiten gibt.

Auch Hashwert-Vergleiche durch starke Hashfunktionen sind einfach zu erkennen aber schwer zu entfernen. Wurden die Hashwerte zusammen mit den anderen Zeichenketten extrahiert, kann versucht werden die vorliegenden Hashwerte zu erraten. Dafür können Wissen über das Eingabeformat und die nicht gehashten Zeichenketten des Programms als Wörterbuch dienen. Liegt eine Menge von Testdaten vor, können diese verwendet werden, um dynamisch den Kontrollfluss nachzuvollziehen. Dieser Ansatz ist allerdings sehr aufwändig und verbraucht viele Ressourcen des Angreifers.

Für diese Arbeit wurde statische Analyse mit dem Dekompilierer Ghidra ausgeführt. Damit ist es in unter zehn Minuten möglich die Schutzmaßnahmen im Programm zu finden und dann mit einem Hex-edior zu entfernen. Für die Analyse wird das in der AntiFuzz-Software (Güler et al. 2019a) mitgelieferte Testprogramm verwendet. Es ist, auch wenn alle Schutzmaßnahmen aktiviert sind, ausreichend die init-Funktion durch NOPs zu ersetzten, um mit AFL-QEMU den Absturz im Programm zu finden. Wie auch beim ungeschützten Programm, wird der Absturz dann in unter einer Minute gefunden. Auch in einem Binärprogramm, in dem die Symbole entfernt wurden, ist das einfach möglich. Da der Quellcode von AntiFuzz öffentlich ist, können die Header-Dateien in Ghidra importiert werden. Somit ist auch in einem kompliziertem Programm das Auffinden und Entfernen der init-Funktion möglich. In (Güler et al. 2019b) wird explizit gemacht, dass in AntiFuzz keine Schutzmaßnahmen gegen das Entfernen der Schutzmaßnahmen eingebaut sind und zusätzlich Obfuscation-Techniken eingesetzt werden sollen. Damit ist der Schutz gegen Fuzzing mit AntiFuzz immer nur so schwer zu entfernen wie die verwendete Obfuscation.

In einem Paper aus 2020 stellen Wang et al. (Wang et al. 2020) einen Fuzzer TortoiseFuzz mit einer neuartigen Priorisierung von Eingaben vor. Fuzzer wie AFL sind rein abdeckungsgesteuert und deshalb sehr anfällig für die Schutzmaßnahme des Einfügens vieler Pfade. Um die Abdeckung zu maximieren, verbringt AFL viel Zeit damit, die funktionslosen Pfade (eingefügt durch (Güler et al. 2019b) oder (Jung et al. 2019b)) zu testen. Wang et al. schlagen deshalb vor, Eingaben anhand mehrerer Faktoren zu priorisieren. Faktoren der Priorisierung enthalten:

  • Eingaben die zu Funktionen führen, welche dafür bekannt sind bei falscher Benutzung Fehler zu verursachen. Beispiele sind memcpy, memset, malloc, free, etc.

  • Eingaben die zu Schleifenbedingungen führen, da Schleifenbedingungen beispielsweise Off-By-One-Fehler enthalten können. Aktuelle Fuzzing-Schutzmaßnahmen beinhalten weder diese Funktionen noch Schleifen.

Da die genannten Funktionen wegen der teilweise verwendeten Systemaufrufe aufwändig sind, können auch die Elementarblöcke nicht um diese ergänzt werden, ohne das Programm sehr zu verlangsamen. Für TortoiseFuzz existiert auch eine Version für Binärprogramme. Wang et al. evaluieren TortoiseFuzz gegen Fuzzification und messen dabei, dass die Programmabdeckung nur um 25% verglichen mit dem ungeschützten Binärprogramm reduziert wird, statt einer 90% Abnahme bei AFL. Leider ist der Quellcode von TortoiseFuzz zum Zeitpunkt der Einreichung noch nicht veröffentlicht worden.

Fazit

In dieser Arbeit wurde ein Überblick über Schutzmaßnahmen gegen Fuzzer zu geben. Keine der Methoden zur Fuzzer-Erkennung sind erfolgreich darin, große Klassen von Fuzzern zu erkennen. Schutzmaßnahmen, bei welchen kein Unterschied zwischen normaler Ausführung und der Ausführung durch einen Fuzzer besteht, sind breiter anwendbar. Diese Schutzmaßnahmen wurden theoretisch in ihrer Anwendbarkeit und Wirksamkeit bewertet. Dabei wurde, mittels einer formalen Definition von Fuzzing-Schutzmaßnahme gezeigt, dass nicht jede Schutzmaßnahme in jedem Kontext einsetzbar ist und einige Schutzmaßnahmen grundsätzliche, von einer Schutzmaßnahme erwartete Eigenschaften nicht erfüllen. Die Evaluationen anderer Forschungsarbeiten, sowie stichprobenartige Tests in dieser Arbeit zeigen, dass die betrachteten Methoden in Kombination wirksam Fuzzing verhindern, falls die Angreifer nicht von den Fuzzing-Schutzmaßnahmen wissen und das Programm eine typische Struktur hat. Mit TortoiseFuzz gibt es einen Fuzzer, der weit weniger anfällig gegen Fuzzing-Schutzmaßnahmen ist, als bisherige Fuzzer. Leider konnte aufgrund begrenzter Ressourcen keine umfangreichere Evaluation der Maßnahmen vorgenommen werden. Weiter zeigt diese Arbeit, dass momentane Schutzmaßnahmen gegen Fuzzing einfach zu erkennen sind. Angreifer, die von der Existenz einer Maßnahme wissen, können überprüfen, ob ein Programm diese verwendet, statt ihre Ressourcen auf ein geschütztes Programm zu verschwenden. Einzig für die in dieser Arbeit als Fuzzer-Erkennung vorgeschlagenen Honeywords kann keine zufriedenstellende Erkennung angegeben werden. Das Entfernen von Schutzmaßnahmen ist unterschiedlich schwierig. Versuche die Absturzerkennung des Fuzzers zu täuschen sind einfach zu entfernen, während Verzögerungen und das Einfügen vieler Pfade größere Herausforderungen darstellen. Außerdem wurde festgestellt, dass einige Schutzmaßnahmen unerwünschte Nebenwirkungen, wie die Möglichkeit von DoS-Angriffen oder der Vergrößerung der Menge der gültiger Eingaben, mit sich bringen. Neben der grundsätzlichen Überlegung, ob man die Schwachstellensuche für andere erschweren will, ist das eine weitere Abwägung, die für jeden Anwendungsfall individuell getroffen werden muss.

In zukünftiger Forschung können Fuzzing-Schutzmaßnahmen in Programmen dazu verwendet werden, Fuzzer zu evaluieren, da geschützte Programme eine Art schlechtesten Fall für aktuelle Fuzzer darstellen. Fuzzer, die es schaffen trotz Fuzzing-Schutzmaßnahmen gut zu funktionieren, könnten möglicherweise auch in ungeschützten Programmen mehr Bugs finden.

Ich möchte Jun.-Prof. Dr. Christian Wressnegger für das Anbieten dieses interessanten Seminars und die hervorragende Betreuung insbesondere während der Einschränkungen durch die Corona-Pandemie besonders danken. Außerdem möchte ich den anonymen Reviewern für ihr konstruktives, detailliertes und umfangreiches Feedback danken.

Ich veröffentliche finale Version dieser Serminararbeit unter CC-BY-SA-Lizenz. Siehe: https://creativecommons.org/licenses/by-sa/3.0/de/

Arya, Abhishek, Oliver Chang, Martin Barbella, and Jonathan Metzman. 2019. “Open Sourcing Clusterfuzz.” February 2019. https://github.com/mirrorer/afl/blob/master/docs/env_variables.txt.

Aschermann, Cornelius, Sergej Schumilo, Ali Abbasi, and Thorsten Holz. 2020. “IJON: Exploring Deep State Spaces via Fuzzing.” In 2020 Ieee Symposium on Security and Privacy (Sp), 1:893–908. IEEE Computer Society.

Aschermann, Cornelius, Sergej Schumilo, Tim Blazytko, Robert Gawlik, and Thorsten Holz. 2019. “REDQUEEN: Fuzzing with Input-to-State Correspondence.” In Symposium on Network and Distributed System Security (Ndss).

Autor, Unbekannter. 2016. “Circumventing Fuzzing Roadblocks with Compiler Transformations.” August 2016. https://lafintel.wordpress.com/.

Banescu, Sebastian, Christian Collberg, Vijay Ganesh, Zack Newsham, and Alexander Pretschner. 2016. “Code Obfuscation Against Symbolic Execution Attacks.” In Proceedings of the 32nd Annual Conference on Computer Security Applications, 189–200.

Barak, Boaz, Oded Goldreich, Rusell Impagliazzo, Steven Rudich, Amit Sahai, Salil Vadhan, and Ke Yang. 2001. “On the (Im) Possibility of Obfuscating Programs.” In Annual International Cryptology Conference, 1–18. Springer.

Basiri, Ali, Lorin Hochstein, Nora Jones, and Haley Tucker. 2019. “Automating Chaos Experiments in Production.” In 2019 Ieee/Acm 41st International Conference on Software Engineering: Software Engineering in Practice (Icse-Seip), 31–40.

Behera, Chandan Kumar, and Lalitha Bhaskari. 2015. “Different Obfuscation Techniques for Code Protection.” Procedia Computer Science 70: 757–63.

Bruening, Derek. 2014. “Self-Interpretation Due to ... on an Already-Caught Early Thread.” November 2014. https://github.com/DynamoRIO/dynamorio/issues/1443.

Chang, Oliver, Abhishek Arya, Kostya Serebryany, and Josh Armour. 2017. “OSS-Fuzz: Five Months Later, and Rewarding Projects.” May 2017. https://security.googleblog.com/2017/05/oss-fuzz-five-months-later-and.html.

Dinesh, Sushuant, Nathan Burow, Dongyan Xu, and Mathias Payer. 2020. “RetroWrite: Statically Instrumenting Cots Binaries for Fuzzing and Sanitization.” In 2020 Ieee Symposium on Security and Privacy (Sp), 128–42. IEEE Computer Society.

Edholm, Emil, and David Göransson. 2016. “Escaping the Fuzz-Evaluating Fuzzing Techniques and Fooling Them with Anti-Fuzzing.” Master’s thesis.

Güler, Emre, Cornelius Aschermann, Ali Abbasi, and Thorsten Holz. 2019a. “Antifuzz.” https://github.com/RUB-SysSec/antifuzz.

———. 2019b. “AntiFuzz: Impeding Fuzzing Audits of Binary Executables.” In 28th USENIX Security Symposium (USENIX Security 19), 1931–47. USENIX Association.

hu, zhenghao, yu hu, and brendan dolan-gavitt. 2018. “Chaff Bugs: Deterring Attackers by Making Software Buggier.” Arxiv Preprint Arxiv:1808.00659.

Huet, Thomas. 2017. “AFL Environmental Variables.” November 2017. https://github.com/mirrorer/afl/blob/master/docs/env_variables.txt.

Ispoglou, Kyriakos, Daniel Austin, Vishwath Mohan, and Mathias Payer. 2020. “FuzzGen: Automatic Fuzzer Generation.” In 29th USENIX Security Symposium (USENIX Security 20). USENIX Association.

Juels, Ari, and Ronald Rivest. 2013. “Honeywords: Making Password-Cracking Detectable.” In Proceedings of the 2013 Acm Sigsac Conference on Computer & Communications Security, 145–60. CCS ’13. Association for Computing Machinery.

Jung, Jinho, Hong Hu, David Solodukhin, Daniel Pagan, Kyu Hyung Lee, and Taesoo Kim. 2019a. “Fuzzification.” https://github.com/sslab-gatech/fuzzification.

———. 2019b. “Fuzzification: Anti-Fuzzing Techniques.” In 28th USENIX Security Symposium (USENIX Security 19), 1913–30. USENIX Association.

Kang Li. 2018. “AFL’s Blindspot and How to Resist Afl Fuzzing for Arbitrary Elf Binaries.” Black Hat USA. August 2018. https://i.blackhat.com/us-18/Wed-August-8/us-18-Li-AFLs-Blindspot-And-How-To-Resist-AFL-Fuzzing-For-Arbitrary-ELF-Binaries.pdf.

Leurent, Gaëtan, and Thomas Peyrin. 2020. “SHA-1 Is a Shambles - First Chosen-Prefix Collision on Sha-1 and Application to the Pgp Web of Trust.” Cryptology ePrint Archive, Report 2020/014.

Liu, Xiaolong, Qiang Wei, Qingxian Wang, Zheng Zhao, and Zhongxu Yin. 2018. “CAFA: A Checksum-Aware Fuzzing Assistant Tool for Coverage Improvement.” Security and Communication Networks 2018.

Miller, Barton, Louis Fredriksen, and Bryan So. 1990. “An Empirical Study of the Reliability of UNIX Utilities.” Communications of the ACM 33 (12): 32–44.

Miller, Charlie. 2010. “Anti-Fuzzing.” Unpublished. https://www.scribd.com/document/316851783/anti-fuzzing-pdf.

Peng, Hui, Yan Shoshitaishvili, and Mathias Payer. 2018. “T-Fuzz: Fuzzing by Program Transformation.” In 2018 Ieee Symposium on Security and Privacy (Sp), 697–710. IEEE.

“Radamsa.” n.d. Accessed July 4, 2020. https://gitlab.com/akihe/radamsa.

Sharma, Vaibhav, Navid Emamdoost, Seonmo Kim, and Stephen McCamant. 2020. “It Doesn’t Have to Be so Hard: Efficient Symbolic Reasoning for Crcs.”

Shastry, Bhargava, Markus Leutner, Tobias Fiebig, Kashyap Thimmaraju, Fabian Yamaguchi, Konrad Rieck, Stefan Schmid, Jean-Pierre Seifert, and Anja Feldmann. 2017. “Static Program Analysis as a Fuzzing Aid.” In International Symposium on Research in Attacks, Intrusions, and Defenses, 26–47. Springer.

She, Dongdong, Kexin Pei, Dave Epstein, Junfeng Yang, Baishakhi Ray, and Suman Jana. 2019. “NEUZZ: Efficient Fuzzing with Neural Program Smoothing.” In 2019 Ieee Symposium on Security and Privacy (Sp), 803–17. IEEE.

Stephens, Nick, John Grosen, Christopher Salls, Andrew Dutcher, Ruoyu Wang, Jacopo Corbetta, Yan Shoshitaishvili, Christopher Kruegel, and Giovanni Vigna. 2016. “Driller: Augmenting Fuzzing Through Selective Symbolic Execution.” In NDSS, 16:1–16. 2016.

Stevens, Marc. 2013. “New Collision Attacks on Sha-1 Based on Optimal Joint Local-Collision Analysis.” In Annual International Conference on the Theory and Applications of Cryptographic Techniques, 245–61. Springer.

Sutton, Michael, Adam Greene, and Pedram Amini. 2007. “Fuzzing: Brute Force Vulnerability Discovery,” January.

Tonder, Rijnard van, John Kotheimer, and Claire Le Goues. 2018. “Semantic Crash Bucketing.” In Proceedings of the 33rd ACM/IEEE International Conference on Automated Software Engineering - ASE 2018. ACM Press.

Voris, Jonathan, Jill Jermyn, Angelos Keromytis, and Salvatore Stolfo. 2013. “Bait and Snitch: Defending Computer Systems with Decoys.”

Wang, Yanhao, Xiangkun Jia, Yuwei Liu, Kyle Zeng, Tiffany Bao, Dinghao Wu, and Purui Su. 2020. “Not All Coverage Measurements Are Equal: Fuzzing by Coverage Accounting for Input Prioritization.” In.

Whitehouse, Ollie. 2014. “Introduction to Anti-Fuzzing: A Defence in Depth Aid.” January 2014. https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2014/january/introduction-to-anti-fuzzing-a-defence-in-depth-aid/.

Wressnegger, Christian, Frank Boldewin, and Konrad Rieck. 2013. “Deobfuscating Embedded Malware Using Probable-Plaintext Attacks.” In International Workshop on Recent Advances in Intrusion Detection, 164–83. Springer.

Yang, Xuejun, Yang Chen, Eric Eide, and John Regehr. 2011. “Finding and Understanding Bugs in c Compilers.” In Proceedings of the 32nd Acm Sigplan Conference on Programming Language Design and Implementation, 283–94.

Yun, Insu, Sangho Lee, Meng Xu, Yeongjin Jang, and Taesoo Kim. 2018. “QSYM : A Practical Concolic Execution Engine Tailored for Hybrid Fuzzing.” In 27th USENIX Security Symposium (USENIX Security 18), 745–61. USENIX Association.