
Strumento di strumentazione binaria statica per eseguibili Windows x64
peafl64 è uno strumento di instrumentazione statica per PE x64 in Windows.
L'instrumentazione statica è la pratica di modificare i file eseguibili e aggiungere codice in posizioni specifiche al loro interno.
L'instrumentazione aggiunge codice all'inizio di ogni blocco di base nel binario, registrando il flusso di esecuzione in modo compatibile con AFL.
Consente di fare fuzzing su binari in User mode (usando WinAFL) e Kernel mode (usando kAFL) senza accesso al loro codice sorgente.
Ci sono altri modi per fare fuzzing su binari Windows; in questo progetto abbiamo scelto di concentrarci sull'instrumentazione statica perché è il metodo più veloce.
Questo progetto si basa sullo strumento pe-afl di wmliang, con supporto aggiuntivo per x64.
Lo script di instrumentazione richiede un output dell'analisi di IDA.
Per crearlo, esegui lo script ida_dumper.py fornito in IDA.
Lo script richiede IDA 7+ e python3.8+.
usage: pe_afl.py [-h] [-n] [-cb] [-tf] [-te THREAD_ENTRY] [-nt NTOSKRNL] [-e ENTRY] [-l ENLARGE] [-v] [-lf] pefile ida_dump
positional arguments:
pefile Target PE file for instrumentation
ida_dump dump.json from IDA (created by ida_dumper.py)
optional arguments:
-h, --help show this help message and exit
-n, --nop Instrument with NOPs for testing
-cb, --callback Instrument with a callback, which is in the helper driver that's written in C
-tf, --thread-filter Driver instrumentation that filters only on thread ID (must use "-te" with this option)
-te THREAD_ENTRY, --thread-entry THREAD_ENTRY
The address (RVA) of the thread's initialization function
-nt NTOSKRNL, --ntoskrnl NTOSKRNL
ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver)
-e ENTRY, --entry ENTRY
Inject code on entry point, ie. -e9090
-l ENLARGE, --enlarge ENLARGE
Enlarge factor for sections, default=4
-v, --verbose Print debug log
-lf, --logfile Print log to pe-afl64.log rather than stream
Instrumentazione di un binario in user mode con NOP
PS pe-afl-64> python .\pe_afl.py -n C:\Work\cmd.exe C:\Work\cmd.exe.dump.json
[*] User-mode binary is being instrumented
[*] Single-thread instrument is on
[*] Preparing new sections
[*] Added section .text^
[*] Added section .cov
[*] Expanding relative jumps
[*] Expanded 3874 of 14353 branches
[*] Building address map
[*] Updating relative instructions
[*] Updating relocations...
[*] Updating Export table
[*] Updating load config
[*] Updating exception records
[*] Updating the PE headers
[*] Finalizing...
[*] Creating instrumented code
[*] Writing address mapping to C:\Work\cmd.exe.mapping.txt
[*] Updating jump tables
[*] Updated .text
...
[*] Removing temporary PE files
[*] Fixing PE checksum
[*] Instrumented binary saved to: C:\Work\cmd.instrumented.exe
Instrumentazione di un binario in kernel mode con filtro per process ID
python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json"
Instrumentazione di un binario in kernel mode con filtro per thread ID e output verboso
python .\pe_afl.py -v -tf -te 0x40000 -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe.dump.json"
Prima di tutto devi verificare se la tua macchina si avvia usando BIOS o UEFI.
Per le macchine Hyper-V: le macchine Gen 1 sono basate su BIOS e le Gen 2 su UEFI.
Se la tua macchina è basata su BIOS, devi applicare una patch a winload.exe:
ImgpValidateImageHashmov eax, edi con xor eax, eax nell'ultimo blocco di codice della funzionebcdedit /set path \Windows\system32\winload2.exeSe la tua macchina si avvia usando UEFI, usa l'utilità EfiGuard per applicare una patch a winload.efi.
Comandi rapidi se usi Hyper-V Manager:
FS1:
cd EFI/Boot
Load EfiGuard.Dxe.efi
FS0:
\EFI\Boot\bootx64.efi
Poi instrumenta il driver che preferisci. Per caricare un driver instrumentato su una macchina Windows deve essere firmato, e un certificato di auto-firma è sufficiente per superare le richieste del sistema operativo:
# In elevated powershell terminal
$c = New-SelfSignedCertificate -Type CodeSigningCert -KeyUsage DigitalSignature -Subject 'CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
Set-AuthenticodeSignature .\driver.instrumented.sys -Certificate $c -Force
Se il driver che hai instrumentato è già usato dal sistema, usa questi comandi (da amministratore) per sostituire il file del driver:
set NAME=mydriver.sys
icacls %NAME% /save C:\windows\temp\%NAME%.icacls
takeown /F %NAME%
icacls %NAME% /grant Everyone:F
move %NAME% %NAME%.bak
move instrumented_driver.sys %NAME%
icacls . /restore C:\windows\temp\%NAME%.icacls
Poi esegui:
bcdedit /set recoveryenabled no
bcdedit /set nointegritychecks on
shutdown -t 0 -r
L'integrazione con WinAFL avviene compilando un harness usando gli header forniti.
Insieme agli header c'è example.c, un programma di esempio che mostra come usarli.
Gli header forniti sono una leggera modifica degli header già forniti da WinAFL per l'integrazione con
un altro strumento di Static Binary Instrumentation chiamato Syzygy.
Il modo in cui ci integriamo con kAFL è abbastanza semplice.
Normalmente, un harness kAFL è in esecuzione su una macchina virtuale e parla con il frontend del fuzzer usando speciali "hypercall".
Queste hypercall dicono al fuzzer di fare molte cose, tra cui caricare i dati di copertura da IntelPT e analizzarli come bitmap AFL.
Poiché peafl64 rende obsoleto il tracing IntelPT, dobbiamo preparare un modo per trasmettere i dati di copertura al fuzzer.
Per questo abbiamo esteso qemu e kvm con "hypercall" che permettono all'harness (in user mode) in esecuzione in una VM di inviare i dati di copertura raccolti usando il driver helper.
Questa sezione riguarda specificamente la configurazione su ESXi, ma dovrebbe essere rilevante anche per altre piattaforme di virtualizzazione come AWS.
La configurazione è abbastanza semplice:
install.sh qemu, esegui install.sh qemu_sbiPer fare fuzzing con kAFL e peafl64, dobbiamo preparare una macchina per il fuzzing:
Per il resto, il fuzzing con il nostro fork di kAFL è uguale al normale fuzzing con kAFL.
Panoramica dei passaggi:
Prima di tutto, usiamo IDA per trovare e analizzare le istruzioni e i punti di interesse.
L'analisi crea un file dump.json che contiene tutte queste informazioni in formato json.
instrument.py contiene la logica di instrumentazione, e il flusso stesso è delineato nella funzione process_pe.
Per instrumentare il binario, duplichiamo le sue sezioni eseguibili, dove si troverà tutto il codice instrumentato.
Successivamente, elaboriamo tutte le istruzioni relative e determiniamo come e se dobbiamo gestirle.
Supponiamo di avere un jmp corto dall'indirizzo X all'indirizzo Y. Inseriamo il codice di instrumentazione tra X e Y, e ora il target si trova all'indirizzo Z (Z = Y + len(instrumentation)).
Questo significa che dobbiamo modificare il salto.
Se l'indirizzo Z ora si trova al di fuori del range dei salti corti - dobbiamo trasformare il salto da short a far. (instrument.py:expand_relative_instructions)
Per esempio:
short jmp 0x57 (eb 55) -> near jmp 0x604 (e9 ff 05 00 00)Questo vale anche per istruzioni come call, loop e salti condizionali.
Un altro caso comune che richiede gestione sono le istruzioni relative a rip introdotte nell'assembly x64:
call [rip+0x1000]Aggiorniamo i seguenti elementi per puntare al codice instrumentato: tabella delle rilocazioni, tabella delle esportazioni, configurazione di caricamento, directory TLS e record delle eccezioni.
Gli aggiornamenti vengono eseguiti aggiornando tutti gli indirizzi dalle sezioni originali alle loro controparti instrumentate. (instrument.py:update_addr)
Gli header vengono aggiornati per riflettere le modifiche alla struttura del PE - sezioni aggiunte, entrypoint modificato e dimensione del PE. (instrument.py:update_pe_headers)
Inoltre, peafl64 è in grado di instrumentare il kernel di Windows. Per farlo, analizza e aggiorna la Dynamic Value Relocation Table (DVRT) e la SSDT, e gestisce le specificità di PatchGuard.
La DVRT è una tabella generata dal compilatore che descrive le posizioni degli indirizzi che devono essere modificati al caricamento del PE.
Viene usata per migliorare KASLR e aiuta a mitigare la vulnerabilità Spectre (1, 2, 3).
La DVRT viene analizzata usando un insieme di classi che replicano la struttura della tabella (drt.py; instrument.py:get_updated_dynamic_relocs)
Gestire e aggiornare la SSDT non è stato così semplice.
Il suo indirizzo non è esportato, quindi abbiamo dovuto fare affidamento o sui simboli o su euristiche per individuarla.
La nostra soluzione usa un approccio euristico, usando NtWaitForSingleObject come marcatore costante per tutte le versioni di Windows 10 e trovando l'indirizzo della SSDT in relazione ad esso. (instrument.py:ntoskrnl_update_KiServiceTable)
Questo approccio funziona per tutti i kernel Windows 10, ma dovrà essere adattato per altre versioni del kernel.