Zum Inhalt springen

Snapchange - Snapshot-Based Fuzzing mit KVM Cheatsheet

Snapchange - Snapshot-Based Fuzzing mit KVM Cheatsheet

Snapchange (von AWS) ist ein Rust-Framework für Snapshot-basiertes Fuzzing. Traditionelle Fuzzer starten das Ziel für jeden Input erneut, was fatal langsam ist, wenn der interessante Code hinter teurem Setup sitzt — einem Netzwerk-Handshake, einer Anmeldung, einer geparsten Config, einer geladenen Datenbank. Snapchange nimmt stattdessen einen Memory-Snapshot des Ziels genau im Moment von Interesse, stelle dann diesen Zustand wieder her und mutiere von dort mit KVM, um sehr hohe Iterations-Raten gegen Code zu erreichen, den konventionelles Fuzzing überhaupt nicht erreichen kann.

Snapshot-Fuzzing erfordert KVM und Root. Führe in einer isolierten Umgebung aus.

Anforderungen

AnforderungAnmerkung
Linux mit KVM/dev/kvm zugänglich
Rust-ToolchainSnapchange ist eine Rust-Bibliothek
QEMUWird zum Erstellen des Snapshots verwendet
RootFür KVM und Speicherzugriff

Das Modell

PhaseWas passiert
1. SnapshotFühre das Ziel in QEMU zum Interessenspunkt aus, dump Memory + Register
2. HarnessSchreibe einen Rust-Fuzzer, der beschreibt, wo Input geht und wann zu stoppen ist
3. FuzzSnapchange stellt den Snapshot pro Iteration wieder her und mutiert den Input
4. TriageCrashes werden mit dem genauen Register/Memory-Zustand aufgezeichnet

Der kritische Einblick: Alle Setup-Kosten werden einmal bezahlt, im Snapshot. Jede nachfolgende Iteration startet aus diesem Zustand in Mikrosekunden.

Erstellen eines Snapshots

# Konzeptionell: Führe das Ziel unter QEMU aus, break an der Funktion von Interesse,
# dann dump physischen Memory und Register-Zustand
./snapchange/qemu_snapshot/take_snapshot.sh --target ./my-server
ArtefaktEnthält
fuzzvm.physmemVollständiger Guest-Physischer Memory
fuzzvm.qemuregsCPU-Register-Zustand
*.symbolsSymboltabelle für Coverage/Breakpoints
vmlinux / binaryFür Symbolisierung

Schreiben eines Fuzzers

// Konzeptionelle Form — siehe Snapchange-Beispiele für vollständigen Code
impl Fuzzer for MyFuzzer {
    type Input = Vec<u8>;
    const START_ADDRESS: u64 = 0x555555555000;
    const MAX_INPUT_LENGTH: usize = 1024;

    fn set_input(&mut self, input: &Self::Input, fuzzvm: &mut FuzzVm<Self>) -> Result<()> {
        // Schreibe den mutierten Input in den Guest-Speicher, wo das Ziel ihn liest
        fuzzvm.write_bytes_dirty(VirtAddr(BUFFER_ADDR), CR3, input)?;
        Ok(())
    }

    fn reset_breakpoints(&self) -> Option<&[AddressLookup]> { /* stop conditions */ }
    fn crash_breakpoints(&self) -> Option<&[AddressLookup]> { /* crash sites */ }
}
ElementZweck
START_ADDRESSWo die Ausführung jeder Iteration fortgesetzt wird
set_inputInjiziere den Fuzz-Input in den Guest-Speicher
reset_breakpointsWo eine Iteration normalerweise endet
crash_breakpointsAdressen, die einen Crash anzeigen (z. B. panic, abort)

Ausführung

BefehlBeschreibung
cargo run -r -- fuzz -c 8Fuzz mit 8 Cores
cargo run -r -- project translateSymbolisiere/inspiziere den Snapshot
cargo run -r -- coverageGeneriere Coverage aus dem Corpus
cargo run -r -- trace <input>Single-Step-Trace ein Input
cargo run -r -- minimize <input>Schrumpfe einen Crashing-Input

Warum Snapshot-Fuzzing hier gewinnt

Ziel-CharakteristikKonventionelles FuzzingSnapchange
Teurer StartupBezahlt es jede IterationBezahlt es einmal
Erfordert Auth/HandshakeSchwer zu erreichenSnapshot dahinter
Stateful ProtocolSchwierigSnapshot mid-session
Kernel/Hypervisor-CodeSehr schwierigNatürlicher Fit

Coverage & Triage

FähigkeitAnmerkung
Breakpoint-CoverageCoverage via Breakpoints auf Basic-Blöcken
Crash-DedupGruppiert nach Faulting-Adresse/Zustand
Single-Step-TracesVollständiger Anweisungs-Trace für einen gegebenen Input
Memory-InspektionenLese Guest-Speicher zum Crash-Zeitpunkt

Snapchange vs andere Fuzzer

AspektSnapchangeAFL++LibAFL (Nyx)syzkaller
AusführungKVM-Snapshot-WiederherstellungProzess-ForkSnapshot (Nyx)VM + Syscalls
Setup-KostenEinmal bezahltJede IterationEinmalPro VM
ZielAnything in einem VM-SnapshotUserspace-BinärdateienKonfigurierbarOS-Kernel
AufwandHoch (schreibe einen Rust-Fuzzer)NiedrigHochMittel

Vergleiche mit LibAFL (das auch Snapshot-Backends bietet) und syzkaller für Kernel-Syscall-Fuzzing.

Ressourcen