
Tool zur statischen Binärinstrumentierung für Windows-x64-Executables
peafl64 ist ein statisches Instrumentierungswerkzeug für x64-PEs in Windows.
Statische Instrumentierung ist die Praxis, ausführbare Dateien zu bearbeiten und an bestimmten Stellen Code hinzuzufügen.
Die Instrumentierung fügt am Anfang jedes Grundblocks in der Binärdatei Code hinzu und protokolliert den Ausführungsfluss auf eine AFL-kompatible Weise.
Sie ermöglicht es uns, Binärdateien im Usermode (mit WinAFL) und im Kernelmode (mit kAFL) zu fuzzen, ohne Zugriff auf deren Quellcode zu haben.
Es gibt andere Möglichkeiten, Windows-Binärdateien zu fuzzen; in diesem Projekt haben wir uns auf statische Instrumentierung konzentriert, weil sie die schnellste Methode ist.
Dieses Projekt baut auf dem pe-afl-Werkzeug von wmliang auf und erweitert es um x64-Unterstützung.
Das Instrumentierungsskript benötigt eine IDA-Analyseausgabe.
Um sie zu erstellen, führen Sie das mitgelieferte Skript ida_dumper.py in IDA aus.
Das Skript erfordert IDA 7+ und Python 3.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
Instrumentieren einer Usermode-Binärdatei mit NOPs
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
Instrumentieren einer Kernelmode-Binärdatei mit Prozess-ID-Filterung
python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json"
Instrumentieren einer Kernelmode-Binärdatei mit Thread-ID-Filterung und ausführlicher Ausgabe
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"
Zuerst müssen Sie prüfen, ob Ihr Rechner per BIOS oder UEFI bootet.
Bei Hyper-V-Maschinen: Gen-1-Maschinen basieren auf BIOS, Gen-2-Maschinen auf UEFI.
Wenn Ihre Maschine auf BIOS basiert, müssen Sie winload.exe patchen:
ImgpValidateImageHash darinmov eax, edi durch xor eax, eaxbcdedit /set path \Windows\system32\winload2.exe ausWenn Ihre Maschine per UEFI bootet, verwenden Sie das Dienstprogramm EfiGuard, um winload.efi zu patchen.
Befehls-Spickzettel bei Verwendung des Hyper-V-Managers:
FS1:
cd EFI/Boot
Load EfiGuard.Dxe.efi
FS0:
\EFI\Boot\bootx64.efi
Instrumentieren Sie dann den Treiber Ihrer Wahl. Damit ein instrumentierter Treiber auf einem Windows-Rechner geladen werden kann, muss er signiert sein; ein selbstsigniertes Zertifikat reicht aus, um die Anforderungen des Betriebssystems zu erfüllen:
# 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
Wenn der instrumentierte Treiber bereits vom System verwendet wird, verwenden Sie diese Befehle (als Administrator), um die Treiberdatei zu ersetzen:
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
Führen Sie dann aus:
bcdedit /set recoveryenabled no
bcdedit /set nointegritychecks on
shutdown -t 0 -r
Die Integration mit WinAFL erfolgt durch Kompilieren eines Harness mit den mitgelieferten Headern.
Neben den Headern gibt es example.c, ein Beispielprogramm, das deren Verwendung zeigt.
Die mitgelieferten Header sind eine leichte Abwandlung der bereits von WinAFL bereitgestellten Header, um die Integration mit einem anderen statischen Binärinstrumentierungswerkzeug namens Syzygy zu ermöglichen.
Die Art und Weise, wie wir mit kAFL integrieren, ist ziemlich einfach.
Normalerweise läuft ein kAFL-Harness auf einer virtuellen Maschine und kommuniziert über spezielle „Hypercalls“ mit dem Frontend des Fuzzers.
Diese Hypercalls weisen den Fuzzer an, viele Dinge zu tun, unter anderem Coverage-Daten von IntelPT zu laden und als AFL-Bitmap zu parsen.
Da peafl64 das IntelPT-Tracing überflüssig macht, müssen wir eine Möglichkeit vorbereiten, die Coverage-Daten an den Fuzzer zu übertragen.
Daher haben wir qemu und kvm um „Hypercalls“ erweitert, die es dem (Usermode-)Harness, der in einer VM läuft, ermöglichen, die mit dem Hilfstreiber gesammelten Coverage-Daten zu senden.
Dies betrifft speziell die Einrichtung auf ESXi, sollte aber auch für andere Virtualisierungsplattformen wie AWS relevant sein.
Die Einrichtung ist ziemlich einfach:
install.sh qemu den Befehl install.sh qemu_sbi ausUm mit kAFL und peafl64 zu fuzzen, müssen wir eine Fuzzing-Maschine einrichten:
Abgesehen davon ist das Fuzzing mit unserem kAFL-Fork dasselbe wie normales Fuzzing mit kAFL.
Schritte im Überblick:
Zuerst verwenden wir IDA, um die relevanten Anweisungen und Stellen zu finden und zu analysieren.
Die Analyse erstellt eine Datei dump.json, die all diese Informationen im JSON-Format enthält.
instrument.py enthält die Instrumentierungslogik, und der Ablauf selbst ist in der Funktion process_pe beschrieben.
Um die Binärdatei zu instrumentieren, duplizieren wir ihre ausführbaren Sektionen, in denen sich der gesamte instrumentierte Code befinden wird.
Als Nächstes verarbeiten wir alle relativen Anweisungen und bestimmen, wie und ob wir sie behandeln müssen.
Angenommen, wir haben einen kurzen Sprung (short jmp) von Adresse X zu Adresse Y. Wir fügen zwischen X und Y Instrumentierungscode ein, und nun befindet sich das Ziel an Adresse Z (Z = Y + len(instrumentation)).
Das bedeutet, dass wir den Sprung modifizieren müssen.
Wenn Adresse Z nun außerhalb des Bereichs kurzer Sprünge liegt, müssen wir den Sprung von short in far umwandeln. (instrument.py:expand_relative_instructions)
Zum Beispiel:
short jmp 0x57 (eb 55) -> near jmp 0x604 (e9 ff 05 00 00)Das gilt auch für Anweisungen wie call, loop und bedingte Sprünge.
Ein weiterer häufiger Fall, der behandelt werden muss, sind RIP-relative Anweisungen, die in x64-Assembly eingeführt wurden:
call [rip+0x1000]Wir aktualisieren die folgenden Dinge, sodass sie auf den instrumentierten Code verweisen: Relokationstabelle, Exporttabelle, Load-Konfiguration, das TLS-Verzeichnis und Ausnahmedatensätze.
Die Aktualisierungen erfolgen, indem alle Adressen von den ursprünglichen Sektionen auf ihre instrumentierten Gegenstücke umgestellt werden. (instrument.py:update_addr)
Die Header werden aktualisiert, um die Änderungen an der PE-Struktur widerzuspiegeln – hinzugefügte Sektionen, geänderter Einstiegspunkt und PE-Größe. (instrument.py:update_pe_headers)
Darüber hinaus kann peafl64 den Windows-Kernel instrumentieren. Dazu analysiert und aktualisiert es die Dynamic Value Relocation Table (DVRT) und die SSDT und behandelt PatchGuard-Besonderheiten.
Die DVRT ist eine vom Compiler erzeugte Tabelle, die die Positionen von Adressen beschreibt, die beim Laden des PE geändert werden müssen.
Sie wird verwendet, um KASLR zu verbessern, und trägt zur Abschwächung der Spectre-Schwachstelle bei (1, 2, 3).
Die DVRT wird mit einer Reihe von Klassen geparst, die die Struktur der Tabelle nachbilden (drt.py; instrument.py:get_updated_dynamic_relocs)
Das Behandeln und Aktualisieren der SSDT war nicht ganz so einfach.
Ihre Adresse wird nicht exportiert, daher mussten wir uns entweder auf Symbole oder auf Heuristiken verlassen, um sie zu finden.
Unsere Lösung verwendet einen heuristischen Ansatz, der NtWaitForSingleObject als konstanten Marker für alle Windows-10-Versionen nutzt und die Adresse der SSDT relativ dazu findet. (instrument.py:ntoskrnl_update_KiServiceTable)
Dieser Ansatz funktioniert für alle Windows-10-Kernel, muss aber für andere Kernelversionen angepasst werden.