move cli parsing, use multibyteinput
This commit is contained in:
parent
acf9b04e70
commit
77799f77a9
@ -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
88
fuzzers/FRET/src/cli.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -10,3 +10,5 @@ pub mod systemstate;
|
||||
mod mutational;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod worst;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod cli;
|
@ -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() {
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user