Giter Club home page Giter Club logo

snapchange's Introduction

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.

snapchange's People

Contributors

amazon-auto avatar corydu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

snapchange's Issues

trace: 'attempt to add with overflow'

I always get the following error when trying to use the trace feature:

$ RUST_BACKTRACE=1 cargo +nightly run -- trace snapshot/crashes/SIGSEGV_addr_0xcafecafe_code_AddressNotMappedToObject/c2b9b72428f4059c
...
[2023-06-02T08:59:17Z INFO  snapchange::stack_unwinder] "./snapshot/vmlinux" has no .eh_frame_hdr
[2023-06-02T08:59:17Z INFO  snapchange::cmdline] Gathering unwinders took: 139.963µs
[2023-06-02T08:59:17Z INFO  snapchange::commands::trace] Gathering single step trace of "snapshot/crashes/SIGSEGV_addr_0xcafecafe_code_AddressNotMappedToObject/c2b9b72428f4059c"
[2023-06-02T08:59:17Z INFO  snapchange::commands::trace] Fuzzer: 0x5555555552f8 RIP: 0x5555555552f8
[2023-06-02T08:59:18Z INFO  snapchange::commands::trace] Single step trace
[2023-06-02T08:59:18Z INFO  snapchange::commands::trace] Tracing timeout: 60s
[2023-06-02T08:59:18Z INFO  snapchange::commands::trace] Getting vmlinux
[2023-06-02T08:59:19Z INFO  snapchange::commands::trace] Getting binary contexts
thread 'main' panicked at 'attempt to add with overflow', /root/snapchange/src/addrs.rs:74:18
stack backtrace:
   0: rust_begin_unwind
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/panicking.rs:578:5
   1: core::panicking::panic_fmt
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/core/src/panicking.rs:67:14
   2: core::panicking::panic
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/core/src/panicking.rs:117:5
   3: snapchange::addrs::VirtAddr::offset
             at /root/snapchange/src/addrs.rs:74:18
   4: snapchange::memory::Memory::set_page_boundaries
             at /root/snapchange/src/memory.rs:1042:21
   5: snapchange::memory::Memory::read_bytes
             at /root/snapchange/src/memory.rs:642:9
   6: snapchange::memory::Memory::read_c_string
             at /root/snapchange/src/memory.rs:1166:13
   7: snapchange::fuzzvm::FuzzVm<FUZZER>::read_c_string
             at /root/snapchange/src/fuzzvm.rs:2773:9
   8: snapchange::commands::trace::start_core
             at /root/snapchange/src/commands/trace.rs:306:33
...

I have been able to work around the issue with the following patch but I'm guessing that this more or less just ignores the error instead of addressing the actual issue:

diff --git a/src/addrs.rs b/src/addrs.rs
index 6752033..771f176 100644
--- a/src/addrs.rs
+++ b/src/addrs.rs
@@ -71,7 +71,10 @@ impl VirtAddr {
     #[allow(dead_code)]
     #[must_use]
     pub const fn offset(self, offset: u64) -> VirtAddr {
-        VirtAddr(self.0 + offset)
+        match self.0.checked_add(offset) {
+            None => self,
+            Some(addr) => VirtAddr(addr),
+        }
     }

     /// Get the 4 page table indexes that this [`VirtAddr`] corresponds maps with when

Failed to write MSR

I have managed to get snapchange to run on the 01_getpid example, however only after commenting out this line:

ensure!(msrs_written == write_msr.len(), "Failed to write MSR");

With the assertion in place the output looks like this:

$ RUST_BACKTRACE=1 cargo +nightly run -- fuzz
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/fuzzer_template fuzz`
Using project config: Config {
    stats: Stats {
        merge_coverage_timer: 60s,
        maximum_new_corpus_size: 250,
        minimum_total_corpus_percentage_sync: 10,
        maximum_total_corpus_percentage_sync: 50,
    },
    guest_memory_size: 5368709120,
}
Thread 0 returned err.. Failed to write MSR

Stack backtrace:
   0: anyhow::error::<impl anyhow::Error>::msg
             at /root/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anyhow-1.0.71/src/error.rs:83:36
   1: anyhow::__private::format_err
             at /root/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anyhow-1.0.71/src/lib.rs:669:13
   2: snapchange::fuzzvm::FuzzVm<FUZZER>::restore_guest_msrs
             at /root/snapchange/src/fuzzvm.rs:3568:9
   3: snapchange::fuzzvm::FuzzVm<FUZZER>::init_guest
             at /root/snapchange/src/fuzzvm.rs:3257:21
   4: snapchange::fuzzvm::FuzzVm<FUZZER>::create
             at /root/snapchange/src/fuzzvm.rs:777:9
   5: snapchange::commands::fuzz::start_core
             at /root/snapchange/src/commands/fuzz.rs:669:22
   6: snapchange::commands::fuzz::run::{{closure}}::{{closure}}
             at /root/snapchange/src/commands/fuzz.rs:376:17
   7: std::panicking::try::do_call
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/panicking.rs:485:40
   8: __rust_try
   9: std::panicking::try
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/panicking.rs:449:19
  10: std::panic::catch_unwind
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/panic.rs:142:14
  11: snapchange::commands::fuzz::run::{{closure}}
             at /root/snapchange/src/commands/fuzz.rs:375:26
  12: std::sys_common::backtrace::__rust_begin_short_backtrace
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/sys_common/backtrace.rs:135:18
  13: std::thread::Builder::spawn_unchecked_::{{closure}}::{{closure}}
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/thread/mod.rs:529:17
  14: <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/core/src/panic/unwind_safe.rs:271:9
  15: std::panicking::try::do_call
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/panicking.rs:485:40
  16: __rust_try
  17: std::panicking::try
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/panicking.rs:449:19
  18: std::panic::catch_unwind
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/panic.rs:142:14
  19: std::thread::Builder::spawn_unchecked_::{{closure}}
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/thread/mod.rs:528:30
  20: core::ops::function::FnOnce::call_once{{vtable.shim}}
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/core/src/ops/function.rs:250:5
  21: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/alloc/src/boxed.rs:1985:9
  22: <alloc::boxed::Box<F,A> as core::ops::function::FnOnce<Args>>::call_once
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/alloc/src/boxed.rs:1985:9
  23: std::sys::unix::thread::Thread::new::thread_start
             at /rustc/5ea3f0ae08c07472239a94ce55601e9b63eb1f45/library/std/src/sys/unix/thread.rs:108:17
  24: start_thread
             at /build/glibc-6iIyft/glibc-2.28/nptl/pthread_create.c:486:8
  25: clone
             at /build/glibc-6iIyft/glibc-2.28/misc/../sysdeps/unix/sysv/linux/x86_64/clone.S:95
Stats waiting for threads to terminate
All cores are dead..
Stats breaking from finish..
Writing graph data to disk..
Writing corpus (24) to disk: 0.00 MB
[stats] FINISHED!
Stats thread: Ok(())
Kick thread: Ok(())

Some light debugging revealed that only the last two MSRs are not being written:

snapchange/src/fuzzvm.rs

Lines 3517 to 3526 in a442cd3

kvm_msr_entry {
index: Msr::Ia32GsBase as u32,
data: self.vbcpu.gs.base,
..kvm_bindings::kvm_msr_entry::default()
},
kvm_msr_entry {
index: Msr::Ia32FsBase as u32,
data: self.vbcpu.fs.base,
..kvm_bindings::kvm_msr_entry::default()
},


System info:

Operating System: Debian GNU/Linux 10 (buster)
Kernel: Linux 6.1.0-9-amd64
Architecture: x86-64

Suggestion: Resolve Kernel Compilation Errors on Ubuntu 22.04.2

Hi Team,

I've been using your project and I must say, it's amazing. However, I've noticed a minor issue when running on Ubuntu 22.04.2 that could use some attention.

During my use, I've encountered kernel compilation errors that seem to be tied to the binutils version. Luckily, I've found a workaround: using kernel version 5.19 seems to resolve the problem.

So, I'm thinking it could be useful to implement this fix into the project. If you're open to it, I'm ready to prepare a PR for this change.

Here's a brief overview of what the fix would look like:

-  # Use kernel v5.4
-  ./init.sh --kernel-version v5.4
-
+  if [ "$( cat /etc/os-release | grep ^VERSION= | cut -d'"' -f2)" = "22.04.2 LTS (Jammy Jellyfish)" ]; 
+  then 
+    # Use kernel v5.19 
+    ./init.sh --kernel-version v5.19
+  else 
+    # Use kernel v5.4
+    ./init.sh --kernel-version v5.4
+  fi

Let me know what you think about small fixes like that, and if there are any further steps I should take.

Coverage without .bin file

👋 I'm working with snapchange and finding it super easy to use and think it's really great. I had a question regarding coverage.

I can generate coverage for binaries that I have, including shared libraries. I just place the binary/library in the snapshot directory with a .bin extension, along with the coverage file. This seems to work well because I can specify the base address from the vmmap for loaded libraries.

However, what if I have something that is custom packed. So there is a loader that does mmap and loads code into the memory region, and then executes code from there. I can dump the executable memory regions to disk, and can even generate code coverage for those memory regions. However I can not just load the memory dump into the snapshot directory with a .bin extension because it gives me an error.

     Finished release [optimized] target(s) in 0.09s
     Running `target/release/fuzzer_template fuzz --cores 12 --timeout 60s`
thread 'main' panicked at src/main.rs:6:49:
called `Result::unwrap()` on an `Err` value: Object error: Error("Unknown file magic")

Stack backtrace:
   0: anyhow::error::<impl core::convert::From<E> for anyhow::Error>::from
   1: snapchange::cmdline::get_project_state
   2: snapchange::snapchange_main
   3: fuzzer_template::main
   4: std::sys_common::backtrace::__rust_begin_short_backtrace
   5: std::rt::lang_start::{{closure}}
   6: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/core/src/ops/function.rs:284:13
   7: std::panicking::try::do_call
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/panicking.rs:524:40
   8: std::panicking::try
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/panicking.rs:488:19
   9: std::panic::catch_unwind
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/panic.rs:142:14
  10: std::rt::lang_start_internal::{{closure}}
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/rt.rs:148:48
  11: std::panicking::try::do_call
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/panicking.rs:524:40
  12: std::panicking::try
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/panicking.rs:488:19
  13: std::panic::catch_unwind
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/panic.rs:142:14
  14: std::rt::lang_start_internal
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/rt.rs:148:20
  15: main
  16: __libc_start_main
             at /build/glibc-SzIz7B/glibc-2.31/csu/../csu/libc-start.c:308:16
  17: _start
stack backtrace:
   0: rust_begin_unwind
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/std/src/panicking.rs:617:5
   1: core::panicking::panic_fmt
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/core/src/panicking.rs:67:14
   2: core::result::unwrap_failed
             at /rustc/d12c6e947ceacf3b22c154caf9532b390d8dc88a/library/core/src/result.rs:1652:5
   3: fuzzer_template::main
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

This honestly seems pretty reasonable to me. But I'd like to be able to give the fuzzer information about coverage in these regions and don't want to have to try to create an ELF file just to do this.

Do you have any ideas how I can approach this use case? Any suggestions are greatly appreciated.

Thanks again for releasing this tool.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.