/snapchange

Lightweight fuzzing of a memory snapshot using KVM

Primary LanguageRustApache License 2.0Apache-2.0

Snapchange

Lightweight fuzzing of a memory snapshot using KVM

Snapchange provides the ability to load a raw memory dump and register state into a KVM virtual machine (VM) for execution. At a point in execution, this VM can be reset to its initial state by resetting the dirty pages found by KVM or pages manually dirtied by a fuzzer.

Quick Links:

Tutorials

Aspirations

  • Replay a physical memory and register state snapshot using KVM
  • Parallel execution across multiple cores
  • Provide a set of introspection features to the guest VM
  • Real-time coverage state via breakpoint coverage
  • Real-time performance metrics of fuzzer components
  • Provide fuzzing utilities such as single-step debug tracing, testcase minimization, and testcase coverage
  • Input abstraction to allow custom mutation and generation strategies

Example:

Create a target fuzzer from the fuzzer template

$ cp -r -L fuzzer_template your_new_fuzzer

Modify your_new_fuzzer/create_snapshot.sh to take a snapshot of your target

Update src/fuzzer.rs to inject mutated data into the guest VM

#[derive(Default)]
pub struct TemplateFuzzer;

impl Fuzzer for TemplateFuzzer {
    // The type of Input being fuzzed. Used to know how to generate and mutate useful inputs.
    type Input = Vec<u8>;
    // The starting address of the snapshot
    const START_ADDRESS: u64 = 0x402363;
    // The maximum length of mutated input to generate
    const MAX_INPUT_LENGTH: usize = 100;

    fn set_input(&mut self, input: &Self::Input, fuzzvm: &mut FuzzVm<Self>) -> Result<()> {
        // Write the mutated input into the data buffer in the guest VM
        fuzzvm.write_bytes_dirty(VirtAddr(0x402004), CR3, &input)?;
        Ok(())
    }

    fn reset_breakpoints(&self) -> Option<&[BreakpointLookup]> {
        Some(&[
            // Reset when the VM hits example1!main+0x123
            BreakpointLookup::SymbolOffset("example1!main", 0x123)
        ])
    }
}

Start fuzzing with 16 cores

$ cargo run -r -- fuzz -c 16

Implementation

Quick usage of terms for this README:

  • Hypervisor: The target agnostic code executing the snapshot in KVM
  • Fuzzer: The target specific code used to modify and monitor the guest for a target specific fuzz case

The hypervisor begins by mapping the physical memory file for each core requested. In this way, each core has its own, unique copy of memory. The hypervisor then creates the KVM guest and gives the guest this backing memory. This guest's register state is then initialized with the given register state and execution of the guest is launched. The hypervisor waits until the guest exits. Each exit is handled by the hyperisor and some are passed to the fuzzer for target specific mutation, modification, or introspection. If the handler of the exit signifies that the guest should be reset, the hyperisor exits the run loop and resets the guest back to the original snapshot state and restarts the run loop again.

Coverage of the guest is generated by using coverage breakpoints. A separate file with a list of addresses to breakpoint can be given to the hypervisor. If any of these addresses are hit, the address will be added to the coverage database and the instruction for that address will be restored. In this way, the breakpoint will not be triggered again.

Project directory

Snapchange leverages target specific project directories for configuration. This directory is where input and output files and directories are placed. The following file extensions/directories are used as inputs:

  • .physmem - The file containing the raw, physical memory file
  • Register file (one of the following)
    • .regs - JSON register file containing the register state
    • .qemuregs - Output from info registers from qemu

The full list of files and their uses in the project directory can be found here

Debugging Trace

A full example of the debugging single-step trace can be found here.

ITERATION 604 0x00007ffff7ecb0d5 0x11115000 | libc-2.31.so!__GI___getpid+0x5 (0x7ffff7ecb0d5)              
    syscall 
    [0f, 05]
ITERATION 605 0xffffffff83a00000 0x11115000 | entry_SYSCALL_64+0x0 (0xffffffff83a00000)                    
    swapgs 
    [0f, 01, f8]
ITERATION 606 0xffffffff83a00003 0x11115000 | entry_SYSCALL_64+0x3 (0xffffffff83a00003)                    
    mov qword ptr gs:[0xa014], rsp 
    [None:0x0+0xa014=0xa014]] 
    RSP:0x7fffffffeb78 -> �example1!main+0x19 (0x55555555514e)�-> 0xff8458b48f44589
    [65, 48, 89, 24, 25, 14, a0, 00, 00]
ITERATION 607 0xffffffff83a0000c 0x11115000 | entry_SYSCALL_64+0xc (0xffffffff83a0000c)                    
    nop 
    [66, 90]
ITERATION 608 0xffffffff83a0000e 0x11115000 | entry_SYSCALL_64+0xe (0xffffffff83a0000e)                    
    mov rsp, cr3 
    RSP:0x7fffffffeb78 -> example1!main+0x19 (0x55555555514e) -> 0xff8458b48f44589
    CR3:0x11115000
    [0f, 20, dc]

Snapshots

Information about obtaining a snapshot via VirtualBox or QEMU are below:

The examples include a make_example.sh (like example 1) script which goes a full snapshot from scratch. These examples can be used as a template for other targets for reproducible snapshots.

Documentation and clippy

make all
cargo doc --open

Where to begin reading?

The HACKING provides a few higher level locations in the code base to start understanding the system.

Security

See CONTRIBUTING for more information.

License

This project is licensed under the Apache-2.0 License.