
Fuzzer distribuito, basato su snapshot e guidato dalla copertura del codice per target in modalità utente e kernel su Windows e Linux, con backend emulatore e hypervisor.
what the fuzzUn fuzzer distribuito, guidato dalla copertura del codice, cross-platform, basato su snapshot, progettato per attaccare target in modalità utente e/o kernel in esecuzione su Microsoft Windows e Linux in modalità utente (sperimentale!).
what the fuzz o wtf è un fuzzer distribuito, guidato dalla copertura del codice, personalizzabile, cross-platform, basato su snapshot, progettato per attaccare target in modalità utente e/o kernel in esecuzione su Microsoft Windows o Linux (sperimentale, vedere linux_mode). L'esecuzione del target può essere effettuata all'interno di un emulatore con bochscpu (più lento, più preciso), all'interno di una VM Windows con le API Windows Hypervisor Platform o all'interno di una VM Linux con le API KVM (più veloce).
Ha scoperto vulnerabilità di corruzione della memoria in un'ampia gamma di software: IDA Pro, un popolare gioco AAA, il kernel Windows, il client RDP Microsoft, il driver per display GPU NVIDIA, ecc.
I binari compilati sono disponibili sia dagli artefatti CI che dalla sezione Releases sia per Windows che per Linux.
Se desideri saperne di più sulla sua storia o su come usarlo su un target reale, ti consiglio di dare un'occhiata a questi post per iniziare 🔥
Il modo migliore per provare le funzionalità è lavorare con i moduli fuzzer_hevd / fuzzer_tlv_server. Puoi scaricare gli archivi target-hevd.7z / target-tlv_server.7z ed estrarli nella directory targets/. Gli archivi contengono le strutture di directory previste per ogni target:
inputs è la cartella dove inserire i tuoi casi di test di input,outputs è la cartella dove vengono salvati i file minset correnti,coverage è la cartella dove ci si aspetta che siano i file .cov,crashes è dove vengono salvati i crash,state è dove vengono archiviati il dump della memoria (mem.dmp), lo stato della CPU (regs.json) e lo store dei simboli (symbol-store.json). Lo store dei simboli è un semplice file JSON che viene utilizzato sui sistemi Linux per sapere dove posizionare i breakpoint poiché non c'è supporto per simboli / dbgeng su quelle piattaforme. wtf genera questo file in esecuzione ogni volta che esegui il target su Windows.Quanto segue presuppone che tu abbia scaricato il file target-hevd.7z allegato all'ultima release e lo abbia estratto nella directory targets del tuo clone di wtf. Dovresti avere wtf/targets/hevd in cui trovi le directory inputs / outputs, ecc.
Il server è fondamentalmente il cervello e tiene traccia di tutto lo stato: la copertura del codice aggregata, il corpus, genera e distribuisce i casi di test al client.
Ecco come potresti scegliere di avviare un nodo server locale:```text wtf.exe master --name hevd --max_len=1028 --runs=10000000
L'opzione `max_len` è usata per limitare la dimensione del test-case generato, `runs` è il numero di test-case che verranno generati, `address` specifica dove **wtf** deve restare in ascolto, `target` è una directory con l'albero delle directory descritto sopra (l'utente può anche scegliere di sovrascrivere quelle directory con `--input` / `--output` / `--crashes`) e `name` specifica il nome del tuo modulo di fuzzing in modo che il master possa invocare la tua funzione generatrice se ne hai definita una.
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/4a03fc75eed3ed5a92f7f10def697dbf220ae36b0701f05b37759eb432fc0fd9.webp">
</p>
### Nodi di fuzzing
I nodi client eseguono un test-case che è stato generato e distribuito dal server e comunicano il risultato al server (copertura del codice, risultato, ecc.).
Ecco come si avvia un nodo client che utilizza il backend *bochscpu*:```text
wtf.exe fuzz --name hevd --limit 10000000
Il sottocomando fuzz viene utilizzato con l'opzione name per specificare quale modulo fuzzer deve essere utilizzato, backend specifica il backend di esecuzione e limit il numero massimo di istruzioni da eseguire per test case (a seconda del backend, questa opzione ha un significato diverso).
Se desideri eseguire un test-case (o una cartella piena di test-case), puoi utilizzare il sottocomando run.
Ecco come eseguiresti il test-case crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0:```
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>
### Minset di un corpus
Per eseguire il minset di un corpus, è necessario utilizzare un nodo server e tanti nodi client quanti necessari, come si farebbe per un lavoro di fuzzing. Puoi semplicemente impostare l'opzione `runs` a 0.
Ecco come eseguire il minset del corpus in `outputs` nella directory `minset` (mostra anche come sovrascrivere le directory `inputs` e `outputs`):```
wtf.exe master --name hevd --max_len=1028 --runs=0 --inputs=outputs --outputs=minset
Il meccanismo principale disponibile per ispezionare in un backend di esecuzione è generare una traccia di esecuzione. bochscpu è il backend più veloce per farlo, perché uscire dalla modalità VMX è molto costoso negli altri backend.
Ecco come genereresti una traccia di esecuzione per il test-case 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>
Per simbolizzare le tracce di esecuzione dovresti usare [symbolizer-rs](https://github.com/0vercl0k/symbolizer-rs). Ecco come simbolizzeresti la traccia di esecuzione `crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0.trace` generata sopra:```
symbolizer-rs.exe --trace crash-0xfffff764b91c0000-0x0-0xffffbf84fb10e780-0x2-0x0.rip.trace
Se hai bisogno di maggiore consapevolezza contestuale, il backend bochscpu ti permette di generare tracce di esecuzione che possono essere caricate nell'esploratore di tracce Tenet. Qui sotto, parto da un crash in memmove e torno indietro per scoprire da dove proviene il puntatore sorgente (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>
### Generazione di tracce di copertura del codice
Per generare tracce di copertura del codice puoi semplicemente usare il sottocomando `run` con l'opzione `--trace-type=cov`.
Ecco come generare tracce di copertura del codice per tutti i file all'interno della cartella `minset` e salvarle nella cartella `coverage-traces`:```
wtf.exe run --name hevd --input minset --trace-path=coverage-traces --trace-type=cov
Queste tracce non sono direttamente caricabili in lighthouse perché non sono simbolizzate.
Ecco come simbolizzare tutti i file all'interno della cartella coverage-traces e scrivere i risultati in coverage-traces-symbolized:```
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>
E infine, puoi caricare questi file in [lighthouse](https://github.com/gaasedelen/lighthouse):
<p align='center'>
<img src="https://assets.kitploit.com/production/public/readmes/4699/35d9c25c3d26abf5761fec9e7848ba09d090c7aab32322a740732d82e85964e8.webp">
</p>
Inoltre, se non ti interessa la copertura del codice individuale, il master mantiene un file `coverage.cov` che contiene la copertura aggregata unica del codice che è stata esercitata. Semplifica la verifica rapida della copertura globale durante un lavoro di fuzzing.
## Come funziona?
**wtf** esegue la modalità utente e kernel attraverso un *execution backend* e si affida all'utente per inserire i casi di test nel target. A differenza di altri strumenti di fuzzing classici, **wtf** non fa gran parte del lavoro pesante; lo fa l'utente. L'utente deve conoscere molto bene il target e l'onboarding di un target è un processo iterativo che richiede tempo. Offre comunque molta flessibilità se sei pronto a metterti all'opera :)
Il flusso di lavoro usuale per preparare un target è il seguente:
1. Esegui il tuo target in una VM Hyper-V con Windows, con una CPU virtuale e 4GB di RAM.
1. Porta il tuo target nello stato desiderato usando [KD](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/). Ad esempio, per targettare il gestore IOCTL di [HEVD](https://github.com/hacksysteam/HackSysExtremeVulnerableDriver), ho scelto di fermare il target in modalità utente appena prima che il client invochi [DeviceIoControl](https://docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol). Questo varia a seconda dei target, ma probabilmente vorrai essere vicino al codice che vuoi fuzzare.
```
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. Usa [snapshot](https://github.com/0vercl0k/snapshot) per generare il dump di crash del kernel e il file `regs.json` che contiene lo stato della CPU. Raccomando di salvare questi file in una directory `state` sotto la tua directory `target` (ad esempio `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. Crea un [modulo fuzzer](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc), scrivi il codice che [inserisce un caso di test](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L20) nel target e definisci [le](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L81) [varie](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L104) [condizioni](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L115) per [rilevare crash](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L115) o [la fine di un caso di test](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_hevd.cc#L69).
1. Puoi anche creare il tuo mutatore/generatore sottoclassando l'interfaccia [Mutator_t](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/mutator.h). Il [fuzzer_tlv_server.cc](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) è un buon esempio per capire come implementare il tuo.
A questo punto dovresti iniziare a iterare e verificare che il modulo fuzzer funzioni come previsto. I back-end di esecuzione sono una scatola nera, quindi dovresti generare tracce di esecuzione per assicurarti che percorra i percorsi giusti e faccia le cose giuste. Durante questa fase uso principalmente il backend [bochscpu](https://github.com/yrp604/bochscpu) perché è completamente deterministico, si avvia velocemente, è possibile generare tracce di esecuzione, la copertura del codice è gratuita, ecc. Nel complesso, è un ambiente più piacevole per sviluppare e prototipare.
Una volta che sei soddisfatto del modulo, puoi iniziare a considerare di farlo funzionare con i backend [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) se hai bisogno che funzioni sotto di essi. Una differenza importante tra il backend *bochscpu* e gli altri è che gli altri usano breakpoint software per fornire informazioni sulla copertura del codice. Di conseguenza, dovrai caricare i moduli per cui vuoi la copertura in [IDA](https://hex-rays.com/IDA-pro/) e usare lo script [gen_coveragefile_ida.py](https://github.com/0vercl0k/wtf/blob/HEAD/scripts/gen_coveragefile_ida.py) per generare un semplice file JSON che viene caricato da wtf. Sei libero di generare tu stesso questo file JSON con qualsiasi strumento: è fondamentalmente un elenco di indirizzi virtuali di blocchi di base.
Puoi anche targettare applicazioni [WoW64](https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details) usando il comando `!wow64exts.sw` di Windbg per passare al contesto a 64-bit appena prima di creare lo snapshot (grazie a [@cube0x8](https://twitter.com/cube0x8) per aver condiviso questo trucco!):```
32.kd:x86> !wow64exts.sw
The context is partially valid. Only x86 user-mode context is available.
Switched to Host mode
32.kd> !snapshot
I target complessi solitamente hanno anche stati complessi ed è probabile che tu debba inviare più di un testcase in una sessione per innescare problemi complessi. tlv_server.cc è un esempio di un server di questo tipo in cui esercitare la funzione di parsing con un solo testcase non è sufficiente per scoprire i bug.
Per gestire questo caso, dai un'occhiata a fuzzer_tlv_server.cc che mostra un esempio di come risolvere questo problema.
wtf viene fornito con due popolari mutator generici: libfuzzer e honggfuzz. Potresti voler fornire il tuo o generare testcase autonomamente.
Per farlo, puoi creare una sottoclasse dell'interfaccia Mutator_t e registrare la funzione che istanzia il tuo mutator quando definisci il tuo modulo di fuzzing:```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);
Dai un'occhiata alla classe [CustomMutator_t](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) nel modulo [fuzzer_tlv_server.cc](https://github.com/0vercl0k/wtf/blob/HEAD/src/wtf/fuzzer_tlv_server.cc) per un esempio completo.
## Execution backends
In questa sezione menziono brevemente varie differenze tra i backend di esecuzione.
### bochscpu
- ✅ Copertura completa del codice di sistema (copertura degli edge disponibile tramite `--edges`),
- ✅ Demand-paging,
- ✅ Timeout è il numero di istruzioni, molto preciso,
- ✅ Tracciamento completo delle esecuzioni supportato,
- ✅ Completamente deterministico,
- ❌ Velocità sembra buona per esecuzioni brevi ma non per esecuzioni lunghe (~100x più lento di KVM quando fuzzavo IDA).
### whv
- ✔ Copertura del codice tramite breakpoint software,
- ❌ Demand-paging, quindi l'avvio è lento (poiché deve caricare l'intero crash-dump in memoria),
- ✔ Timeout implementato con un timer,
- ✅ Tracciamento completo delle esecuzioni supportato ma lento (l'uscita da VMX è costosa),
- ✔ Deterministico se si gestisce manualmente la fonte di non deterministico (ad esempio, patchando `nt!ExGenRamdom` che usa `rdrand`),
- ✔ Velocità sembra ok per esecuzioni lunghe (molti colli di bottiglia in whv comunque; ~10x più lento di kvm quando fuzzavo IDA).
### KVM
- ✔ Copertura del codice tramite breakpoint software,
- ✅ Demand-paging supportato tramite UFDD,
- ✔ Timeout implementato con un timer. ✅ Se l'hardware supporta la virtualizzazione PMU, viene utilizzato per generare un [PMI](https://forum.osdev.org/viewtopic.php?f=1&t=27040) dopo X istruzioni ritirate (`MSR_IA32_FIXED_CTR0`),
- ✅ Tracciamento completo delle esecuzioni supportato ma lento (l'uscita da VMX è costosa),
- ✔ Deterministico se si gestisce manualmente la fonte di non deterministico (ad esempio, patchando `nt!ExGenRamdom` che usa `rdrand`),
- ✅ Più veloce per esecuzioni lunghe (~500m - 1.5 miliardi di istruzioni; ~100x più veloce di *bochscpu*, ~10x più veloce di *whv* quando fuzzavo IDA).
## Build
La [CI](https://github.com/0vercl0k/wtf/actions/workflows/wtf.yml) compila **wtf** su Ubuntu usando sia [clang++](https://clang.llvm.org/) che [g++](https://gcc.gnu.org/gcc-11/), su Windows usando [Visual Studio](https://visualstudio.microsoft.com/vs/community/) di Microsoft e su OSX usando [clang++](https://clang.llvm.org/).
Per compilarlo da solo devi avviare un *Visual Studio Developper Command Prompt* ed eseguire [build-release.bat](https://github.com/0vercl0k/wtf/blob/HEAD/src/build/build-release.bat) che usa il generatore [Ninja](https://ninja-build.org/) oppure [build-release-msvc.bat](https://github.com/0vercl0k/wtf/blob/HEAD/src/build/build-release-msvc.bat) per generare un file di soluzione di Visual Studio:```
(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
Un ringraziamento speciale a: