From de4481e70d6c2a5acf6826f55b63fb1281d4f524 Mon Sep 17 00:00:00 2001 From: Alwin Berger Date: Mon, 10 Jan 2022 00:22:57 +0100 Subject: [PATCH] add a basic demo --- fuzzers/wcet_qemu_sys/Cargo.toml | 20 ++ fuzzers/wcet_qemu_sys/README.md | 10 + fuzzers/wcet_qemu_sys/src/main.rs | 33 +++ fuzzers/wcet_qemu_sys/src/showmap.rs | 316 +++++++++++++++++++++++++++ fuzzers/wcet_qemu_sys/src/worst.rs | 218 ++++++++++++++++++ fuzzers/wcet_qemu_sys/starter.sh | 3 + 6 files changed, 600 insertions(+) create mode 100644 fuzzers/wcet_qemu_sys/Cargo.toml create mode 100644 fuzzers/wcet_qemu_sys/README.md create mode 100644 fuzzers/wcet_qemu_sys/src/main.rs create mode 100644 fuzzers/wcet_qemu_sys/src/showmap.rs create mode 100644 fuzzers/wcet_qemu_sys/src/worst.rs create mode 100755 fuzzers/wcet_qemu_sys/starter.sh diff --git a/fuzzers/wcet_qemu_sys/Cargo.toml b/fuzzers/wcet_qemu_sys/Cargo.toml new file mode 100644 index 0000000000..868c2bc2b6 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wcet_qemu_sys" +version = "0.1.2" +authors = [ "Alwin Berger " ] +edition = "2021" + +[features] +default = ["std"] +std = [] + +[profile.release] +debug = true + +[dependencies] +libafl = { path = "../../libafl/" } +libafl_qemu = { path = "../../libafl_qemu/", features = ["systemmode", "arm"] } +clap = { version = "3.0.0-beta.2", features = ["default"] } +serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib +hashbrown = { version = "0.11", features = ["serde", "ahash-compile-time-rng"], default-features=false } # A faster hashmap, nostd compatible +nix = "0.23.0" diff --git a/fuzzers/wcet_qemu_sys/README.md b/fuzzers/wcet_qemu_sys/README.md new file mode 100644 index 0000000000..d917ff87cf --- /dev/null +++ b/fuzzers/wcet_qemu_sys/README.md @@ -0,0 +1,10 @@ +# Systemmode Fuzzing + +This folder contains an example fuzzer tailored for system-images. +For the moment it is mostly hardcoded. + +## Build + +To build this example, run `cargo build --release` or `cargo build --release`. +Also build the FreeRTOS demo named CORTEX_M3_MPS2_QEMU_GCC. +Then run `./starter.sh RTOSDemo.axf` and observe. diff --git a/fuzzers/wcet_qemu_sys/src/main.rs b/fuzzers/wcet_qemu_sys/src/main.rs new file mode 100644 index 0000000000..5d7a3b5017 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/main.rs @@ -0,0 +1,33 @@ +#[cfg(target_os = "linux")] +// pub mod fuzzer; +pub mod showmap; +pub mod worst; +use libafl_qemu::{ + edges, + edges::QemuEdgeCoverageHelper, + emu, filter_qemu_args, +}; + +fn main() { + // let mut args2: Vec = vec![ + // "qemu-system-arm", + // "-machine","mps2-an385", + // "-monitor", "null", + // "-semihosting", + // "--semihosting-config", "enable=on,target=native", + // "-kernel", "RTOSDemo.axf", + // "-serial", "stdio", "-nographic", + // "-snapshot", "-drive", "if=none,format=qcow2,file=dummy.qcow2", + // "-S" + // ].iter().map(|x| x.to_string()).collect(); + // init(&mut args2, &Vec::new()); + // let succ = emu::snapshot_save("Start"); + // println!("Snaphsot: {}",succ); + // emu::set_breakpoint(0x00004f5c); + // emu::run(); + // let succ = emu::snapshot_load("Start"); + // emu::run(); + #[cfg(target_os = "linux")] + // fuzzer::main() + showmap::main() +} diff --git a/fuzzers/wcet_qemu_sys/src/showmap.rs b/fuzzers/wcet_qemu_sys/src/showmap.rs new file mode 100644 index 0000000000..74e7e3d9bf --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/showmap.rs @@ -0,0 +1,316 @@ +//! A singlethreaded QEMU fuzzer that can auto-restart. + +use libafl::corpus::Corpus; +use libafl::state::HasCorpus; +use libafl::Fuzzer; +use libafl::bolts::tuples::Merge; +use libafl::mutators::tokens_mutations; +use libafl::mutators::havoc_mutations; +use libafl::mutators::StdScheduledMutator; +use libafl::stages::StdMutationalStage; +use libafl_qemu::QemuExecutor; +use libafl::bolts::tuples::Named; +use libafl::observers::ObserversTuple; +use libafl::events::EventFirer; +use libafl::state::HasClientPerfMonitor; +use libafl::feedbacks::Feedback; +use libafl::Evaluator; +use libafl::inputs::Input; +use libafl::corpus::InMemoryCorpus; +use libafl::events::SimpleEventManager; +use libafl::stats::SimpleStats; +use crate::worst::HitcountsMapObserver; +use crate::worst::HitFeedback; +use clap::{App, Arg}; +use std::{ + env, + fs::{self}, + path::PathBuf, +}; + +use libafl::{ + bolts::{ + current_nanos, + rands::StdRand, + tuples::{tuple_list}, + }, + corpus::{QueueCorpusScheduler}, + executors::{ExitKind}, + fuzzer::{StdFuzzer}, + inputs::{BytesInput, HasTargetBytes}, + observers::{VariableMapObserver}, + state::{StdState}, + Error, +}; +use libafl_qemu::{ + edges, + edges::QemuEdgeCoverageHelper, + emu::Emulator, filter_qemu_args, + snapshot_sys, + snapshot_sys::QemuSysSnapshotHelper, + elf::EasyElf, +}; + + +/// The fuzzer main +pub fn main() { + // Registry the metadata types used in this fuzzer + // Needed only on no_std + //RegistryBuilder::register::(); + + let mut args: Vec = env::args().collect(); + + let res = match App::new("libafl_qemu_fuzzbench") + .version("0.4.0") + .author("AFLplusplus team") + .about("LibAFL-based fuzzer with QEMU for Fuzzbench") + .arg( + Arg::new("out") + .long("libafl-out") + .required(false) + .takes_value(true), + ) + .arg( + Arg::new("in") + .long("libafl-in") + .required(true) + .takes_value(true), + ) + .arg( + Arg::new("tokens") + .long("libafl-tokens") + .takes_value(true), + ) + .arg( + Arg::new("logfile") + .long("libafl-logfile") + .default_value("libafl.log"), + ) + .arg( + Arg::new("timeout") + .long("libafl-timeout") + .default_value("1000"), + ) + .try_get_matches_from(filter_qemu_args()) + { + Ok(res) => res, + Err(err) => { + println!( + "Syntax: {}, --libafl-in \n{:?}", + env::current_exe() + .unwrap_or_else(|_| "fuzzer".into()) + .to_string_lossy(), + err.info, + ); + return; + } + }; + + println!( + "Workdir: {:?}", + env::current_dir().unwrap().to_string_lossy().to_string() + ); + + // For fuzzbench, crashes and finds are inside the same `corpus` directory, in the "queue" and "crashes" subdir. + let mut out_dir = PathBuf::from(res.value_of("out").unwrap().to_string()); + if fs::create_dir(&out_dir).is_err() { + println!("Out dir at {:?} already exists.", &out_dir); + if !out_dir.is_dir() { + println!("Out dir at {:?} is not a valid directory!", &out_dir); + return; + } + } + let mut worstcases = out_dir.clone(); + worstcases.push("worstcase"); + out_dir.push("queue"); + + let in_dir = PathBuf::from(res.value_of("in").unwrap().to_string()); + if !in_dir.is_dir() { + println!("In dir at {:?} is not a valid directory!", &in_dir); + return; + } + + fuzz(in_dir) + .expect("An error occurred while fuzzing"); +} + +/// The actual fuzzer +fn fuzz( + seed_dir: PathBuf, +) -> Result<(), Error> { + //=========== Setup emulator + let mut env: Vec<(String, String)> = env::vars().collect(); + let mut args2: Vec = vec![ + "qemu-system-arm", + "-machine","mps2-an385", + "-monitor", "null", + "-semihosting", + "--semihosting-config", "enable=on,target=native", + "-kernel", "RTOSDemo.axf", + "-serial", "stdio", "-nographic", + "-snapshot", "-drive", "if=none,format=qcow2,file=dummy.qcow2", + "-S" + ].iter().map(|x| x.to_string()).collect(); + let emu = Emulator::new(&mut args2, &mut env); + //=========== Analyze the binary to find the target function address + // let mut elf_buffer = Vec::new(); + // let elf = EasyElf::from_file(emu::binary_path(), &mut elf_buffer)?; + + // let test_one_input_ptr = elf + // .resolve_symbol("LLVMFuzzerTestOneInput", 0) + // .expect("Symbol LLVMFuzzerTestOneInput not found"); + // println!("LLVMFuzzerTestOneInput @ {:#x}", test_one_input_ptr); + + + + //====== Create the input field + let input_addr = 0x00006de4+0xc; + println!("Placing input at {:#x}", input_addr); + emu.set_breakpoint(0x00004f5c); + + //====== Create the most simple status display and managers. + + let stats = SimpleStats::new(|s| println!("{}", s)); + + let mut mgr = SimpleEventManager::new(stats); + + + //========== EDGES_MAP is static field which somehow(?) gets handed over to the Qemu instrumentation + //========== Otherwise setup generic observers + // Create an observation channel using the coverage map + let edges = unsafe { &mut edges::EDGES_MAP }; + let edges_counter = unsafe { &mut edges::MAX_EDGES_NUM }; + let edges_observer = + HitcountsMapObserver::new(VariableMapObserver::new("edges", edges, edges_counter)); + + //========= Feedback-Function evaluate the Maps. Need to dump it for debugging and check if it reaches targets. + let feedback = DumpMapFeedback::new(); + + // A feedback to choose if an input is a solution or not + let objective = HitFeedback::new(); + + // create a State from scratch + let mut state = StdState::new( + // RNG + StdRand::with_seed(current_nanos()), + // Corpus that will be evolved, we keep it in memory for performance + InMemoryCorpus::new(), + // Corpus in which we store solutions (crashes in this example), + // on disk so the user can get them after stopping the fuzzer + InMemoryCorpus::new(), + // States of the feedbacks. + // They are the data related to the feedbacks that you want to persist in the State. + (), + ); + + // A minimization+queue policy to get testcasess from the corpus + let scheduler = QueueCorpusScheduler::new(); + + // A fuzzer with feedbacks and a corpus scheduler + let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + + //======== The harness has to start the execution of the emulator + // The wrapped harness function, calling out to the LLVM-style harness + let mut harness = |input: &BytesInput| { + let target = input.target_bytes(); + let mut buf = target.as_slice(); + let mut len = buf.len(); + if len > 4096 { + buf = &buf[0..4096]; + len = 4096; + } + + unsafe { + emu.write_mem(0x00006de4+0xc,buf); + // println!("{:#?}",edges_copy); + + emu.run(); + // println!("{:#?}",edges_copy); + } + + ExitKind::Ok + }; + + //======= Construct the executor, including the Helpers. The edges_observer still contains the ref to EDGES_MAP + let mut executor = QemuExecutor::new( + &mut harness, + &emu, + tuple_list!( + QemuEdgeCoverageHelper::new(), + // QemuCmpLogHelper::new(), + // QemuAsanHelper::new(), + QemuSysSnapshotHelper::new() + ), + tuple_list!(edges_observer), + &mut fuzzer, + &mut state, + &mut mgr, + )?; + let firstinput = match seed_dir.clone().is_dir() { + true => seed_dir.clone().read_dir().expect("Directory not a directory?").next().expect("Directory empty?").expect("File not in directory?").path(), + false => seed_dir.clone() + }; + fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, Input::from_file(&firstinput).expect("Could not load file")).expect("Evaluation failed"); + // fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, Input::from_file(&firstinput).expect("Could not load file")).expect("Evaluation failed"); + // let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations())); + // let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + // if state.corpus().count() < 1 { + // state + // .load_initial_inputs(&mut fuzzer, &mut executor, &mut mgr, &[seed_dir.clone()]) + // .expect("Failed to load initial corpus"); + // println!("We imported {} inputs from disk.", state.corpus().count()); + // } + // fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr) + // .expect("Error in the fuzzing loop"); + return Ok(()); +} + +//=========================== Debugging Feedback +/// A [`Feedback`] meant to dump the edgemap for debugging. +#[derive(Debug)] +pub struct DumpMapFeedback {} + +impl Feedback for DumpMapFeedback +where + I: Input, + S: HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &I, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + let observer = _observers.match_name::>>("edges") + .expect("HitcountsMapObserver not found"); + println!("{:#?}",observer.edgemap); + Ok(true) + } +} + +impl Named for DumpMapFeedback { + #[inline] + fn name(&self) -> &str { + "HitFeedback" + } +} + +impl DumpMapFeedback { + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new() -> Self { + Self {} + } +} + +impl Default for DumpMapFeedback { + fn default() -> Self { + Self::new() + } +} \ No newline at end of file diff --git a/fuzzers/wcet_qemu_sys/src/worst.rs b/fuzzers/wcet_qemu_sys/src/worst.rs new file mode 100644 index 0000000000..9b207b99ad --- /dev/null +++ b/fuzzers/wcet_qemu_sys/src/worst.rs @@ -0,0 +1,218 @@ +use libafl::observers::VariableMapObserver; +use hashbrown::{HashMap}; +use libafl::observers::ObserversTuple; +use libafl::executors::ExitKind; +use libafl::events::EventFirer; +use libafl::state::HasClientPerfMonitor; +use libafl::inputs::Input; +use libafl::feedbacks::Feedback; +use libafl::state::HasMetadata; +use libafl_qemu::edges::QemuEdgesMapMetadata; +use libafl::observers::MapObserver; +use serde::{Deserialize, Serialize}; + +use libafl::{ + bolts::{ + tuples::Named, + HasLen, + }, + observers::Observer, + Error, +}; + +/// Map observer with hitcounts postprocessing +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(bound = "M: serde::de::DeserializeOwned")] +pub struct HitcountsMapObserver +where + M: serde::Serialize + serde::de::DeserializeOwned, +{ + base: M, + pub edgemap: HashMap<(u64,u64),u8>, +} + +static COUNT_CLASS_LOOKUP: [u8; 256] = [ + 0, 1, 2, 4, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, +]; + +impl Observer for HitcountsMapObserver +where + M: MapObserver + Observer, + S: HasMetadata, // Need to grab the HashMap from a Helper +{ + #[inline] + fn pre_exec(&mut self, state: &mut S, input: &I) -> Result<(), Error> { + self.edgemap = HashMap::new(); + self.base.pre_exec(state, input) + } + + #[inline] + fn post_exec(&mut self, state: &mut S, input: &I) -> Result<(), Error> { + //new stuff + let original_hashmap=&state.metadata().get::().unwrap().map; + for (key, val) in original_hashmap.iter() { + self.edgemap.insert(*key,*self.base.get(*val as usize)); + } + // println!("Post-Exec Len: {} Cap: {}",self.edgemap.len(),self.edgemap.capacity()); + // println!("{:#?}",self.edgemap); + //end new stuff + let cnt = self.usable_count(); + for i in 0..cnt { + *self.get_mut(i) = COUNT_CLASS_LOOKUP[*self.get(i) as usize]; + } + self.base.post_exec(state, input) + } +} + +impl Named for HitcountsMapObserver +where + M: Named + serde::Serialize + serde::de::DeserializeOwned, +{ + #[inline] + fn name(&self) -> &str { + self.base.name() + } +} + +impl HasLen for HitcountsMapObserver +where + M: MapObserver, +{ + #[inline] + fn len(&self) -> usize { + self.base.len() + } +} + +impl MapObserver for HitcountsMapObserver +where + M: MapObserver, +{ + #[inline] + fn map(&self) -> Option<&[u8]> { + self.base.map() + } + + #[inline] + fn map_mut(&mut self) -> Option<&mut [u8]> { + self.base.map_mut() + } + + #[inline] + fn usable_count(&self) -> usize { + self.base.usable_count() + } + + #[inline] + fn initial(&self) -> u8 { + self.base.initial() + } + + #[inline] + fn initial_mut(&mut self) -> &mut u8 { + self.base.initial_mut() + } + + #[inline] + fn set_initial(&mut self, initial: u8) { + self.base.set_initial(initial); + } +} + +impl HitcountsMapObserver +where + M: serde::Serialize + serde::de::DeserializeOwned, +{ + /// Creates a new [`MapObserver`] + pub fn new(base: M) -> Self { + Self { edgemap:HashMap::new(), base } + } +} + +//=================================================================== + + +/// A [`HitFeedback`] reports as interesting when all predicted worst case edges have been matched. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct HitFeedback {} + +impl Feedback for HitFeedback +where + I: Input, + S: HasClientPerfMonitor, +{ + fn is_interesting( + &mut self, + _state: &mut S, + _manager: &mut EM, + _input: &I, + _observers: &OT, + _exit_kind: &ExitKind, + ) -> Result + where + EM: EventFirer, + OT: ObserversTuple, + { + // TODO Replace with match_name_type when stable + // let observer = _observers.match_name::>("edges").expect("SelectedEdgeObserver not found"); + let observer = _observers.match_name::>>("edges") + .expect("HitcountsMapObserver not found"); + let mut hit_target: bool = true; + //check if we've hit any targets. + // let inc : u64 = (0x401180); + // let call1 : u64 = (0x40119f); + // let call2 : u64 = (0x4011b0); + // let call3 : u64 = (0x4011c8); + let to_check : Vec<(u64,u64)> = vec![ + (0x401190,0x4011c8), // start -> call + (0x40119b,0x4011b0), // cmp -> call + (0x4011b5,0x40119f), // cmp -> call + ]; + let expected : Vec = vec![ 1, 1, 1]; + let combo = to_check.iter().zip(expected.iter()); + for (edg, val) in combo { + // println!("Feedback Len: {} Cap: {}",observer.edgemap.len(),observer.edgemap.capacity()); + match observer.edgemap.get(edg) { + Some(x) => hit_target &= val == x, + None =>hit_target = false + } + } + if hit_target { + Ok(true) + } else { + Ok(false) + } + } +} + +impl Named for HitFeedback { + #[inline] + fn name(&self) -> &str { + "HitFeedback" + } +} + +impl HitFeedback { + /// Creates a new [`HitFeedback`] + #[must_use] + pub fn new() -> Self { + Self {} + } +} + +impl Default for HitFeedback { + fn default() -> Self { + Self::new() + } +} diff --git a/fuzzers/wcet_qemu_sys/starter.sh b/fuzzers/wcet_qemu_sys/starter.sh new file mode 100755 index 0000000000..654c6c4c91 --- /dev/null +++ b/fuzzers/wcet_qemu_sys/starter.sh @@ -0,0 +1,3 @@ +mkdir -p target/test_in target/test_out +[ ! -f target/test_in/test ] && echo " !test" > target/test_in/test +LD_LIBRARY_PATH=target/debug target/debug/wcet_qemu_sys $1 --libafl-out target/test_out --libafl-in target/test_in