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:
- Cookbook provides examples fuzz, trace, coverage, minimize, and project command line utilities
- Taking a snapshot with QEMU
- Architecture
- Fuzzer Lifecycle
Tutorials
- Tutorial 1 - Basic Usage
- Tutorial 2 -
LibTIFF
with ASAN - Tutorial 3 -
FFmpeg
with custom mutator - Tutorial 4 - Syscall fuzzer
- Tutorial 5 - Redqueen
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
your_new_fuzzer/create_snapshot.sh
to take a snapshot of your target
Modify src/fuzzer.rs
to inject mutated data into the guest VM
Update #[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 frominfo registers
fromqemu
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.