move cli parsing, use multibyteinput

This commit is contained in:
Alwin Berger 2024-06-28 14:38:25 +02:00
parent acf9b04e70
commit 77799f77a9
11 changed files with 265 additions and 259 deletions

View File

@ -53,7 +53,7 @@ codegen-units = 1
debug = true
[dependencies]
libafl = { path = "../../libafl/" }
libafl = { path = "../../libafl/", features = ["multipart_inputs"] }
libafl_bolts = { path = "../../libafl_bolts/" }
libafl_qemu = { path = "../../libafl_qemu/", features = ["arm", "systemmode"] }
serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib

88
fuzzers/FRET/src/cli.rs Normal file
View File

@ -0,0 +1,88 @@
use clap::{Parser, Subcommand};
use std::path::PathBuf;
// Argument parsing ================================================================================
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Kernel Image
#[arg(short, long, value_name = "FILE")]
pub kernel: PathBuf,
/// Sets a custom config file
#[arg(short, long, value_name = "FILE")]
pub config: PathBuf,
/// Sets the prefix of dumed files
#[arg(short='n', long, value_name = "FILENAME")]
pub dump_name: Option<PathBuf>,
/// do time dumps
#[arg(short='t', long)]
pub dump_times: bool,
/// do worst-case dumps
#[arg(short='a', long)]
pub dump_cases: bool,
/// do trace dumps (if supported)
#[arg(short='r', long)]
pub dump_traces: bool,
/// do graph dumps (if supported)
#[arg(short='g', long)]
pub dump_graph: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand,Clone)]
pub enum Commands {
/// run a single input
Showmap {
/// take this input
#[arg(short, long)]
input: PathBuf,
},
/// start fuzzing campaign
Fuzz {
/// disable heuristic
#[arg(short, long)]
random: bool,
/// seed for randomness
#[arg(short, long)]
seed: Option<u64>,
/// runtime in seconds
#[arg(short, long)]
time: Option<u64>,
}
}
pub fn set_env_from_config(kernel : &PathBuf, path : &PathBuf) {
let is_csv = path.as_path().extension().map_or(false, |x| x=="csv");
if !is_csv {
let lines = std::fs::read_to_string(path).expect("Config file not found");
let lines = lines.lines().filter(
|x| x.len()>0
);
for l in lines {
let pair = l.split_once('=').expect("Non VAR=VAL line in config");
std::env::set_var(pair.0, pair.1);
}
} else {
let mut reader = csv::Reader::from_path(path).expect("CSV read from config failed");
let p = kernel.as_path();
let stem = p.file_stem().expect("Kernel filename error").to_str().unwrap();
for r in reader.records() {
let rec = r.expect("CSV entry error");
if stem == &rec[0] {
std::env::set_var("FUZZ_MAIN", &rec[1]);
std::env::set_var("FUZZ_INPUT", &rec[2]);
std::env::set_var("FUZZ_INPUT_LEN", &rec[3]);
std::env::set_var("BREAKPOINT", &rec[4]);
break;
}
}
}
}

View File

@ -7,17 +7,16 @@ use libafl_bolts::{
core_affinity::Cores, current_nanos, rands::StdRand, shmem::{ShMemProvider, StdShMemProvider}, tuples::tuple_list, AsSlice
};
use libafl::{
common::{HasMetadata, HasNamedMetadata}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, events::{launcher::Launcher, EventConfig}, executors::ExitKind, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{BytesInput, HasTargetBytes}, monitors::MultiMonitor, observers::{CanTrack, VariableMapObserver}, prelude::{havoc_mutations, minimizer::TopRatedsMetadata, CorpusId, Generator, HitcountsMapObserver, RandBytesGenerator, SimpleEventManager, SimpleMonitor, SimpleRestartingEventManager, StdScheduledMutator}, schedulers::QueueScheduler, stages::StdMutationalStage, state::{HasCorpus, StdState}, Error, Evaluator
common::{HasMetadata, HasNamedMetadata}, corpus::{Corpus, InMemoryCorpus, OnDiskCorpus}, events::{launcher::Launcher, EventConfig}, executors::ExitKind, feedback_or, feedback_or_fast, feedbacks::{CrashFeedback, MaxMapFeedback, TimeoutFeedback}, fuzzer::{Fuzzer, StdFuzzer}, inputs::{multi::MultipartInput, BytesInput, HasTargetBytes, Input}, monitors::MultiMonitor, observers::{CanTrack, VariableMapObserver}, prelude::{havoc_mutations, minimizer::TopRatedsMetadata, CorpusId, Generator, HitcountsMapObserver, RandBytesGenerator, SimpleEventManager, SimpleMonitor, SimpleRestartingEventManager, StdScheduledMutator}, schedulers::QueueScheduler, stages::StdMutationalStage, state::{HasCorpus, StdState}, Error, Evaluator
};
use libafl_qemu::{
edges::{self, edges_map_mut_ptr, QemuEdgeCoverageHelper, MAX_EDGES_FOUND}, elf::EasyElf, emu::Emulator, GuestAddr, GuestPhysAddr, QemuExecutor, QemuFilterList, QemuHooks, Regs, StdInstrumentationFilter
edges::{self, edges_map_mut_ptr, QemuEdgeCoverageHelper, MAX_EDGES_FOUND}, elf::EasyElf, emu::Emulator, GuestAddr, GuestPhysAddr, QemuExecutor, QemuExitReason, QemuFilterList, QemuHooks, Regs, StdInstrumentationFilter
};
use rand::{SeedableRng, StdRng, Rng};
use crate::{
clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP}, mutational::{input_bytes_to_interrupt_times, InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME}, qemustate::QemuStateRestoreHelper, systemstate::{self, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback, SystraceErrorFeedback}, helpers::{QemuSystemStateHelper, ISR_SYMBOLS}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler}
clock::{ClockTimeFeedback, IcHist, QemuClockIncreaseFeedback, QemuClockObserver, FUZZ_START_TIMESTAMP}, mutational::{input_bytes_to_interrupt_times, InterruptShiftStage, MINIMUM_INTER_ARRIVAL_TIME}, qemustate::QemuStateRestoreHelper, systemstate::{self, feedbacks::{DumpSystraceFeedback, NovelSystemStateFeedback, SystraceErrorFeedback}, helpers::{get_function_range, load_symbol, try_load_symbol, QemuSystemStateHelper, ISR_SYMBOLS}, observers::QemuSystemStateObserver, schedulers::{GenerationScheduler, LongestTraceScheduler}, stg::{stg_map_mut_slice, GraphMaximizerCorpusScheduler, STGEdge, STGNode, StgFeedback, MAX_STG_NUM}}, worst::{AlwaysTrueFeedback, ExecTimeIncFeedback, TimeMaximizerCorpusScheduler, TimeProbMassScheduler, TimeStateMaximizerCorpusScheduler}
};
use std::time::{SystemTime, UNIX_EPOCH};
use clap::{Parser, Subcommand};
use csv::Reader;
use petgraph::{dot::{Config, Dot}, Graph};
use petgraph::graph::EdgeIndex;
@ -27,6 +26,10 @@ use crate::systemstate::stg::STGFeedbackState;
use crate::clock::QEMU_ICOUNT_SHIFT;
use libafl::inputs::HasMutatorBytes;
use libafl_qemu::Qemu;
use crate::cli::Cli;
use crate::cli::Commands;
use crate::cli::set_env_from_config;
use clap::Parser;
// Constants ================================================================================
@ -38,61 +41,6 @@ pub const FIRST_INT : u32 = 500000;
pub const MAX_NUM_INTERRUPT: usize = 128;
pub const DO_NUM_INTERRUPT: usize = 128;
pub static mut MAX_INPUT_SIZE: usize = 32;
/// Read ELF program headers to resolve physical load addresses.
fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr {
let ret;
for i in &tab.goblin().program_headers {
if i.vm_range().contains(&vaddr.try_into().unwrap()) {
ret = vaddr - TryInto::<GuestPhysAddr>::try_into(i.p_vaddr).unwrap()
+ TryInto::<GuestPhysAddr>::try_into(i.p_paddr).unwrap();
return ret - (ret % 2);
}
}
return vaddr;
}
pub fn load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> GuestAddr {
try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol))
}
pub fn try_load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> Option<GuestAddr> {
let ret = elf
.resolve_symbol(symbol, 0);
if do_translation {
Option::map_or(ret, None, |x| Some(virt2phys(x as GuestPhysAddr,&elf) as GuestAddr))
} else {ret}
}
pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range<GuestAddr>> {
let gob = elf.goblin();
let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect();
funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value));
for sym in &gob.syms {
if let Some(sym_name) = gob.strtab.get_at(sym.st_name) {
if sym_name == symbol {
if sym.st_value == 0 {
return None;
} else {
//#[cfg(cpu_target = "arm")]
// Required because of arm interworking addresses aka bit(0) for thumb mode
let addr = (sym.st_value as GuestAddr) & !(0x1 as GuestAddr);
//#[cfg(not(cpu_target = "arm"))]
//let addr = sym.st_value as GuestAddr;
// look for first function after addr
let sym_end = funcs.iter().find(|x| x.st_value > sym.st_value);
if let Some(sym_end) = sym_end {
// println!("{} {:#x}..{} {:#x}", gob.strtab.get_at(sym.st_name).unwrap_or(""),addr, gob.strtab.get_at(sym_end.st_name).unwrap_or(""),sym_end.st_value & !0x1);
return Some(addr..((sym_end.st_value & !0x1) as GuestAddr));
}
return None;
};
}
}
}
return None;
}
pub fn get_all_fn_symbol_ranges(elf: &EasyElf, api_range: std::ops::Range<GuestAddr>) -> HashMap<String,std::ops::Range<GuestAddr>> {
let mut api_addreses : HashMap<String,std::ops::Range<GuestAddr>> = HashMap::new();
@ -123,63 +71,6 @@ static mut libafl_interrupt_offsets : [u32; MAX_NUM_INTERRUPT];
static mut libafl_num_interrupts : usize;
}
// Argument parsing ================================================================================
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Kernel Image
#[arg(short, long, value_name = "FILE")]
kernel: PathBuf,
/// Sets a custom config file
#[arg(short, long, value_name = "FILE")]
config: PathBuf,
/// Sets the prefix of dumed files
#[arg(short='n', long, value_name = "FILENAME")]
dump_name: Option<PathBuf>,
/// do time dumps
#[arg(short='t', long)]
dump_times: bool,
/// do worst-case dumps
#[arg(short='a', long)]
dump_cases: bool,
/// do trace dumps (if supported)
#[arg(short='r', long)]
dump_traces: bool,
/// do graph dumps (if supported)
#[arg(short='g', long)]
dump_graph: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand,Clone)]
enum Commands {
/// run a single input
Showmap {
/// take this input
#[arg(short, long)]
input: PathBuf,
},
/// start fuzzing campaign
Fuzz {
/// disable heuristic
#[arg(short, long)]
random: bool,
/// seed for randomness
#[arg(short, long)]
seed: Option<u64>,
/// runtime in seconds
#[arg(short, long)]
time: Option<u64>,
}
}
/// Takes a state, cli and a suffix, writes out the current worst case
macro_rules! do_dump_case {
@ -193,12 +84,12 @@ macro_rules! do_dump_case {
for i in 0..corpus.count() {
let tc = corpus.get(corpus.nth(i.into())).expect("Could not get element from corpus").borrow();
if worst < tc.exec_time().expect("Testcase missing duration") {
worst_input = Some(tc.input().as_ref().unwrap().bytes().to_owned());
worst_input = Some(tc.input().as_ref().unwrap().clone());
worst = tc.exec_time().expect("Testcase missing duration");
}
}
if let Some(wi) = worst_input {
fs::write(dump_path,wi).expect("Failed to write worst corpus element");
wi.to_file(dump_path);
}
}
}
@ -260,39 +151,12 @@ macro_rules! do_dump_toprated {
};
}
fn env_from_config(kernel : &PathBuf, path : &PathBuf) {
let is_csv = path.as_path().extension().map_or(false, |x| x=="csv");
if !is_csv {
let lines = std::fs::read_to_string(path).expect("Config file not found");
let lines = lines.lines().filter(
|x| x.len()>0
);
for l in lines {
let pair = l.split_once('=').expect("Non VAR=VAL line in config");
std::env::set_var(pair.0, pair.1);
}
} else {
let mut reader = csv::Reader::from_path(path).expect("CSV read from config failed");
let p = kernel.as_path();
let stem = p.file_stem().expect("Kernel filename error").to_str().unwrap();
for r in reader.records() {
let rec = r.expect("CSV entry error");
if stem == &rec[0] {
std::env::set_var("FUZZ_MAIN", &rec[1]);
std::env::set_var("FUZZ_INPUT", &rec[2]);
std::env::set_var("FUZZ_INPUT_LEN", &rec[3]);
std::env::set_var("BREAKPOINT", &rec[4]);
break;
}
}
}
}
// Fuzzer setup ================================================================================
pub fn fuzz() {
let cli = Cli::parse();
env_from_config(&cli.kernel, &cli.config);
set_env_from_config(&cli.kernel, &cli.config);
unsafe {FUZZ_START_TIMESTAMP = SystemTime::now();}
if cli.dump_name.is_none() && (cli.dump_times || cli.dump_cases || cli.dump_traces || cli.dump_graph) {
panic!("Dump name not give but dump is requested");
@ -431,68 +295,69 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| {
"if=none,format=qcow2,file=dummy.qcow2",
].into_iter().map(String::from).collect();
let env: Vec<(String, String)> = env::vars().collect();
let emu = Qemu::init(&args, &env).expect("Emulator creation failed");
let qemu = Qemu::init(&args, &env).expect("Emulator creation failed");
if let Some(main_addr) = main_addr {
qemu.set_breakpoint(main_addr);
unsafe {
libafl_qemu::sys::libafl_qemu_set_native_breakpoint(main_addr as u64);
emu.run();
libafl_qemu::sys::libafl_qemu_remove_native_breakpoint(main_addr as u64);
match qemu.run() {
Ok(QemuExitReason::Breakpoint(_)) => {}
_ => panic!("Unexpected QEMU exit."),
}
}
qemu.remove_breakpoint(main_addr);
}
qemu.set_breakpoint(breakpoint); // BREAKPOINT
let devices = qemu.list_devices();
println!("Devices = {devices:?}");
#[cfg(feature = "snapshot_fast")]
let initial_snap = Some(emu.create_fast_snapshot(true));
let initial_snap = Some(qemu.create_fast_snapshot(true));
#[cfg(not(feature = "snapshot_fast"))]
let initial_snap = None;
unsafe { emu.set_breakpoint(breakpoint); }// BREAKPOINT
// 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();
let mut harness = |input: &MultipartInput<BytesInput>| {
unsafe {
#[cfg(feature = "fuzz_int")]
{
let t = input_bytes_to_interrupt_times(buf);
let time_bytes = input.parts_by_name("interrupts").next().unwrap().1.bytes();
let t = input_bytes_to_interrupt_times(time_bytes);
for i in 0..t.len() {libafl_interrupt_offsets[i]=t[i];}
libafl_num_interrupts=t.len();
if buf.len() > libafl_num_interrupts*4 {
buf = &buf[libafl_num_interrupts*4..];
len = buf.len();
}
// for i in 0 .. libafl_num_interrupts {
// libafl_interrupt_offsets[i] = FIRST_INT+TryInto::<u32>::try_into(i).unwrap()*MINIMUM_INTER_ARRIVAL_TIME;
// }
// println!("Load: {:?}", libafl_interrupt_offsets[0..libafl_num_interrupts].to_vec());
}
if len > MAX_INPUT_SIZE {
buf = &buf[0..MAX_INPUT_SIZE];
len = MAX_INPUT_SIZE;
}
// Note: I could not find a difference between write_mem and write_phys_mem for my usecase
emu.write_mem(input_addr, buf);
if let Some(s) = input_length_ptr {
emu.write_mem(s, &len.to_le_bytes())
}
emu.run();
// If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash
let mut pcs = (0..emu.num_cpus())
.map(|i| emu.cpu_from_index(i))
.map(|cpu| -> Result<u32, _> { cpu.read_reg(Regs::Pc) });
match pcs
.find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0)))
{
Some(_) => ExitKind::Ok,
None => ExitKind::Crash,
}
}
};
let mut bytes = input.parts_by_name("bytes").next().unwrap().1.bytes();
let mut len = bytes.len();
if len > MAX_INPUT_SIZE {
bytes = &bytes[0..MAX_INPUT_SIZE];
len = MAX_INPUT_SIZE;
}
// Note: I could not find a difference between write_mem and write_phys_mem for my usecase
qemu.write_mem(input_addr, bytes);
if let Some(s) = input_length_ptr {
qemu.write_mem(s, &len.to_le_bytes())
}
qemu.run();
// If the execution stops at any point other then the designated breakpoint (e.g. a breakpoint on a panic method) we consider it a crash
let mut pcs = (0..qemu.num_cpus())
.map(|i| qemu.cpu_from_index(i))
.map(|cpu| -> Result<u32, _> { cpu.read_reg(Regs::Pc) });
match pcs
.find(|pc| (breakpoint..breakpoint + 5).contains(pc.as_ref().unwrap_or(&0)))
{
Some(_) => ExitKind::Ok,
None => ExitKind::Crash,
}
}
};
// Create an observation channel to keep track of the execution time
let clock_time_observer = QemuClockObserver::new("clocktime", if cli.dump_times {cli.dump_name.clone().map(|x| x.with_extension("time"))} else {None} );
@ -599,7 +464,7 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| {
let qhelpers = (QemuEdgeCoverageHelper::new(denylist, QemuFilterList::None), qhelpers);
let qhelpers = (QemuStateRestoreHelper::with_fast(initial_snap), qhelpers);
let mut hooks = QemuHooks::new(emu.clone(),qhelpers);
let mut hooks = QemuHooks::new(qemu.clone(),qhelpers);
let observer_list = tuple_list!();
#[cfg(feature = "observe_systemstate")]
@ -633,16 +498,17 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| {
if let Commands::Showmap { input } = cli.command.clone() {
let s = input.as_os_str();
let show_input = if s=="-" {
let mut buf = Vec::<u8>::new();
std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin");
buf
} else if s=="$" {
env::var("SHOWMAP_TEXTINPUT").expect("SHOWMAP_TEXTINPUT not set").as_bytes().to_owned()
} else {
fs::read(s).expect("Input file for DO_SHOWMAP can not be read")
};
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, BytesInput::new(show_input))
// let show_input = BytesInput::new(if s=="-" {
// let mut buf = Vec::<u8>::new();
// std::io::stdin().read_to_end(&mut buf).expect("Could not read Stdin");
// buf
// } else if s=="$" {
// env::var("SHOWMAP_TEXTINPUT").expect("SHOWMAP_TEXTINPUT not set").as_bytes().to_owned()
// } else {
// // fs::read(s).expect("Input file for DO_SHOWMAP can not be read")
// });
let show_input = MultipartInput::from_file(input.as_os_str()).expect("Error reading input file");
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, show_input)
.unwrap();
do_dump_times!(state, &cli, "");
do_dump_stg!(state, &cli, "");
@ -651,7 +517,9 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| {
unsafe {
let mut rng = StdRng::seed_from_u64(se);
for i in 0..10 {
let inp = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
let inp1 = BytesInput::new(vec![rng.gen::<u8>(); MAX_NUM_INTERRUPT*4]);
let inp2 = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
let inp = MultipartInput::from([("interrupts",inp1),("bytes",inp2)]);
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap();
}
}
@ -692,7 +560,9 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| {
while start_time.elapsed() < target_duration {
// let inp = generator.generate(&mut state).unwrap();
// libafl's generator is too slow
let inp = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
let inp1 = BytesInput::new(vec![rng.gen::<u8>(); MAX_NUM_INTERRUPT*4]);
let inp2 = BytesInput::new(vec![rng.gen::<u8>(); MAX_INPUT_SIZE]);
let inp = MultipartInput::from([("interrupts",inp1),("bytes",inp2)]);
fuzzer.evaluate_input(&mut state, &mut executor, &mut mgr, inp).unwrap();
}
}} else {
@ -752,7 +622,7 @@ let mut run_client = |state: Option<_>, mut mgr, _core_id| {
let emu = Qemu::init(&args, &env).expect("Emu creation failed");
if let Some(main_addr) = main_addr {
unsafe { emu.set_breakpoint(main_addr); }// BREAKPOINT
emu.set_breakpoint(main_addr); // BREAKPOINT
}
unsafe {
emu.run();

View File

@ -10,3 +10,5 @@ pub mod systemstate;
mod mutational;
#[cfg(target_os = "linux")]
mod worst;
#[cfg(target_os = "linux")]
mod cli;

View File

@ -11,6 +11,8 @@ mod systemstate;
mod worst;
#[cfg(target_os = "linux")]
mod mutational;
#[cfg(target_os = "linux")]
mod cli;
#[cfg(target_os = "linux")]
pub fn main() {

View File

@ -10,7 +10,7 @@ use libafl_bolts::rands::{
Rand
};
use libafl::{
common::{HasMetadata, HasNamedMetadata}, corpus::{self, Corpus}, fuzzer::Evaluator, inputs::HasMutatorBytes, mark_feature_time, prelude::{new_hash_feedback, CorpusId, MutationResult, Mutator, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error
common::{HasMetadata, HasNamedMetadata}, corpus::{self, Corpus}, fuzzer::Evaluator, inputs::{HasMutatorBytes, HasTargetBytes, Input, MultipartInput}, mark_feature_time, prelude::{new_hash_feedback, CorpusId, MutationResult, Mutator, UsesInput}, stages::Stage, start_timer, state::{HasCorpus, HasRand, MaybeHasClientPerfMonitor, UsesState}, Error
};
use libafl::prelude::State;
use crate::{clock::{IcHist, QEMU_ISNS_PER_USEC}, fuzzer::{DO_NUM_INTERRUPT, FIRST_INT, MAX_NUM_INTERRUPT}, systemstate::{stg::{STGFeedbackState, STGNodeMetadata}, CaptureEvent, ExecInterval, FreeRTOSSystemStateMetadata, ReducedFreeRTOSSystemState}};
@ -72,13 +72,15 @@ where
}
}
impl<E, EM, Z> Stage<E, EM, Z> for InterruptShiftStage<E, EM, Z>
impl<E, EM, Z, I> Stage<E, EM, Z> for InterruptShiftStage<E, EM, Z>
where
E: UsesState<State = Z::State>,
EM: UsesState<State = Z::State>,
Z: Evaluator<E, EM>,
Z::State: MaybeHasClientPerfMonitor + HasCorpus + HasRand + HasMetadata + HasNamedMetadata,
<Z::State as UsesInput>::Input: HasMutatorBytes
<Z::State as UsesInput>::Input: Input,
Z::State: UsesInput<Input = MultipartInput<I>>,
I: HasMutatorBytes + Default
{
fn perform(
&mut self,
@ -86,35 +88,34 @@ where
executor: &mut E,
state: &mut Self::State,
manager: &mut EM
// corpus_idx: CorpusId,
) -> Result<(), Error> {
let mut myrand = StdRand::new();
myrand.set_seed(state.rand_mut().next());
let mut _input = state.current_testcase()?.clone();
let mut newinput = _input.input().as_ref().unwrap().clone();
let current_case = state.current_testcase()?;
let old_input = current_case.input().as_ref().unwrap();
let old_interrupt_times = old_input.parts_by_name("interrupts").next();
let mut new_input = old_input.clone();
let mut new_interrupt_times : &mut I = if new_input.parts_by_name("interrupts").next().is_some() {
new_input.parts_by_name_mut("interrupts").next().unwrap()
} else {
new_input.add_part(String::from("interrupts"), I::default()); new_input.parts_by_name_mut("interrupts").next().unwrap()
}.1;
let mut do_rerun = false;
// if state.rand_mut().between(1, 100) <= 50 // only attempt the mutation half of the time
{
// need our own random generator, because borrowing rules
let mut target_bytes : Vec<u8> = vec![];
{
let input = _input.input().as_ref().unwrap();
target_bytes = input.bytes().to_vec();
}
// produce a slice of absolute interrupt times
let mut interrupt_offsets : [u32; MAX_NUM_INTERRUPT] = [u32::MAX; MAX_NUM_INTERRUPT];
let mut num_interrupts : usize = 0;
{
let t = input_bytes_to_interrupt_times(&target_bytes);
let t = input_bytes_to_interrupt_times(new_interrupt_times.bytes());
for i in 0..t.len() {interrupt_offsets[i]=t[i];}
num_interrupts=t.len();
}
interrupt_offsets.sort_unstable();
// println!("Vor Mutator: {:?}", interrupt_offsets[0..num_interrupts].to_vec());
// let num_i = min(target_bytes.len() / 4, DO_NUM_INTERRUPT);
let mut suffix = target_bytes.split_off(4 * num_interrupts);
let mut prefix : Vec<[u8; 4]> = vec![];
// let mut suffix : Vec<u8> = vec![];
#[cfg(feature = "mutate_stg")]
@ -201,7 +202,7 @@ where
// }
// else { // old version of the alternative search
{
let tmp = _input.metadata_map().get::<STGNodeMetadata>();
let tmp = current_case.metadata_map().get::<STGNodeMetadata>();
if tmp.is_some() {
let trace = tmp.expect("STGNodeMetadata not found");
@ -318,15 +319,13 @@ where
}
}
}
let mut n : Vec<u8> = vec![];
n = [prefix.concat(), suffix].concat();
newinput.drain(..);
newinput.extend(&n);
new_interrupt_times.drain(..);
new_interrupt_times.extend(&prefix.concat());
}
drop(current_case);
// InterruptShifterMutator::mutate(&mut mymut, state, &mut input, 0)?;
if do_rerun {
let (_, corpus_idx) = fuzzer.evaluate_input(state, executor, manager, newinput)?;
let (_, corpus_idx) = fuzzer.evaluate_input(state, executor, manager, new_input)?;
}
Ok(())
}

View File

@ -73,7 +73,7 @@ where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>
{
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
let observer = observers.match_name::<QemuSystemStateObserver<S::Input>>("systemstate")
.expect("QemuSystemStateObserver not found");
let clock_observer = observers.match_name::<QemuClockObserver>("clocktime") //TODO not fixed
.expect("QemuClockObserver not found");
@ -181,7 +181,7 @@ where
OT: ObserversTuple<S>
{
if self.dumpfile.is_none() {return Ok(false)};
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
let observer = observers.match_name::<QemuSystemStateObserver<S::Input>>("systemstate")
.expect("QemuSystemStateObserver not found");
let names : Vec<String> = observer.last_run.iter().map(|x| x.current_task.task_name.clone()).collect();
match &self.dumpfile {
@ -263,7 +263,7 @@ where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>
{
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
let observer = observers.match_name::<QemuSystemStateObserver<S::Input>>("systemstate")
.expect("QemuSystemStateObserver not found");
Ok(self.dump_case&&!observer.success)
}

View File

@ -2,8 +2,10 @@ use std::ops::Range;
use hashbrown::HashMap;
use libafl::prelude::ExitKind;
use libafl::prelude::UsesInput;
use libafl_qemu::elf::EasyElf;
use libafl_qemu::read_user_reg_unchecked;
use libafl_qemu::GuestAddr;
use libafl_qemu::GuestPhysAddr;
use libafl_qemu::QemuHooks;
use libafl_qemu::Hook;
use libafl_qemu::helpers::{QemuHelper, QemuHelperTuple};
@ -24,9 +26,67 @@ use super::CaptureEvent;
pub const ISR_SYMBOLS : &'static [&'static str] = &[
// ISRs
"Reset_Handler","Default_Handler","Default_Handler2","Default_Handler3","Default_Handler4","Default_Handler5","Default_Handler6","vPortSVCHandler","xPortPendSVHandler","xPortSysTickHandler","isr_starter"//,"vTaskGenericNotifyGiveFromISR"
"Reset_Handler","Default_Handler","Default_Handler2","Default_Handler3","Default_Handler4","Default_Handler5","Default_Handler6","vPortSVCHandler","xPortPendSVHandler","xPortSysTickHandler","isr_starter"
];
/// Read ELF program headers to resolve physical load addresses.
fn virt2phys(vaddr: GuestPhysAddr, tab: &EasyElf) -> GuestPhysAddr {
let ret;
for i in &tab.goblin().program_headers {
if i.vm_range().contains(&vaddr.try_into().unwrap()) {
ret = vaddr - TryInto::<GuestPhysAddr>::try_into(i.p_vaddr).unwrap()
+ TryInto::<GuestPhysAddr>::try_into(i.p_paddr).unwrap();
return ret - (ret % 2);
}
}
return vaddr;
}
/// Lookup a symbol in the ELF file, optionally resolve segment offsets
pub fn load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> GuestAddr {
try_load_symbol(elf, symbol, do_translation).expect(&format!("Symbol {} not found", symbol))
}
pub fn try_load_symbol(elf : &EasyElf, symbol : &str, do_translation : bool) -> Option<GuestAddr> {
let ret = elf
.resolve_symbol(symbol, 0);
if do_translation {
Option::map_or(ret, None, |x| Some(virt2phys(x as GuestPhysAddr,&elf) as GuestAddr))
} else {ret}
}
/// Try looking up the address range of a function in the ELF file
pub fn get_function_range(elf: &EasyElf, symbol: &str) -> Option<std::ops::Range<GuestAddr>> {
let gob = elf.goblin();
let mut funcs : Vec<_> = gob.syms.iter().filter(|x| x.is_function()).collect();
funcs.sort_unstable_by(|x,y| x.st_value.cmp(&y.st_value));
for sym in &gob.syms {
if let Some(sym_name) = gob.strtab.get_at(sym.st_name) {
if sym_name == symbol {
if sym.st_value == 0 {
return None;
} else {
//#[cfg(cpu_target = "arm")]
// Required because of arm interworking addresses aka bit(0) for thumb mode
let addr = (sym.st_value as GuestAddr) & !(0x1 as GuestAddr);
//#[cfg(not(cpu_target = "arm"))]
//let addr = sym.st_value as GuestAddr;
// look for first function after addr
let sym_end = funcs.iter().find(|x| x.st_value > sym.st_value);
if let Some(sym_end) = sym_end {
// println!("{} {:#x}..{} {:#x}", gob.strtab.get_at(sym.st_name).unwrap_or(""),addr, gob.strtab.get_at(sym_end.st_name).unwrap_or(""),sym_end.st_value & !0x1);
return Some(addr..((sym_end.st_value & !0x1) as GuestAddr));
}
return None;
};
}
}
}
return None;
}
//============================= Qemu Helper
/// A Qemu Helper with reads FreeRTOS specific structs from Qemu whenever certain syscalls occur, also inject inputs

View File

@ -28,20 +28,19 @@ use super::{
/// that will get updated by the target.
#[derive(Serialize, Deserialize, Debug)]
#[allow(clippy::unsafe_derive_deserialize)]
pub struct QemuSystemStateObserver
pub struct QemuSystemStateObserver<I>
{
pub last_run: Vec<ReducedFreeRTOSSystemState>,
pub last_states: HashMap<u64, ReducedFreeRTOSSystemState>,
pub last_trace: Vec<ExecInterval>,
pub last_input: Vec<u8>,
pub last_input: I,
pub success: bool,
name: Cow<'static, str>,
}
impl<S> Observer<S> for QemuSystemStateObserver
impl<S> Observer<S> for QemuSystemStateObserver<S::Input>
where
S: UsesInput,
S::Input : HasTargetBytes,
{
#[inline]
fn pre_exec(&mut self, _state: &mut S, _input: &S::Input) -> Result<(), Error> {
@ -67,19 +66,19 @@ where
// println!("{:?}",abbs);
// let abbs = trace_to_state_abb(&self.last_run);
// println!("{:?}",abbs);
self.last_input=_input.target_bytes().as_slice().to_owned();
self.last_input=_input.clone();
Ok(())
}
}
impl Named for QemuSystemStateObserver
impl<I> Named for QemuSystemStateObserver<I>
{
fn name(&self) -> &Cow<'static, str> {
&self.name
}
}
impl HasLen for QemuSystemStateObserver
impl<I> HasLen for QemuSystemStateObserver<I>
{
#[inline]
fn len(&self) -> usize {
@ -87,12 +86,14 @@ impl HasLen for QemuSystemStateObserver
}
}
impl QemuSystemStateObserver {
impl<I> QemuSystemStateObserver<I>
where I: Default {
pub fn new() -> Self {
Self{last_run: vec![], last_trace: vec![], last_input: vec![], name: Cow::from("systemstate".to_string()), last_states: HashMap::new(), success: false }
Self{last_run: vec![], last_trace: vec![], last_input: I::default(), name: Cow::from("systemstate".to_string()), last_states: HashMap::new(), success: false }
}
}
impl Default for QemuSystemStateObserver {
impl<I> Default for QemuSystemStateObserver<I>
where I: Default {
fn default() -> Self {
Self::new()
}

View File

@ -245,17 +245,6 @@ impl STGNodeMetadata {
}
}
// impl AsSlice for STGNodeMetadata {
// /// Convert the slice of system-states to a slice of hashes over enumerated states
// fn as_slice(&self) -> &[usize] {
// self.indices.as_slice()
// }
// type Entry = usize;
// type SliceRef = &[usize];
// }
impl Deref for STGNodeMetadata {
type Target = [usize];
/// Convert to a slice
@ -467,7 +456,6 @@ impl StgFeedback {
impl<S> Feedback<S> for StgFeedback
where
S: State + UsesInput + MaybeHasClientPerfMonitor + HasNamedMetadata,
S::Input: HasTargetBytes,
{
#[allow(clippy::wrong_self_convention)]
fn is_interesting<EM, OT>(
@ -482,7 +470,7 @@ where
EM: EventFirer<State = S>,
OT: ObserversTuple<S>,
{
let observer = observers.match_name::<QemuSystemStateObserver>("systemstate")
let observer = observers.match_name::<QemuSystemStateObserver<S::Input>>("systemstate")
.expect("QemuSystemStateObserver not found");
let clock_observer = observers.match_name::<QemuClockObserver>("clocktime")
.expect("QemuClockObserver not found");

View File

@ -48,7 +48,6 @@ pub type TimeMaximizerCorpusScheduler<CS, O> =
pub struct MaxTimeFavFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
phantom: PhantomData<S>,
}
@ -56,7 +55,6 @@ where
impl<S> TestcaseScore<S> for MaxTimeFavFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
fn compute(state: &S, entry: &mut Testcase<<S as UsesInput>::Input>) -> Result<f64, Error> {
// TODO maybe enforce entry.exec_time().is_some()
@ -420,7 +418,6 @@ pub type TimeProbMassScheduler<S> =
pub struct TimeProbFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
phantom: PhantomData<S>,
}
@ -428,7 +425,6 @@ where
impl<S> TestcaseScore<S> for TimeProbFactor<S>
where
S: HasCorpus + HasMetadata,
S::Input: HasLen,
{
fn compute(_state: &S, entry: &mut Testcase<<S as UsesInput>::Input>) -> Result<f64, Error> {
// TODO maybe enforce entry.exec_time().is_some()