
Verteilter, code-coverage-gesteuerter snapshot-basierter Fuzzer für Benutzermodus- und Kernelmodus-Ziele auf Windows und Linux, mit Emulator- und Hypervisor-Backends.
what the fuzzEin verteilter, code-abdeckungsgesteuerter, plattformübergreifender snapshot-basierter Fuzzer, der für Angriffe auf Benutzer- und/oder Kernel-Modus-Ziele unter Microsoft Windows und Linux-Benutzermodus (experimentell!) entwickelt wurde.
what the fuzz oder wtf ist ein verteilter, code-abdeckungsgesteuerter, anpassbarer, plattformübergreifender snapshot-basierter Fuzzer, der für Angriffe auf Benutzer- und/oder Kernel-Modus-Ziele unter Microsoft Windows oder Linux (experimentell, siehe linux_mode) entwickelt wurde. Die Ausführung des Ziels kann innerhalb eines Emulators mit bochscpu (langsam, präzise), innerhalb einer Windows-VM mit den Windows Hypervisor Platform APIs oder innerhalb einer Linux-VM mit den KVM APIs (schnellste) erfolgen.
Es deckte Speicherkorruptions-Schwachstellen in einer Vielzahl von Software auf: IDA Pro, ein beliebtes AAA-Spiel, den Windows-Kernel, den Microsoft RDP-Client, den NVIDIA GPU Display-Treiber, usw.
Kompilierte Binärdateien sind entweder aus den CI-Artefakten oder aus dem Abschnitt Releases für Windows und Linux verfügbar.
Wenn Sie mehr über seine Geschichte oder die Verwendung an einem echten Ziel erfahren möchten, empfehle ich, sich diese Beiträge anzusehen, um loszulegen 🔥
Die beste Möglichkeit, die Funktionen auszuprobieren, ist die Arbeit mit den Modulen fuzzer_hevd / fuzzer_tlv_server. Sie können die Archive target-hevd.7z / target-tlv_server.7z herunterladen und in das Verzeichnis targets/ extrahieren. Die Archive enthalten die Verzeichnisstrukturen, die für alle Ziele erwartet werden:
inputs ist der Ordner, in den Ihre Eingabe-Testfälle abgelegt werden,outputs ist der Ordner, in den die aktuellen Minset-Dateien gespeichert werden,coverage ist der Ordner, in dem die .cov-Dateien erwartet werden,crashes ist der Ort, an dem die Abstürze gespeichert werden,state ist der Ort, an dem der Speicherauszug (mem.dmp) sowie der CPU-Zustand (regs.json) und der Symbol-Store (symbol-store.json) gespeichert werden. Der Symbol-Store ist eine einfache JSON-Datei, die auf Linux-Systemen verwendet wird, um zu wissen, wo Haltepunkte gesetzt werden müssen, da auf diesen Plattformen keine Unterstützung für Symbole / dbgeng vorhanden ist. wtf generiert diese Datei zur Laufzeit jedes Mal, wenn Sie Ihr Ziel unter Windows ausführen.Im Folgenden wird davon ausgegangen, dass Sie die Datei target-hevd.7z des neuesten Releases heruntergeladen und in das targets-Verzeichnis Ihres Klons von wtf extrahiert haben. Sie sollten wtf/targets/hevd haben, in dem Sie die Verzeichnisse inputs / outputs, usw. finden.
Der Server ist im Grunde das Gehirn und verwaltet den gesamten Zustand: die aggregierte Code-Abdeckung, das Korpus, er generiert und verteilt die Testfälle an den Client.
So könnten Sie einen lokalen Server-Knoten starten:```text wtf.exe master --name hevd --max_len=1028 --runs=10000000
Die Option `max_len` wird verwendet, um die Größe des generierten Testfalls zu begrenzen, `runs` ist die Anzahl der Testfälle, die generiert werden, `address` gibt an, wo **wtf** lauschen muss, `target` ist ein Verzeichnis mit dem oben beschriebenen Verzeichnisbaum (der Benutzer kann diese Verzeichnisse auch mit `--input` / `--output` / `--crashes` überschreiben) und `name` gibt den Namen Ihres Fuzzing-Moduls an, damit der Master Ihre Generatorfunktion aufrufen kann, falls Sie eine definiert haben.
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/4a03fc75eed3ed5a92f7f10def697dbf220ae36b0701f05b37759eb432fc0fd9.webp">
</p>
### Fuzzing-Knoten
Die Client-Knoten führen einen Testfall aus, der vom Server generiert und verteilt wurde, und übermitteln das Ergebnis zurück an den Server (Codeabdeckung, Ergebnis usw.).
So würden Sie einen Client-Knoten starten, der das *bochscpu*-Backend verwendet:```text
wtf.exe fuzz --name hevd --limit 10000000
Der Befehl fuzz wird mit der Option name verwendet, um anzugeben, welches Fuzzer-Modul verwendet werden soll. backend gibt das Ausführungs-Backend an und limit die maximale Anzahl von Anweisungen, die pro Testfall ausgeführt werden sollen (abhängig vom Backend hat diese Option eine unterschiedliche Bedeutung).
Wenn Sie einen Testfall (oder einen Ordner mit Testfällen) ausführen möchten, können Sie den Befehl run verwenden.
So würden Sie den Testfall crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0 ausführen:```
wtf.exe run --name hevd --limit 10000000 --input crashes\crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/49b3ca8582d6314724e5615c687472499d5f8f41d04c9543c0a6a070c51f56f8.webp">
</p>
### Minseting a corpus
Um einen Corpus zu minset, müssen Sie einen Serverknoten sowie so viele Clientknoten wie nötig verwenden, ähnlich wie bei einem Fuzzing-Job. Sie können einfach die `runs`-Optionen auf 0 setzen.
So würden Sie den Corpus in `outputs` in das `minset`-Verzeichnis minset (zeigt auch, wie Sie die Verzeichnisse `inputs` und `outputs` überschreiben können):```
wtf.exe master --name hevd --max_len=1028 --runs=0 --inputs=outputs --outputs=minset
Der wichtigste Mechanismus zur Analyse in einem Ausführungs-Backend ist die Erzeugung einer Ausführungsspur. bochscpu ist das schnellste Backend dafür, da das Verlassen des VMX-Modus auf den anderen Backends sehr teuer ist.
So erzeugen Sie eine Ausführungsspur für den Testfall crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0:```
wtf.exe run --name hevd --limit 10000000 --input crashes\crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0 --trace-type=rip
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/340b4b251e378eaf21c6f5ffdd4f013bd2cef89f23a604cffd0ebf897d27c71f.webp">
</p>
Um Ausführungsspuren zu symbolisieren, sollten Sie [symbolizer-rs](https://github.com/0vercl0k/symbolizer-rs) verwenden. So symbolisieren Sie den oben generierten Ausführungstrace `crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0.trace`:```
symbolizer-rs.exe --trace crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0.rip.trace
Wenn Sie feststellen, dass Sie mehr Kontextbewusstsein benötigen, ermöglicht das bochscpu-Backend die Generierung von Ausführungsspuren, die im Tenet-Trace-Explorer geladen werden können. Im Folgenden beginne ich mit einem Absturz in memmove und gehe zurück, um herauszufinden, woher der Quellzeiger stammt (user-mode!):```
wtf.exe run --name hevd --limit 10000000 --input crashes\crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0 --trace-type=tenet
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/34df2f5ca8f378db664d3f01bcbefdd43409e300d256d50e3f4f630eb06cc7be.webp">
</p>
### Generieren von Code-Coverage-Traces
Um Code-Coverage-Traces zu generieren, kannst du einfach den Unterbefehl `run` mit der Option `--trace-type=cov` verwenden.
So würdest du Code-Coverage-Traces für alle Dateien im Ordner `minset` generieren und im Ordner `coverage-traces` speichern:```
wtf.exe run --name hevd --input minset --trace-path=coverage-traces --trace-type=cov
Diese Traces sind nicht direkt in lighthouse ladbar, weil sie nicht symbolisiert sind.
So würden Sie alle Dateien im Ordner coverage-traces symbolisieren und die Ergebnisse in coverage-traces-symbolized schreiben:```
symbolizer-rs.exe --trace coverage-traces -o coverage-traces-symbolized --style modoff
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/8a3c3ca18571abef16a9f87f736fe4d52ac102f41095eab4db32533cb5b198b4.webp">
</p>
Und schließlich können Sie diese in [lighthouse](https://github.com/gaasedelen/lighthouse) laden:
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/35d9c25c3d26abf5761fec9e7848ba09d090c7aab32322a740732d82e85964e8.webp">
</p>
Wenn Sie sich nicht für die individuelle Codeabdeckung interessieren, verwaltet der Master eine `coverage.cov`-Datei, die die eindeutige aggregierte Codeabdeckung enthält, die ausgeübt wurde. Es erleichtert die schnelle Überprüfung der globalen Codeabdeckung während eines Fuzzing-Jobs.
## Wie funktioniert es?
**wtf** führt Benutzer- und Kernelmodus über ein *Ausführungs-Backend* aus und verlässt sich darauf, dass der Benutzer Testfälle in das Ziel einfügt. Im Gegensatz zu anderen klassischen Fuzzer-Tools erledigt **wtf** nicht viel der Schwerarbeit; der Benutzer tut es. Der Benutzer muss das präparierte Ziel sehr gut kennen, und das Einbinden eines Ziels ist ein iterativer Prozess, der Zeit in Anspruch nehmen wird. Es bietet jedoch viel Flexibilität, wenn Sie bereit sind, loszulegen :)
Der übliche Arbeitsablauf zum Einbinden eines Ziels ist wie folgt:
1. Bringen Sie Ihr Ziel in einer Hyper-V-VM mit Windows und einer virtuellen CPU und 4 GB RAM zum Laufen.
1. Versetzen Sie Ihr Ziel mithilfe von [KD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/) in den gewünschten Zustand. Zum Beispiel habe ich für das Ziel des [HEVD](https://github.com/hacksysteam/HackSysExtremeVulnerableDriver)-IOCTL-Handlers das Ziel im Benutzermodus direkt vor dem Aufruf von [DeviceIoControl](https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol) durch den Client gestoppt. Dies variiert je nach Ihren Zielen, aber Sie möchten es wahrscheinlich in der Nähe des Codes, den Sie fuzzen möchten.
```
kd> r
rax=000000dfd98ff3d0 rbx=0000000000000088 rcx=0000000000000088
rdx=00000000deadbeef rsi=0000000000000000 rdi=0000000000000000
rip=00007ff6f5bb111e rsp=000000dfd98ff380 rbp=0000000000000000
r8=000000dfd98ff3d0 r9=0000000000000400 r10=000002263e823055
r11=00007ff6f5bcb54d r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
hevd_client!main+0xae:
00007ff6`f5bb111e ff15dc1e0100 call qword ptr [hevd_client!_imp_DeviceIoControl (00007ff6`f5bc3000)] ds:002b:00007ff6`f5bc3000={KERNEL32!DeviceIoControlImplementation (00007ff8`3e2e6360)}
```
1. Verwenden Sie [snapshot](https://github.com/0vercl0k/snapshot), um den Kernel-Crash-Dump sowie die Datei `regs.json` zu generieren, die den CPU-Zustand enthält. Ich empfehle, diese Dateien in einem `state`-Verzeichnis unter Ihrem `target`-Verzeichnis abzulegen (z. B. `targets/hevd/state`):
```
kd> .load c:\work\codes\snapshot\target\release\snapshot.dll
kd> !snapshot -h
[snapshot] Usage: snapshot [OPTIONS] [STATE_PATH]
Arguments:
[STATE_PATH] The path to save the snapshot to
Options:
-k, --kind <KIND> The kind of snapshot to take [default: full] [possible values: active-kernel, full]
-h, --help Print help
kd> !snapshot c:\work\codes\wtf\targets\hevd\state
[snapshot] Dumping the CPU state into c:\work\codes\wtf\targets\hevd\state\regs.json..
[snapshot] Dumping the memory state into c:\work\codes\wtf\targets\hevd\state\mem.dmp..
Creating c:\\work\\codes\\wtf\\targets\\hevd\\state\\mem.dmp - Full memory range dump
0% written.
5% written. 1 min 50 sec remaining.
10% written. 1 min 17 sec remaining.
15% written. 1 min 30 sec remaining.
[...]
Wrote 4.0 GB in 1 min 32 sec.
The average transfer rate was 44.5 MB/s.
Dump successfully written
[snapshot] Done!
```
1. Erstellen Sie ein [Fuzzer-Modul](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc), schreiben Sie den Code, der [einen Testfall einfügt](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L20) in Ihr Ziel, und definieren Sie [die](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L81) [verschiedenen](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L104) [Bedingungen](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L115), um [Abstürze zu erkennen](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L115) oder [das Ende eines Testfalls](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L69).
1. Sie können auch Ihren eigenen Mutator/Generator erstellen, indem Sie die [Mutator_t](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/mutator.h)-Schnittstelle ableiten. Die [fuzzer_tlv_server.cc](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) ist ein gutes Beispiel, um zu verstehen, wie Sie Ihren eigenen implementieren könnten.
An diesem Punkt sollten Sie beginnen, zu iterieren und zu überprüfen, ob das Fuzzer-Modul wie erwartet funktioniert. Die Ausführungs-Backends sind eine Blackbox, daher sollten Sie Ausführungsspuren generieren, um sicherzustellen, dass die richtigen Pfade durchlaufen werden und die richtigen Dinge getan werden. In dieser Phase verwende ich hauptsächlich das [bochscpu](https://github.com/yrp604/bochscpu)-Backend, da es vollständig deterministisch ist, schnell startet, das Generieren von Ausführungsspuren möglich ist, Codeabdeckung kostenlos kommt usw. Insgesamt ist es eine angenehmere Umgebung zum Entwickeln und Prototypen.
Sobald Sie mit dem Modul zufrieden sind, können Sie beginnen, es mit den [winhv](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/whv_backend.h)- / [kvm](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/kvm_backend.h)-Backends zum Laufen zu bringen, wenn es unter diesen laufen soll. Ein wesentlicher Unterschied zwischen dem *bochscpu*-Backend und den anderen besteht darin, dass die anderen Software-Breakpoints verwenden, um Codeabdeckungsinformationen bereitzustellen. Daher müssen Sie die Module, für die Sie eine Abdeckung wünschen, unter [IDA](https://hex-rays.com/IDA-pro/) laden und das Skript [gen_coveragefile_ida.py](https://github.com/0vercl0k/wtf/blob/HEAD/scripts/gen_coveragefile_ida.py) verwenden, um eine einfache JSON-Datei zu generieren, die von wtf geladen wird. Sie können diese JSON-Datei auch selbst mit einem beliebigen Tool generieren: Es handelt sich im Grunde um eine Liste von Basisblock-Virtualadressen.
Sie können auch [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details)-Anwendungen anvisieren, indem Sie den Windbg-Befehl `!wow64exts.sw` verwenden, um direkt vor der Erstellung des Snapshots in den 64-Bit-Kontext zu wechseln (Dank an [@cube0x8](https://twitter.com/cube0x8) für diesen Trick!):```
32.kd:x86> !wow64exts.sw
The context is partially valid. Only x86 user-mode context is available.
Switched to Host mode
32.kd> !snapshot
Komplexe Ziele haben in der Regel auch komplexe Zustände, und es ist wahrscheinlich, dass Sie mehr als einen Testfall in einer Sitzung liefern müssen, um komplexe Probleme auszulösen. tlv_server.cc ist ein Beispiel für einen solchen Server, bei dem das Ausführen der Parsing-Funktion mit nur einem Testfall nicht ausreicht, um die Fehler zu entdecken.
Um diesen Fall zu behandeln, sehen Sie sich fuzzer_tlv_server.cc an, das ein Beispiel zeigt, wie dieses Problem gelöst werden kann.
wtf wird mit zwei beliebten generischen Mutatoren geliefert: libfuzzer & honggfuzz. Möglicherweise möchten Sie Ihren eigenen bereitstellen oder selbst Testfälle generieren.
Dazu können Sie die Mutator_t-Schnittstelle ableiten und die Funktion registrieren, die Ihren Mutator instanziiert, wenn Sie Ihr Fuzzing-Modul definieren:```c++ class CustomMutator_t : public Mutator_t { public: static std::unique_ptr<Mutator_t> Create(std::mt19937_64 &Rng, const size_t TestcaseMaxSize) { return std::make_unique<CustomMutator_t>(Rng, TestcaseMaxSize); } // ... };
Target_t target("target", Init, InsertTestcase, Restore, CustomMutator_t::Create);
Check out the [CustomMutator_t](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) class in the [fuzzer_tlv_server.cc](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) module for a complete example.
## Ausführungs-Backends
In diesem Abschnitt erwähne ich kurz verschiedene Unterschiede zwischen den Ausführungs-Backends.
### bochscpu
- ✅ Vollständige System-Codeabdeckung (Kantenabdeckung über `--edges` verfügbar),
- ✅ Demand-Paging,
- ✅ Timeout ist die Anzahl der Instruktionen, was sehr präzise ist,
- ✅ Vollständige Ausführungs-Traces werden unterstützt,
- ✅ Vollständig deterministisch,
- ❌ Geschwindigkeit scheint für kurze Ausführungen gut zu sein, aber nicht für lange Ausführungen (~100x langsamer als KVM, als ich IDA fuzzed).
### whv
- ✔ Codeabdeckung über Software-Breakpoints,
- ❌ Demand-Paging, daher langsamer Start (da der vollständige Crash-Dump in den Speicher geladen werden muss),
- ✔ Timeout wird mit einem Timer implementiert,
- ✅ Vollständige Ausführungs-Traces werden unterstützt, sind aber langsam (Beenden von VMX ist kostspielig),
- ✔ Deterministisch, wenn die Quelle der Nichtdeterminismen manuell behandelt wird (z.B. Patchen von `nt!ExGenRamdom`, das `rdrand` verwendet),
- ✔ Geschwindigkeit scheint für lange Ausführungen in Ordnung zu sein (allerdings viele Engpässe in whv; ~10x langsamer als KVM, als ich IDA fuzzed).
### KVM
- ✔ Codeabdeckung über Software-Breakpoints,
- ✅ Demand-Paging wird über UFDD unterstützt,
- ✔ Timeout wird mit einem Timer implementiert. ✅ Wenn die Hardware PMU-Virtualisierung unterstützt, wird diese verwendet, um einen [PMI](https://forum.osdev.org/viewtopic.php?f=1&t=27040) nach X zurückgezogenen Instruktionen zu erzeugen (`MSR_IA32_FIXED_CTR0`),
- ✅ Vollständige Ausführungs-Traces werden unterstützt, sind aber langsam (Beenden von VMX ist kostspielig),
- ✔ Deterministisch, wenn die Quelle der Nichtdeterminismen manuell behandelt wird (z.B. Patchen von `nt!ExGenRamdom`, das `rdrand` verwendet),
- ✅ Am schnellsten für lange Ausführungen (~500 Mio. - 1,5 Mrd. Instruktionen; ~100x schneller als *bochscpu*, ~10x schneller als *whv*, als ich IDA fuzzed).
## Build
Die [CI](https://github.com/0verclk0/wtf/actions/workflows/wtf.yml) baut **wtf** auf Ubuntu sowohl mit [clang++](https://clang.llvm.org/) / [g++](https://gcc.gnu.org/gcc-11/), auf Windows mit Microsofts [Visual Studio](https://visualstudio.microsoft.com/vs/community/) und auf OSX mit [clang++](https://clang.llvm.org/).
Um es selbst zu bauen, müssen Sie eine *Visual Studio Developer-Eingabeaufforderung* starten und entweder [build-release.bat](https://github.com/0vercl0k/wtf/blob/HEAD/src/build/build-release.bat) ausführen, das den [Ninja](https://ninja-build.org/)-Generator verwendet, oder [build-release-msvc.bat](https://github.com/0vercl0k/wtf/blob/HEAD/src/build/build-release-msvc.bat), um eine Visual Studio-Projektmappendatei zu generieren:```
(base) wtf\src\build>build-release.bat
[...]
[2/2] Linking CXX executable wtf.exe
(base) wtf\src\build_msvc>..\build\build-release-msvc.bat
[...]
Finished generating code
wtf.vcxproj -> wtf\src\build_msvc\RelWithDebInfo\wtf.exe
Building Custom Rule wtf/src/CMakeLists.txt
Besonderer Dank geht an: