
Un ipervisore per fuzzing costruito con WHVP e Bochs
Ciao! Benvenuto in applepie! Questo è uno strumento progettato per fuzzing, introspezione e ricerca di bug! È un hypervisor che utilizza l'API Windows Hypervisor Platform presente nelle versioni recenti di Windows (in particolare è stato sviluppato e testato su Windows 10 17763). Bochs viene utilizzato per fornire un'introspezione approfondita e l'emulazione dei dispositivi.
L'API Windows Hypervisor Platform (WHVP) è un insieme di API per accedere alle funzionalità di hypervisor di Hyper-V. Questa API ci facilita l'implementazione di una macchina virtuale interamente in spazio utente senza bisogno di driver speciali o permessi.
Questo è un progetto in rapido sviluppo. Probabilmente twitto quando escono nuove funzionalità prima che vengano documentate.
Mi piace avere oggetti fisici per i miei progetti:
Questo è uno strumento progettato per il fuzzing e l'introspezione durante la ricerca sulla sicurezza. Utilizzando un hypervisor, è possibile applicare tecniche comuni di fuzzing a qualsiasi target, kernel o spazio utente. Questo ambiente permette di fare fuzzing di interi sistemi senza bisogno del codice sorgente del target. A livello di hypervisor è possibile raccogliere la copertura del codice e, se necessario, l'emulazione Bochs può essere utilizzata per fornire introspezione arbitraria in un ambiente di emulazione. Queste informazioni di copertura possono essere utilizzate per determinare l'efficacia dei casi di fuzz. Un caso di fuzz che ha causato un aumento della copertura può essere salvato poiché è risultato interessante. Questo input può essere utilizzato in seguito, costruendovi sopra nuove corruzioni.
Il fuzzing con snapshot è l'uso principale di questo strumento. Si prende uno snapshot di un sistema in un certo stato e lo si salva. Questo snapshot può poi essere caricato per il fuzzing, dove viene iniettato un caso di fuzz e si riprende l'esecuzione. Poiché la VM può essere resettata molto a buon mercato, la VM può essere resettata spesso. Se Word impiega 5 secondi per avviarsi, ma si può fare uno snapshot proprio mentre legge il file, si può ridurre il caso di fuzz a solo ciò che è rilevante per un input. Ciò consente un ciclo molto stretto di fuzzing senza dover avere accesso al codice sorgente. Poiché le VM sono sistemi completamente separati, molte possono essere eseguite in parallelo per scalare su tutti i core.
Attualmente questo strumento supporta solo la raccolta della copertura del codice, il download dinamico dei simboli per Windows e l'analisi di simboli/moduli per target Windows. L'aggiunta del supporto per il fuzzing sarà molto presto.
Dato che ho già scritto quasi tutte le funzionalità qui in precedenza (copertura, fuzzing, reset rapidi, ecc.), mi aspetto che questo progetto diventi rapidamente pronto per il fuzzing, a meno che non venga distratto :D
Punto a fine gennaio per la copertura (fatto!), feedback, elenchi di moduli (fatto!), elenchi di processi, reset rapidi e supporto dei simboli (fatto!). Il che lo renderebbe un fuzzer molto capace.
Il target principale supportato è il moderno Windows 10. I target Windows hanno il download dei simboli dal symbol store. Ciò consente una copertura simbolica nei target Windows pronta all'uso. Tuttavia, il codice è scritto in modo tale che l'enlightenment per Linux possa essere facilmente aggiunto.
Senza alcun enlightenment, qualsiasi SO che si avvia può comunque essere sottoposto a fuzzing e si può raccogliere una copertura di base.
Prima di segnalare problemi di supporto SO, verifica che il problema sia nell'hypervisor/modifiche a Bochs provando ad avviare il tuo target utilizzando Bochs standard precompilato senza hypervisor. Bochs non è comunemente usato e può avere frequenti bug che rompono anche cose comuni come l'avvio di Linux. Soprattutto con i rapidi cambiamenti interni agli usi di CPUID/MSR con le mitigazioni Spectre/Meltdown che vengono integrate nei SO.
Vedi la pagina delle issues su Github per un elenco di problemi. Ne ho già inseriti alcuni. Alcuni di questi devono essere affrontati rapidamente prima di iniziare lo sviluppo del fuzzing.
Per compilare questo hai bisogno di alcune cose:
Installa Visual Studio 2017 e assicurati che sia aggiornato. Stiamo usando API, header e librerie all'avanguardia qui.
Stavo usando cl.exe versione: Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x64
E SDK versione 10.0.17763.0
Installa Rust tramite https://rustup.rs/. Ho usato rustc 1.32.0-nightly (b3af09205 2018-12-04)
Assicurati di installare il toolchain x86_64-pc-windows-msvc poiché per questo progetto è supportato solo il 64-bit.
Assicurati che cargo sia nel tuo PATH. Dovrebbe essere l'impostazione predefinita.
Vai a prendere Python https://www.python.org/ e assicurati che sia nel tuo PATH in modo che python possa essere invocato.
Installa Cygwin a 64 bit (https://www.cygwin.com/setup-x86_64.exe) specificamente in C:\cygwin64. Durante l'installazione di Cygwin assicurati di installare i pacchetti autoconf e make.
Vai su "Attiva o disattiva funzionalità di Windows" e spunta la casella accanto a "Hyper-V" e "Piattaforma Hypervisor Windows". Questo richiede ovviamente che il tuo computer supporti Hyper-V.
Questa guida al processo di installazione è stata verificata su:
Clean install of 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)make)git clone https://github.com/gamozolabs/applepiepython build.py
Questo processo di compilazione iniziale può richiedere circa 2 minuti, su una macchina moderna probabilmente 20-30 secondi.
Basta eseguire python build.py dalla directory principale di questo progetto. Dovrebbe verificare la correttezza dell'ambiente e tutto dovrebbe "funzionare e basta".
Esegui python build.py clean per pulire i binari di Bochs e Rust.
Esegui python build.py deepclean per rimuovere completamente tutti i binari di Bochs e Rust, rimuove anche tutta la configurazione per Bochs. Usalo se riconfiguri Bochs in qualche modo.
Leggi la configurazione di Bochs per capire come impostare il tuo ambiente. Abbiamo alcuni requisiti, come sync=none, ips=1000000, e attualmente solo supporto per processore singolo. Questi sono applicati all'interno del codice stesso per assicurarti di non spararti sui piedi.
Usa le configurazioni incluse bochservisor_test\bochsrc.bxrc e bochservisor_test_real\bochsrc.bxrc come esempi. bochservisor_test_real è probabilmente la configurazione più aggiornata che dovresti guardare come riferimento.
I target Windows hanno l'enlightenment dell'elenco dei moduli, che ci permette di vedere gli elenchi di tutti i moduli nel contesto in cui stiamo eseguendo. Con questo possiamo convertire gli indirizzi delle istruzioni in modulo + offset. Questo modulo + offset aiuta a mantenere le informazioni di copertura tra casi di fuzz in cui lo stato ASLR cambia. Permette anche di colorare il modulo in uno strumento come IDA per vedere visivamente quale codice è stato eseguito.
Per i target Windows, i simboli verranno scaricati dinamicamente dal symbol store usando il tuo _NT_SYMBOL_PATH e usando symchk. Senza symchk nel PATH fallirà silenziosamente. Con i simboli è possibile salvare una versione leggibile della copertura per la visualizzazione. Inoltre, con i simboli privati, la copertura può essere convertita in sorgente:riga in modo che il codice sorgente possa essere colorato.
Ok, non ci sono veramente test, ma c'è bochservisor_test che è un minuscolo sistema operativo che verifica solo che tutto si avvii con l'hypervisor.
C'è poi bochservisor_test_real che è una configurazione che uso per cose come Windows/Linux. Questa è quella che probabilmente verrà aggiornata più frequentemente.
Questo codice introduce una piccola quantità di codice in Bochs per consentire l'accesso modulare al contesto della CPU, dalla memoria fisica dell'ospite alla memoria di supporto, e il passo passo dello stato del dispositivo e della CPU.
Il codice principale che vuoi guardare è in lib.rs nel progetto Rust bochservisor.
Nel ciclo principale della CPU di Bochs usiamo invece LoadLibrary() per caricare la DLL bochservisor. Questa DLL esporta una routine che è il ciclo della CPU in Rust che verrà invocato.
Bochs passerà una struttura a questa routine bochs_cpu_loop che conterrà puntatori a funzioni per ottenere informazioni da Bochs e per avanzare lo stato del dispositivo e della CPU al suo interno.
Quando si verifica MMIO o I/O, l'hypervisor uscirà con un fault di memoria o un fault di istruzione I/O. Mentre WHVP fornisce un'API di emulazione, è davvero carente e non sufficiente.
Piuttosto usiamo Bochs che è già lì e avanziamo di poche istruzioni. Mantenendo lo stato della CPU dell'hypervisor sincronizzato con Bochs, possiamo passare dinamicamente tra hypervisor ed emulazione in qualsiasi momento (o almeno dovremmo essere in grado di farlo).
Ciò significa che l'intero stato dell'hypervisor è sempre sincronizzato con Bochs e quindi cose come gli snapshot di Bochs dovrebbero funzionare normalmente e potrebbero essere avviati senza l'hypervisor (tranne forse alcuni stati CPUID che devono essere memorizzati nelle informazioni dello snapshot).
Quando si verifica MMIO o I/O, eseguiamo un certo numero di istruzioni in emulazione anziché emularne solo una. A causa dei costi dell'API per entrare e uscire dall'hypervisor, e della probabilità che operazioni MMIO simili si verifichino vicine ad altre, avanziamo di poche istruzioni. Questo ci permette di ridurre il sovraccarico dell'API e riduce la frequenza di VMEXIT. Questo è un numero regolabile, ma quello che c'è nel codice probabilmente è lì per un motivo.
Gestiamo gli interrupt in un modo molto interessante. Piuttosto che programmare gli interrupt da consegnare all'hypervisor, gestiamo tutti gli interrupt nell'emulazione stessa di Bochs. Naturalmente, cose come le eccezioni che si verificano interamente all'interno dell'hypervisor non sono gestite da Bochs.
Questo ci offre anche funzionalità che WHVP non supporta, come gli SMI (per SMM). Il BIOS di Bochs usa SMM per impostazione predefinita e senza il supporto SMI è necessario costruire un BIOS personalizzato. L'ho fatto nella mia prima iterazione di questo... non lo consiglio.
Questo progetto è progettato per il fuzzing, tuttavia è così nuovo (ha solo pochi giorni) che non ha nessuna di queste funzionalità.
Alcune delle prime cose che arriveranno saranno:
Potremmo potenzialmente avere le cose del dispositivo di Bochs in esecuzione in un thread in un ciclo in tempo reale, e un altro thread che esegue l'hypervisor. Gli eventi asincroni verrebbero comunicati tramite IPC e permetterebbero di aggiornare i dispositivi mentre l'esecuzione è nell'ospite.
Attualmente tutto avviene in un thread, il che significa che l'hypervisor deve uscire a intervalli per assicurarsi di poter avanzare i dispositivi. È come se avessimo scritto il nostro scheduler.
Potrebbe essere un po' più veloce, ma aumenta anche la complessità e aggiunge il potenziale per problemi di race. È difficile dire se mai accadrà.
Non sono sicuro di quale metodo userò per raccogliere la copertura del codice, ma ci saranno almeno alcune opzioni. Che spaziano da accurate, a veloci, ecc. Tutti questi meccanismi di copertura saranno a livello di sistema e non richiederanno codice sorgente o simboli dei target.
Analisi delle strutture del sistema operativo per ottenere informazioni primitive come elenchi di processi, elenchi di moduli, ecc. Questo verrebbe poi utilizzato per interrogare i PDB e ottenere informazioni sui simboli.
Segnalare i crash in modo significativo. Idealmente, i minidump sarebbero belli perché potrebbero essere caricati ed elaborati in WinDbg. Questo potrebbe essere abbastanza facile poiché i DMP sono solo memoria fisica e contesto del processore, che abbiamo già.
Ho alcune tecniche divertenti per l'analisi delle cause profonde dei bug che hanno avuto successo storicamente. Intendo portarle qui.
Tracciando le pagine sporche e ripristinando solo le cose modificate, dovremmo essere in grado di resettare le VM molto rapidamente. Questo ci dà la capacità di fare fuzzing a velocità massima su tutti i core di un sistema target. È simile a quello che ho fatto in falkervisor, quindi è già pensato e progettato. Deve solo essere portato qui.
Fuzzing estremamente veloce che annulla l'esecuzione quando si verifica MMIO o I/O. Ciò permette di trascorrere tutto il tempo della CPU nell'hypervisor e nessun tempo di emulazione. Ha lo svantaggio di non supportare cose come I/O su disco durante un caso di fuzz, ma è bello.
Alcuni dei concetti fondamentali di questo progetto sono modifiche minime assolute a Bochs. Questo ci permette di mantenere aggiornata la parte Bochs di questo repository.
L'obiettivo è anche spostare quanto più codice possibile in Rust e DLL per rendere il sistema molto più modulare e sicuro. Ciò sperabilmente ridurrà le possibilità di introdurre stupidi bug di corruzione nell'hypervisor stesso, causando risultati di fuzz non validi.
Attualmente l'hypervisor è una DLL e può essere sostituito senza modifiche a Bochs (a meno che l'API FFI non cambi).
Ulteriori modifiche a Bochs stesso devono essere documentate chiaramente, e presto farò un documento per tenere traccia delle modifiche a Bochs che devono essere portate e rivalutate con gli aggiornamenti di Bochs.