
Ein Hypervisor für Fuzzing, erstellt mit WHVP und Bochs.
Hallo! Willkommen bei applepie! Dieses Tool wurde für Fuzzing, Introspection und das Finden von Bugs entwickelt! Es ist ein Hypervisor, der die Windows Hypervisor Platform API verwendet, die in aktuellen Windows-Versionen vorhanden ist (insbesondere wurde es auf Windows 10 17763 entwickelt und getestet). Bochs wird für tiefgehende Introspection und Geräteemulation verwendet.
Die Windows Hypervisor Platform API (WHVP) ist eine API-Sammlung, um auf die Hypervisor-Fähigkeiten von Hyper-V zuzugreifen. Diese API macht es uns leicht, eine virtuelle Maschine komplett im Userspace zu implementieren, ohne dass spezielle Treiber oder Berechtigungen erforderlich sind.
Dies ist ein sich schnell entwickelndes Projekt. Ich werde wahrscheinlich twittern, wenn neue Funktionen erscheinen, bevor sie dokumentiert werden.
Ich mag physische Dinge für meine Projekte:
Dieses Tool wurde für Fuzzing und Introspection während der Sicherheitsforschung entwickelt. Durch die Verwendung eines Hypervisors können gängige Fuzzing-Techniken auf jedes Ziel angewendet werden, sei es Kernel oder Userspace. Diese Umgebung ermöglicht das Fuzzing ganzer Systeme ohne Quellcode des Ziels. Auf Hypervisor-Ebene kann Code Coverage gesammelt werden, und bei Bedarf kann die Bochs-Emulation genutzt werden, um in einer Emulationsumgebung beliebige Introspection durchzuführen. Diese Coverage-Informationen können verwendet werden, um die Effektivität der Fuzz-Fälle zu ermitteln. Ein Fuzz-Fall, der zu einer Erhöhung der Coverage führt, kann als interessanter Fall gespeichert werden. Dieser Input kann später verwendet werden und durch neue Korruptionen erweitert werden.
Snapshot-Fuzzing ist die primäre Verwendung dieses Tools. Dabei wird ein Snapshot eines Systems in einem bestimmten Zustand erstellt und gespeichert. Dieser Snapshot kann dann zum Fuzzing geladen werden, ein Fuzz-Fall wird injiziert und die Ausführung fortgesetzt. Da die VM sehr günstig zurückgesetzt werden kann, kann die VM oft zurückgesetzt werden. Wenn Word 5 Sekunden zum Booten braucht, man es aber genau dann snapshotieren kann, wenn es die Datei liest, kann der Fuzz-Fall auf das reduziert werden, was für einen Input relevant ist. Dies ermöglicht eine sehr enge Fuzzing-Schleife, ohne dass Quellcode benötigt wird. Da die VMs vollständig getrennte Systeme sind, können viele parallel ausgeführt werden, um auf alle Kerne zu skalieren.
Derzeit unterstützt dieses Tool nur das Sammeln von Code Coverage, das dynamische Herunterladen von Symbolen für Windows und das Parsen von Symbolen/Modulen für Windows-Ziele. Die Fuzzing-Unterstützung wird bald hinzugefügt.
Da ich die meisten dieser Funktionen bereits entwickelt habe (Coverage, Fuzzing, schnelle Resets, etc.), erwarte ich, dass dieses Projekt ziemlich schnell fuzzing-bereit wird, es sei denn, ich werde abgelenkt :D
Ich strebe Ende Januar für Coverage (erledigt!), Feedback, Modulauflistungen (erledigt!), Prozesslisten, schnelle Resets und Symbolunterstützung (erledigt!) an. Damit wäre es ein sehr leistungsfähiger Fuzzer.
Das Hauptziel ist modernes Windows 10. Windows-Ziele unterstützen das Herunterladen von Symbolen aus dem Symbolspeicher. Dies ermöglicht symbolische Coverage bei Windows-Zielen von Haus aus. Der Code ist jedoch so geschrieben, dass eine Linux-Erleuchtung (enlightenment) einfach hinzugefügt werden kann.
Ohne jegliche Erleuchtung kann jedes bootfähige Betriebssystem dennoch gefuzzt werden und grundlegende Coverage gesammelt werden.
Bevor Sie Probleme mit der Betriebssystemunterstützung melden, validieren Sie bitte, ob das Problem im Hypervisor/den Änderungen an Bochs liegt, indem Sie versuchen, Ihr Ziel mit standardmäßig vorkompiliertem Bochs ohne Hypervisor zu booten. Bochs wird nicht häufig verwendet und kann oft Fehler verursachen, selbst bei alltäglichen Dingen wie dem Booten von Linux. Insbesondere aufgrund der schnellen internen Änderungen an CPUID/MSR-Verwendungen im Zusammenhang mit Spectre/Meltdown-Mitigation, die in Betriebssysteme einfließen.
Siehe die Issues-Seite auf GitHub für eine Liste der Probleme. Ich habe sie bereits mit einigen bestückt. Einige davon müssen schnell behoben werden, bevor die Fuzzing-Entwicklung beginnt.
Zum Bauen benötigen Sie einige Dinge:
Installieren Sie Visual Studio 2017 und stellen Sie sicher, dass es aktualisiert ist. Wir verwenden hier einige hochaktuelle APIs, Header und Bibliotheken.
Ich habe die cl.exe-Version verwendet: Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x64
Und SDK-Version 10.0.17763.0
Installieren Sie Rust über https://rustup.rs/. Ich habe rustc 1.32.0-nightly (b3af09205 2018-12-04) verwendet.
Stellen Sie sicher, dass Sie die Toolchain x86_64-pc-windows-msvc installieren, da nur 64-Bit für dieses Projekt unterstützt wird.
Stellen Sie sicher, dass cargo in Ihrem Pfad ist. Dies sollte standardmäßig der Fall sein.
Holen Sie sich Python von https://www.python.org/ und stellen Sie sicher, dass es in Ihrem PATH ist, sodass python aufgerufen werden kann.
Installieren Sie 64-Bit-Cygwin (https://www.cygwin.com/setup-x86_64.exe) speziell nach C:\cygwin64. Stellen Sie bei der Installation von Cygwin sicher, dass Sie die Pakete autoconf und make installieren.
Gehen Sie zu "Windows-Features aktivieren oder deaktivieren" und aktivieren Sie das Kontrollkästchen neben "Hyper-V" und "Windows Hypervisor Platform". Dies setzt natürlich voraus, dass Ihr Computer Hyper-V unterstützt.
Diese Installationsanleitung wurde auf Folgendem verifiziert:
Saubere Installation von Windows 10, Build 17763
rustc 1.33.0-nightly (8e2063d02 2019-01-07)
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x64
Visual Studio Community 2017 Version 15.9.4
applepie Commit `f84c084feb487e2e7f31f9052a4ab0addd2c4cf9`
Python 3.7.2 x64
git version 2.20.1.windows.1



autoconf package)make package)git clone https://github.com/gamozolabs/applepiepython build.py aus
Dieser anfängliche Build-Prozess kann etwa 2 Minuten dauern, auf einem modernen Rechner wahrscheinlich 20-30 Sekunden.
Führen Sie einfach python build.py aus dem Stammverzeichnis dieses Projekts aus. Es sollte die Umgebung auf Plausibilität prüfen und alles sollte "einfach funktionieren".
Führen Sie python build.py clean aus, um Bochs- und Rust-Binärdateien zu bereinigen.
Führen Sie python build.py deepclean aus, um alle Bochs- und Rust-Binärdateien vollständig zu entfernen. Es entfernt auch die gesamte Konfiguration für Bochs. Verwenden Sie dies, wenn Sie Bochs auf irgendeine Weise neu konfigurieren.
Lesen Sie die Bochs-Konfiguration, um herauszufinden, wie Sie Ihre Umgebung einrichten. Wir haben einige Anforderungen, wie sync=none, ips=1000000 und derzeit nur Single-Prozessor-Unterstützung. Diese werden im Code selbst erzwungen, um sicherzustellen, dass Sie sich nicht selbst ins Bein schießen.
Verwenden Sie die enthaltenen Konfigurationen bochservisor_test\bochsrc.bxrc und bochservisor_test_real\bochsrc.bxrc als Beispiele. bochservisor_test_real ist wahrscheinlich die aktuellste Konfiguration, die Sie als Referenz betrachten sollten.
Windows-Ziele haben eine Modullisten-Erleuchtung, die es uns ermöglicht, die Auflistungen aller Module im Kontext zu sehen, in dem wir arbeiten. Damit können wir die Befehlsadressen in Modul + Offset umwandeln. Diese Modul + Offset hilft, Coverage-Informationen zwischen Fuzz-Fällen zu erhalten, wenn sich der ASLR-Zustand ändert. Es ermöglicht auch, das Modul in einem Tool wie IDA einzufärben, um visuell zu sehen, welcher Code getroffen wurde.
Für Windows-Ziele werden Symbole dynamisch aus dem Symbolspeicher heruntergeladen, unter Verwendung Ihres _NT_SYMBOL_PATH und symchk. Ohne symchk im Pfad schlägt dies still fehl. Mit Symbolen kann eine menschenlesbare Version der Coverage zur Ansicht gespeichert werden. Darüber hinaus kann die Coverage mit privaten Symbolen in Quelle:Zeile umgewandelt werden, sodass Quellcode eingefärbt werden kann.
Okay, es gibt nicht wirklich Tests, aber es gibt bochservisor_test, ein winziges Betriebssystem, das lediglich überprüft, ob alles mit dem Hypervisor bootet.
Es gibt dann bochservisor_test_real, eine Konfiguration, die ich für Dinge wie Windows/Linux verwende. Dies wird wahrscheinlich am häufigsten aktualisiert.
Diese Codebasis führt eine kleine Menge Code in Bochs ein, um modularen Zugriff auf den CPU-Kontext, die physischen Speicher der Gäste und das Step-by-Step-Ausführen sowohl von Geräten als auch des CPU-Zustands zu ermöglichen.
Der Hauptcode, den Sie sich ansehen sollten, befindet sich in lib.rs im Rust-Projekt bochservisor.
In der Haupt-CPU-Schleife von Bochs laden wir stattdessen LoadLibrary(), um die bochservisor-DLL zu laden. Diese DLL exportiert eine Routine, die die Rust-CPU-Schleife ist, die aufgerufen wird.
Bochs übergibt eine Struktur an diese bochs_cpu_loop-Routine, die Funktionszeiger enthält, um Informationen von Bochs zu erhalten und den Geräte- und CPU-Zustand schrittweise auszuführen.
Wenn MMIO oder I/O auftritt, wird der Hypervisor mit einem Speicherfehler oder einem I/O-Befehl-Fehler beendet. Obwohl WHVP eine Emulations-API bereitstellt, ist diese wirklich lückenhaft und nicht ausreichend.
Stattdessen verwenden wir Bochs, das bereits vorhanden ist, und führen einige Befehle schrittweise aus. Indem wir den Hypervisor-CPU-Zustand mit Bochs synchron halten, können wir dynamisch zwischen Hypervisor und Emulation zu jeder Zeit wechseln (oder zumindest sollten wir das können).
Dies bedeutet, dass der vollständige Hypervisor-Zustand immer mit Bochs synchronisiert ist und daher Dinge wie Bochs-Snapshots normal funktionieren sollten und ohne Hypervisor gebootet werden könnten (außer vielleicht einigen CPUID-Zuständen, die in den Snapshot-Informationen gespeichert werden müssen).
Wenn MMIO oder I/O auftritt, führen wir eine bestimmte Anzahl von Befehlen unter Emulation aus, anstatt nur einen zu emulieren. Aufgrund der API-Kosten für das Betreten und Verlassen des Hypervisors und der Wahrscheinlichkeit, dass ähnliche MMIO-Operationen nebeneinander auftreten, führen wir einige Befehle schrittweise aus. Dies ermöglicht es uns, den Overhead der API zu reduzieren und die VMEXIT-Häufigkeit zu verringern. Dies ist ein einstellbarer Wert, aber was in der Codebasis vorhanden ist, ist wahrscheinlich aus gutem Grund dort.
Interrupts behandeln wir auf eine wirklich interessante Weise. Anstatt Interrupts für die Zustellung an den Hypervisor zu planen, behandeln wir alle Interrupts in der Bochs-Emulation selbst. Dinge wie Ausnahmen, die vollständig innerhalb des Hypervisors auftreten, werden natürlich nicht von Bochs behandelt.
Dies gibt uns auch Funktionen, die WHVP nicht unterstützt, wie SMIs (für SMM). Das Bochs-BIOS verwendet standardmäßig SMM, und ohne SMI-Unterstützung muss ein benutzerdefiniertes BIOS erstellt werden. Das habe ich in meiner ersten Iteration gemacht... nicht zu empfehlen.
Dieses Projekt ist für Fuzzing konzipiert, aber es ist so neu (nur ein paar Tage alt), dass es noch keine dieser Funktionen hat.
Einige der ersten Dinge, die kommen werden, sind:
Wir könnten potenziell Bochs-Geräte in einem Thread in einer Schleife in Echtzeit laufen lassen und einen anderen Thread den Hypervisor ausführen lassen. Asynchrone Ereignisse würden über IPC kommuniziert und ermöglichen, dass die Geräte aktualisiert werden, während die Ausführung im Gast stattfindet.
Derzeit passiert alles in einem Thread, was bedeutet, dass der Hypervisor in einem Intervall beendet werden muss, um sicherzustellen, dass wir Geräte schrittweise ausführen können. Es ist, als hätten wir unseren eigenen Scheduler geschrieben.
Dies könnte etwas schneller sein, erhöht aber auch die Komplexität und fügt potenzielle Race-Bedingungen hinzu. Es ist schwer zu sagen, ob dies jemals passieren wird.
Ich bin mir nicht sicher, welche Methode ich zum Sammeln von Code Coverage verwenden werde, aber es wird mindestens ein paar Optionen geben. Von genau bis schnell usw. Alle diese Coverage-Mechanismen werden auf Systemebene sein und weder Quellcode noch Symbole von Zielen erfordern.
Parsen von Betriebssystemstrukturen, um primitive Informationen wie Prozessauflistungen, Modullisten usw. zu erhalten. Diese würden dann verwendet, um PDBs abzufragen und Symbolinformationen zu erhalten.
Meldung von Abstürzen in einer sinnvollen Weise. Idealerweise wären Minidumps schön, da sie geladen und in WinDbg verarbeitet werden könnten. Dies könnte ziemlich einfach sein, da DMPs nur physischer Speicher und Prozessorkontext sind, die wir bereits haben.
Ich habe einige unterhaltsame Techniken zur Ursachenanalyse von Bugs, die historisch erfolgreich waren. Ich habe vor, diese hierher zu bringen.
Durch die Verfolgung von Dirty Pages und das Wiederherstellen nur modifizierter Dinge sollten wir in der Lage sein, VMs sehr schnell zurückzusetzen. Dies gibt uns die Fähigkeit, mit maximaler Geschwindigkeit auf allen Kernen eines Systemziels zu fuzzen. Dies ähnelt dem, was ich in falkervisor gemacht habe, es ist bereits durchdacht und entworfen. Es muss nur hierher portiert werden.
Extrem schnelles Fuzzing, das die Ausführung abbricht, wenn MMIO oder I/O auftritt. Dies ermöglicht es, die gesamte CPU-Zeit im Hypervisor zu verbringen, ohne Emulationszeit. Dies hat den Nachteil, dass Dinge wie Platten-I/O während eines Fuzz-Falls nicht unterstützt werden, aber es ist nett.
Einige der Kernkonzepte dieses Projekts sind minimale Änderungen an Bochs. Dies ermöglicht es uns, den Bochs-Teil dieses Repos aktuell zu halten.
Das Ziel ist es auch, so viel Code wie möglich nach Rust und in DLLs zu verschieben, um das System viel modularer und sicherer zu machen. Dies wird hoffentlich die Wahrscheinlichkeit verringern, dass wir dumme Korruptions-Bugs im Hypervisor selbst verursachen, die zu ungültigen Fuzz-Ergebnissen führen.
Derzeit ist der Hypervisor eine DLL und kann ohne Änderungen an Bochs ausgetauscht werden (es sei denn, die FFI-API ändert sich).
Weitere Änderungen an Bochs selbst müssen klar dokumentiert werden, und ich werde in Kürze ein Dokument dafür erstellen, um die Änderungen an Bochs zu verfolgen, die mit Bochs-Updates portiert und neu bewertet werden müssen.