Define custom collectors for QemuCallTracerHelper (#1099)
* Define custom collectors for QemuCallTracerHelper and create OnCrashBacktraceCollector * fmt * clippy
This commit is contained in:
parent
3ffec79a17
commit
20c32316eb
@ -52,6 +52,8 @@ pub enum HarnessType {
|
|||||||
InProcess,
|
InProcess,
|
||||||
/// Harness type when the target is a child process
|
/// Harness type when the target is a child process
|
||||||
Child,
|
Child,
|
||||||
|
/// Harness type with an external component filling the backtrace hash (e.g. `CrashBacktraceCollector` in `libafl_qemu`)
|
||||||
|
External,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An observer looking at the backtrace after the harness crashes
|
/// An observer looking at the backtrace after the harness crashes
|
||||||
@ -87,6 +89,17 @@ impl<'a> BacktraceObserver<'a> {
|
|||||||
fn clear_hash(&mut self) {
|
fn clear_hash(&mut self) {
|
||||||
*self.hash.as_mut() = None;
|
*self.hash.as_mut() = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fill the hash value if the harness type is external
|
||||||
|
pub fn fill_external(&mut self, hash: u64, exit_kind: &ExitKind) {
|
||||||
|
if self.harness_type == HarnessType::External {
|
||||||
|
if *exit_kind == ExitKind::Crash {
|
||||||
|
self.update_hash(hash);
|
||||||
|
} else {
|
||||||
|
self.clear_hash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ObserverWithHashField for BacktraceObserver<'a> {
|
impl<'a> ObserverWithHashField for BacktraceObserver<'a> {
|
||||||
@ -108,7 +121,7 @@ where
|
|||||||
exit_kind: &ExitKind,
|
exit_kind: &ExitKind,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if self.harness_type == HarnessType::InProcess {
|
if self.harness_type == HarnessType::InProcess {
|
||||||
if exit_kind == &ExitKind::Crash {
|
if *exit_kind == ExitKind::Crash {
|
||||||
self.update_hash(collect_backtrace());
|
self.update_hash(collect_backtrace());
|
||||||
} else {
|
} else {
|
||||||
self.clear_hash();
|
self.clear_hash();
|
||||||
@ -124,7 +137,7 @@ where
|
|||||||
exit_kind: &ExitKind,
|
exit_kind: &ExitKind,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if self.harness_type == HarnessType::Child {
|
if self.harness_type == HarnessType::Child {
|
||||||
if exit_kind == &ExitKind::Crash {
|
if *exit_kind == ExitKind::Crash {
|
||||||
self.update_hash(collect_backtrace());
|
self.update_hash(collect_backtrace());
|
||||||
} else {
|
} else {
|
||||||
self.clear_hash();
|
self.clear_hash();
|
||||||
|
@ -6,7 +6,9 @@ use std::{
|
|||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use libafl::{executors::ExitKind, inputs::UsesInput, state::HasMetadata};
|
use libafl::{
|
||||||
|
executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata,
|
||||||
|
};
|
||||||
use libc::{
|
use libc::{
|
||||||
c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE,
|
c_void, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, PROT_WRITE,
|
||||||
};
|
};
|
||||||
@ -730,7 +732,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input, exit_kind: &mut ExitKind) {
|
fn post_exec<OT>(
|
||||||
|
&mut self,
|
||||||
|
emulator: &Emulator,
|
||||||
|
_input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
|
exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
{
|
||||||
if self.reset(emulator) == AsanRollback::HasLeaks {
|
if self.reset(emulator) == AsanRollback::HasLeaks {
|
||||||
*exit_kind = ExitKind::Crash;
|
*exit_kind = ExitKind::Crash;
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,234 @@
|
|||||||
|
use core::fmt::Debug;
|
||||||
|
|
||||||
use capstone::prelude::*;
|
use capstone::prelude::*;
|
||||||
use libafl::inputs::UsesInput;
|
use libafl::{
|
||||||
|
bolts::tuples::{MatchFirstType, Named},
|
||||||
|
executors::ExitKind,
|
||||||
|
inputs::{Input, UsesInput},
|
||||||
|
observers::{stacktrace::BacktraceObserver, ObserversTuple},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
capstone,
|
capstone,
|
||||||
|
emu::Emulator,
|
||||||
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
|
helper::{QemuHelper, QemuHelperTuple, QemuInstrumentationFilter},
|
||||||
hooks::QemuHooks,
|
hooks::QemuHooks,
|
||||||
Emulator, GuestAddr, Regs,
|
GuestAddr, Regs,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub trait CallTraceCollector: 'static + Debug {
|
||||||
pub struct QemuCallTracerHelper {
|
fn on_call<QT, S>(
|
||||||
filter: QemuInstrumentationFilter,
|
&mut self,
|
||||||
cs: Capstone,
|
hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
callstack: Vec<GuestAddr>,
|
state: Option<&mut S>,
|
||||||
|
pc: GuestAddr,
|
||||||
|
call_len: usize,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>;
|
||||||
|
|
||||||
|
fn on_ret<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
state: Option<&mut S>,
|
||||||
|
pc: GuestAddr,
|
||||||
|
ret_addr: GuestAddr,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>;
|
||||||
|
|
||||||
|
// Frowarded from the `QemuCallTracerHelper`
|
||||||
|
fn pre_exec<I>(&mut self, _emulator: &Emulator, _input: &I)
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_exec<OT, S>(
|
||||||
|
&mut self,
|
||||||
|
_emulator: &Emulator,
|
||||||
|
_input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
|
_exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
S: UsesInput,
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QemuCallTracerHelper {
|
pub trait CallTraceCollectorTuple: 'static + MatchFirstType + Debug {
|
||||||
|
fn on_call_all<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
pc: GuestAddr,
|
||||||
|
call_len: usize,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>;
|
||||||
|
|
||||||
|
fn on_ret_all<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
_pc: GuestAddr,
|
||||||
|
ret_addr: GuestAddr,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>;
|
||||||
|
|
||||||
|
fn pre_exec_all<I>(&mut self, _emulator: &Emulator, input: &I)
|
||||||
|
where
|
||||||
|
I: Input;
|
||||||
|
|
||||||
|
fn post_exec_all<OT, S>(
|
||||||
|
&mut self,
|
||||||
|
_emulator: &Emulator,
|
||||||
|
input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
|
_exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
S: UsesInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallTraceCollectorTuple for () {
|
||||||
|
fn on_call_all<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
_hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
_pc: GuestAddr,
|
||||||
|
_call_len: usize,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_ret_all<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
_hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
_pc: GuestAddr,
|
||||||
|
_ret_addr: GuestAddr,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_exec_all<I>(&mut self, _emulator: &Emulator, _input: &I)
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_exec_all<OT, S>(
|
||||||
|
&mut self,
|
||||||
|
_emulator: &Emulator,
|
||||||
|
_input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
|
_exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
S: UsesInput,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Head, Tail> CallTraceCollectorTuple for (Head, Tail)
|
||||||
|
where
|
||||||
|
Head: CallTraceCollector,
|
||||||
|
Tail: CallTraceCollectorTuple,
|
||||||
|
{
|
||||||
|
fn on_call_all<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
mut state: Option<&mut S>,
|
||||||
|
pc: GuestAddr,
|
||||||
|
call_len: usize,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
self.0.on_call(
|
||||||
|
hooks,
|
||||||
|
match state.as_mut() {
|
||||||
|
Some(s) => Some(*s),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
pc,
|
||||||
|
call_len,
|
||||||
|
);
|
||||||
|
self.1.on_call_all(hooks, state, pc, call_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_ret_all<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
mut state: Option<&mut S>,
|
||||||
|
pc: GuestAddr,
|
||||||
|
ret_addr: GuestAddr,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
self.0.on_ret(
|
||||||
|
hooks,
|
||||||
|
match state.as_mut() {
|
||||||
|
Some(s) => Some(*s),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
pc,
|
||||||
|
ret_addr,
|
||||||
|
);
|
||||||
|
self.1.on_ret_all(hooks, state, pc, ret_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_exec_all<I>(&mut self, emulator: &Emulator, input: &I)
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
|
self.0.pre_exec(emulator, input);
|
||||||
|
self.1.pre_exec_all(emulator, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_exec_all<OT, S>(
|
||||||
|
&mut self,
|
||||||
|
emulator: &Emulator,
|
||||||
|
input: &S::Input,
|
||||||
|
observers: &mut OT,
|
||||||
|
exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
S: UsesInput,
|
||||||
|
{
|
||||||
|
self.0.post_exec(emulator, input, observers, exit_kind);
|
||||||
|
self.1.post_exec_all(emulator, input, observers, exit_kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct QemuCallTracerHelper<T>
|
||||||
|
where
|
||||||
|
T: CallTraceCollectorTuple,
|
||||||
|
{
|
||||||
|
filter: QemuInstrumentationFilter,
|
||||||
|
cs: Capstone,
|
||||||
|
collectors: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> QemuCallTracerHelper<T>
|
||||||
|
where
|
||||||
|
T: CallTraceCollectorTuple,
|
||||||
|
{
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(filter: QemuInstrumentationFilter) -> Self {
|
pub fn new(filter: QemuInstrumentationFilter, collectors: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
filter,
|
filter,
|
||||||
cs: capstone().detail(true).build().unwrap(),
|
cs: capstone().detail(true).build().unwrap(),
|
||||||
callstack: vec![],
|
collectors: Some(collectors),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,181 +237,280 @@ impl QemuCallTracerHelper {
|
|||||||
self.filter.allowed(addr)
|
self.filter.allowed(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
fn on_ret<QT, S>(hooks: &mut QemuHooks<'_, QT, S>, state: Option<&mut S>, pc: GuestAddr)
|
||||||
pub fn callstack(&self) -> &[GuestAddr] {
|
where
|
||||||
&self.callstack
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
#[cfg(cpu_target = "x86_64")]
|
||||||
|
let ret_addr = {
|
||||||
|
let emu = hooks.emulator();
|
||||||
|
let stack_ptr: GuestAddr = emu.read_reg(Regs::Rsp).unwrap();
|
||||||
|
let mut ret_addr = [0; 8];
|
||||||
|
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
|
||||||
|
GuestAddr::from_le_bytes(ret_addr)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(cpu_target = "i386")]
|
||||||
|
let ret_addr = {
|
||||||
|
let emu = hooks.emulator();
|
||||||
|
let stack_ptr: GuestAddr = emu.read_reg(Regs::Esp).unwrap();
|
||||||
|
let mut ret_addr = [0; 4];
|
||||||
|
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
|
||||||
|
GuestAddr::from_le_bytes(ret_addr)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(any(cpu_target = "arm", cpu_target = "aarch64"))]
|
||||||
|
let ret_addr = {
|
||||||
|
let emu = hooks.emulator();
|
||||||
|
let ret_addr: GuestAddr = emu.read_reg(Regs::Lr).unwrap();
|
||||||
|
ret_addr
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(cpu_target = "mips")]
|
||||||
|
let ret_addr = {
|
||||||
|
let emu = hooks.emulator();
|
||||||
|
let ret_addr: GuestAddr = emu.read_reg(Regs::Ra).unwrap();
|
||||||
|
ret_addr
|
||||||
|
};
|
||||||
|
|
||||||
|
// log::info!("RET @ 0x{:#x}", ret_addr);
|
||||||
|
|
||||||
|
let mut collectors = if let Some(h) = hooks.helpers_mut().match_first_type_mut::<Self>() {
|
||||||
|
h.collectors.take()
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
collectors
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.on_ret_all(hooks, state, pc, ret_addr);
|
||||||
|
hooks
|
||||||
|
.helpers_mut()
|
||||||
|
.match_first_type_mut::<Self>()
|
||||||
|
.unwrap()
|
||||||
|
.collectors = collectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
fn gen_blocks_calls<QT, S>(
|
||||||
self.callstack.clear();
|
hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
pc: GuestAddr,
|
||||||
|
) -> Option<u64>
|
||||||
|
where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
let emu = hooks.emulator();
|
||||||
|
if let Some(h) = hooks.helpers().match_first_type::<Self>() {
|
||||||
|
if !h.must_instrument(pc) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut code = {
|
||||||
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts(emu.g2h(pc), 512)
|
||||||
|
}
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
&mut [0; 512]
|
||||||
|
};
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
unsafe {
|
||||||
|
emu.read_mem(pc, code)
|
||||||
|
}; // TODO handle faults
|
||||||
|
|
||||||
|
let mut iaddr = pc;
|
||||||
|
|
||||||
|
'disasm: while let Ok(insns) = h.cs.disasm_count(code, iaddr.into(), 1) {
|
||||||
|
if insns.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let insn = insns.first().unwrap();
|
||||||
|
let insn_detail: InsnDetail = h.cs.insn_detail(insn).unwrap();
|
||||||
|
for detail in insn_detail.groups() {
|
||||||
|
match u32::from(detail.0) {
|
||||||
|
capstone::InsnGroupType::CS_GRP_CALL => {
|
||||||
|
let call_len = insn.bytes().len();
|
||||||
|
// TODO do not use a closure, find a more efficient way to pass call_len
|
||||||
|
let call_cb = move |hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
state: Option<&mut S>,
|
||||||
|
pc| {
|
||||||
|
// eprintln!("CALL @ 0x{:#x}", pc + call_len);
|
||||||
|
let mut collectors = if let Some(h) =
|
||||||
|
hooks.helpers_mut().match_first_type_mut::<Self>()
|
||||||
|
{
|
||||||
|
h.collectors.take()
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
collectors
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.on_call_all(hooks, state, pc, call_len);
|
||||||
|
hooks
|
||||||
|
.helpers_mut()
|
||||||
|
.match_first_type_mut::<Self>()
|
||||||
|
.unwrap()
|
||||||
|
.collectors = collectors;
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
hooks.instruction_closure(
|
||||||
|
insn.address() as GuestAddr,
|
||||||
|
Box::new(call_cb),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
capstone::InsnGroupType::CS_GRP_RET => {
|
||||||
|
hooks.instruction(insn.address() as GuestAddr, Self::on_ret, false);
|
||||||
|
break 'disasm;
|
||||||
|
}
|
||||||
|
capstone::InsnGroupType::CS_GRP_INVALID
|
||||||
|
| capstone::InsnGroupType::CS_GRP_JUMP
|
||||||
|
| capstone::InsnGroupType::CS_GRP_IRET
|
||||||
|
| capstone::InsnGroupType::CS_GRP_PRIVILEGE => {
|
||||||
|
break 'disasm;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iaddr += insn.bytes().len() as GuestAddr;
|
||||||
|
|
||||||
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
unsafe {
|
||||||
|
code = std::slice::from_raw_parts(emu.g2h(iaddr), 512);
|
||||||
|
}
|
||||||
|
#[cfg(emulation_mode = "systemmode")]
|
||||||
|
unsafe {
|
||||||
|
emu.read_mem(pc, code);
|
||||||
|
} // TODO handle faults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for QemuCallTracerHelper {
|
impl<S, T> QemuHelper<S> for QemuCallTracerHelper<T>
|
||||||
fn default() -> Self {
|
|
||||||
Self::new(QemuInstrumentationFilter::None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> QemuHelper<S> for QemuCallTracerHelper
|
|
||||||
where
|
where
|
||||||
S: UsesInput,
|
S: UsesInput,
|
||||||
|
T: CallTraceCollectorTuple,
|
||||||
{
|
{
|
||||||
fn init_hooks<QT>(&self, hooks: &QemuHooks<'_, QT, S>)
|
fn first_exec<QT>(&self, hooks: &QemuHooks<'_, QT, S>)
|
||||||
where
|
where
|
||||||
QT: QemuHelperTuple<S>,
|
QT: QemuHelperTuple<S>,
|
||||||
{
|
{
|
||||||
hooks.blocks(Some(gen_blocks_calls::<QT, S>), None);
|
hooks.blocks(Some(Self::gen_blocks_calls::<QT, S>), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {
|
fn pre_exec(&mut self, emulator: &Emulator, input: &S::Input) {
|
||||||
|
self.collectors
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.pre_exec_all(emulator, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_exec<OT>(
|
||||||
|
&mut self,
|
||||||
|
emulator: &Emulator,
|
||||||
|
input: &S::Input,
|
||||||
|
observers: &mut OT,
|
||||||
|
exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
{
|
||||||
|
self.collectors
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.post_exec_all(emulator, input, observers, exit_kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OnCrashBacktraceCollector {
|
||||||
|
callstack_hash: u64,
|
||||||
|
observer_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OnCrashBacktraceCollector {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(observer: &BacktraceObserver<'_>) -> Self {
|
||||||
|
Self {
|
||||||
|
callstack_hash: 0,
|
||||||
|
observer_name: observer.name().to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_name(observer_name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
callstack_hash: 0,
|
||||||
|
observer_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn callstack_hash(&self) -> u64 {
|
||||||
|
self.callstack_hash
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.callstack_hash = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallTraceCollector for OnCrashBacktraceCollector {
|
||||||
|
#[allow(clippy::unnecessary_cast)]
|
||||||
|
fn on_call<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
_hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
pc: GuestAddr,
|
||||||
|
call_len: usize,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
self.callstack_hash ^= pc as u64 + call_len as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_cast)]
|
||||||
|
fn on_ret<QT, S>(
|
||||||
|
&mut self,
|
||||||
|
_hooks: &mut QemuHooks<'_, QT, S>,
|
||||||
|
_state: Option<&mut S>,
|
||||||
|
_pc: GuestAddr,
|
||||||
|
ret_addr: GuestAddr,
|
||||||
|
) where
|
||||||
|
S: UsesInput,
|
||||||
|
QT: QemuHelperTuple<S>,
|
||||||
|
{
|
||||||
|
self.callstack_hash ^= ret_addr as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pre_exec<I>(&mut self, _emulator: &Emulator, _input: &I)
|
||||||
|
where
|
||||||
|
I: Input,
|
||||||
|
{
|
||||||
self.reset();
|
self.reset();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/*pub fn on_call<QT, S>(hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, pc: GuestAddr)
|
fn post_exec<OT, S>(
|
||||||
where
|
&mut self,
|
||||||
|
_emulator: &Emulator,
|
||||||
QT: QemuHelperTuple<S>,
|
_input: &S::Input,
|
||||||
{
|
observers: &mut OT,
|
||||||
}*/
|
exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
pub fn on_ret<QT, S>(hooks: &mut QemuHooks<'_, QT, S>, _state: Option<&mut S>, _pc: GuestAddr)
|
OT: ObserversTuple<S>,
|
||||||
where
|
S: UsesInput,
|
||||||
S: UsesInput,
|
|
||||||
QT: QemuHelperTuple<S>,
|
|
||||||
{
|
|
||||||
#[cfg(cpu_target = "x86_64")]
|
|
||||||
let ret_addr = {
|
|
||||||
let emu = hooks.emulator();
|
|
||||||
let stack_ptr: GuestAddr = emu.read_reg(Regs::Rsp).unwrap();
|
|
||||||
let mut ret_addr = [0; 8];
|
|
||||||
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
|
|
||||||
GuestAddr::from_le_bytes(ret_addr)
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(cpu_target = "i386")]
|
|
||||||
let ret_addr = {
|
|
||||||
let emu = hooks.emulator();
|
|
||||||
let stack_ptr: GuestAddr = emu.read_reg(Regs::Esp).unwrap();
|
|
||||||
let mut ret_addr = [0; 4];
|
|
||||||
unsafe { emu.read_mem(stack_ptr, &mut ret_addr) };
|
|
||||||
GuestAddr::from_le_bytes(ret_addr)
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(any(cpu_target = "arm", cpu_target = "aarch64"))]
|
|
||||||
let ret_addr = {
|
|
||||||
let emu = hooks.emulator();
|
|
||||||
let ret_addr: GuestAddr = emu.read_reg(Regs::Lr).unwrap();
|
|
||||||
ret_addr
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(cpu_target = "mips")]
|
|
||||||
let ret_addr = {
|
|
||||||
let emu = hooks.emulator();
|
|
||||||
let ret_addr: GuestAddr = emu.read_reg(Regs::Ra).unwrap();
|
|
||||||
ret_addr
|
|
||||||
};
|
|
||||||
|
|
||||||
// log::info!("RET @ 0x{:#x}", ret_addr);
|
|
||||||
|
|
||||||
if let Some(h) = hooks
|
|
||||||
.helpers_mut()
|
|
||||||
.match_first_type_mut::<QemuCallTracerHelper>()
|
|
||||||
{
|
{
|
||||||
while let Some(addr) = h.callstack.pop() {
|
let observer = observers
|
||||||
if addr == ret_addr {
|
.match_name_mut::<BacktraceObserver<'_>>(&self.observer_name)
|
||||||
break;
|
.expect("A OnCrashBacktraceCollector needs a BacktraceObserver");
|
||||||
}
|
observer.fill_external(self.callstack_hash, exit_kind);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gen_blocks_calls<QT, S>(
|
|
||||||
hooks: &mut QemuHooks<'_, QT, S>,
|
|
||||||
_state: Option<&mut S>,
|
|
||||||
pc: GuestAddr,
|
|
||||||
) -> Option<u64>
|
|
||||||
where
|
|
||||||
S: UsesInput,
|
|
||||||
QT: QemuHelperTuple<S>,
|
|
||||||
{
|
|
||||||
let emu = hooks.emulator();
|
|
||||||
if let Some(h) = hooks.helpers().match_first_type::<QemuCallTracerHelper>() {
|
|
||||||
if !h.must_instrument(pc) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut code = {
|
|
||||||
#[cfg(emulation_mode = "usermode")]
|
|
||||||
unsafe {
|
|
||||||
std::slice::from_raw_parts(emu.g2h(pc), 512)
|
|
||||||
}
|
|
||||||
#[cfg(emulation_mode = "systemmode")]
|
|
||||||
&mut [0; 512]
|
|
||||||
};
|
|
||||||
#[cfg(emulation_mode = "systemmode")]
|
|
||||||
unsafe {
|
|
||||||
emu.read_mem(pc, code)
|
|
||||||
}; // TODO handle faults
|
|
||||||
|
|
||||||
let mut iaddr = pc;
|
|
||||||
|
|
||||||
'disasm: while let Ok(insns) = h.cs.disasm_count(code, iaddr.into(), 1) {
|
|
||||||
if insns.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let insn = insns.first().unwrap();
|
|
||||||
let insn_detail: InsnDetail = h.cs.insn_detail(insn).unwrap();
|
|
||||||
for detail in insn_detail.groups() {
|
|
||||||
match u32::from(detail.0) {
|
|
||||||
capstone::InsnGroupType::CS_GRP_CALL => {
|
|
||||||
// hooks.instruction_closure(insn.address() as GuestAddr, on_call, false);
|
|
||||||
let call_len = insn.bytes().len() as GuestAddr;
|
|
||||||
let call_cb = move |hooks: &mut QemuHooks<'_, QT, S>, _, pc| {
|
|
||||||
// eprintln!("CALL @ 0x{:#x}", pc + call_len);
|
|
||||||
if let Some(h) = hooks
|
|
||||||
.helpers_mut()
|
|
||||||
.match_first_type_mut::<QemuCallTracerHelper>()
|
|
||||||
{
|
|
||||||
h.callstack.push(pc + call_len);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
hooks.instruction_closure(
|
|
||||||
insn.address() as GuestAddr,
|
|
||||||
Box::new(call_cb),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
capstone::InsnGroupType::CS_GRP_RET => {
|
|
||||||
hooks.instruction(insn.address() as GuestAddr, on_ret, false);
|
|
||||||
break 'disasm;
|
|
||||||
}
|
|
||||||
capstone::InsnGroupType::CS_GRP_INVALID
|
|
||||||
| capstone::InsnGroupType::CS_GRP_JUMP
|
|
||||||
| capstone::InsnGroupType::CS_GRP_IRET
|
|
||||||
| capstone::InsnGroupType::CS_GRP_PRIVILEGE => {
|
|
||||||
break 'disasm;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iaddr += insn.bytes().len() as GuestAddr;
|
|
||||||
|
|
||||||
#[cfg(emulation_mode = "usermode")]
|
|
||||||
unsafe {
|
|
||||||
code = std::slice::from_raw_parts(emu.g2h(iaddr), 512);
|
|
||||||
}
|
|
||||||
#[cfg(emulation_mode = "systemmode")]
|
|
||||||
unsafe {
|
|
||||||
emu.read_mem(pc, code);
|
|
||||||
} // TODO handle faults
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use std::{path::PathBuf, sync::Mutex};
|
use std::{path::PathBuf, sync::Mutex};
|
||||||
|
|
||||||
use hashbrown::{hash_map::Entry, HashMap};
|
use hashbrown::{hash_map::Entry, HashMap};
|
||||||
use libafl::{executors::ExitKind, inputs::UsesInput, state::HasMetadata};
|
use libafl::{
|
||||||
|
executors::ExitKind, inputs::UsesInput, observers::ObserversTuple, state::HasMetadata,
|
||||||
|
};
|
||||||
use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter};
|
use libafl_targets::drcov::{DrCovBasicBlock, DrCovWriter};
|
||||||
use rangemap::RangeMap;
|
use rangemap::RangeMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -84,7 +86,15 @@ where
|
|||||||
|
|
||||||
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {}
|
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {}
|
||||||
|
|
||||||
fn post_exec(&mut self, emulator: &Emulator, _input: &S::Input, _exit_kind: &mut ExitKind) {
|
fn post_exec<OT>(
|
||||||
|
&mut self,
|
||||||
|
emulator: &Emulator,
|
||||||
|
_input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
|
_exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
{
|
||||||
if self.full_trace {
|
if self.full_trace {
|
||||||
if DRCOV_IDS.lock().unwrap().as_ref().unwrap().len() > self.drcov_len {
|
if DRCOV_IDS.lock().unwrap().as_ref().unwrap().len() > self.drcov_len {
|
||||||
let mut drcov_vec = Vec::<DrCovBasicBlock>::new();
|
let mut drcov_vec = Vec::<DrCovBasicBlock>::new();
|
||||||
|
@ -463,7 +463,7 @@ impl Drop for GuestMaps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) struct FatPtr(pub *const c_void, pub *const c_void);
|
pub(crate) struct FatPtr(pub *const c_void, pub *const c_void);
|
||||||
|
|
||||||
static mut GDB_COMMANDS: Vec<FatPtr> = vec![];
|
static mut GDB_COMMANDS: Vec<FatPtr> = vec![];
|
||||||
|
@ -126,9 +126,12 @@ where
|
|||||||
}
|
}
|
||||||
self.hooks.helpers_mut().pre_exec_all(&emu, input);
|
self.hooks.helpers_mut().pre_exec_all(&emu, input);
|
||||||
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
|
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
|
||||||
self.hooks
|
self.hooks.helpers_mut().post_exec_all(
|
||||||
.helpers_mut()
|
&emu,
|
||||||
.post_exec_all(&emu, input, &mut exit_kind);
|
input,
|
||||||
|
self.inner.observers_mut(),
|
||||||
|
&mut exit_kind,
|
||||||
|
);
|
||||||
Ok(exit_kind)
|
Ok(exit_kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,9 +291,12 @@ where
|
|||||||
}
|
}
|
||||||
self.hooks.helpers_mut().pre_exec_all(&emu, input);
|
self.hooks.helpers_mut().pre_exec_all(&emu, input);
|
||||||
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
|
let mut exit_kind = self.inner.run_target(fuzzer, state, mgr, input)?;
|
||||||
self.hooks
|
self.hooks.helpers_mut().post_exec_all(
|
||||||
.helpers_mut()
|
&emu,
|
||||||
.post_exec_all(&emu, input, &mut exit_kind);
|
input,
|
||||||
|
self.inner.observers_mut(),
|
||||||
|
&mut exit_kind,
|
||||||
|
);
|
||||||
Ok(exit_kind)
|
Ok(exit_kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use core::{fmt::Debug, ops::Range};
|
use core::{fmt::Debug, ops::Range};
|
||||||
|
|
||||||
use libafl::{bolts::tuples::MatchFirstType, executors::ExitKind, inputs::UsesInput};
|
use libafl::{
|
||||||
|
bolts::tuples::MatchFirstType, executors::ExitKind, inputs::UsesInput,
|
||||||
|
observers::ObserversTuple,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emu::{Emulator, GuestAddr},
|
emu::{Emulator, GuestAddr},
|
||||||
@ -29,7 +32,16 @@ where
|
|||||||
|
|
||||||
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {}
|
fn pre_exec(&mut self, _emulator: &Emulator, _input: &S::Input) {}
|
||||||
|
|
||||||
fn post_exec(&mut self, _emulator: &Emulator, _input: &S::Input, _exit_kind: &mut ExitKind) {}
|
fn post_exec<OT>(
|
||||||
|
&mut self,
|
||||||
|
_emulator: &Emulator,
|
||||||
|
_input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
|
_exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait QemuHelperTuple<S>: MatchFirstType + Debug
|
pub trait QemuHelperTuple<S>: MatchFirstType + Debug
|
||||||
@ -48,7 +60,14 @@ where
|
|||||||
|
|
||||||
fn pre_exec_all(&mut self, _emulator: &Emulator, input: &S::Input);
|
fn pre_exec_all(&mut self, _emulator: &Emulator, input: &S::Input);
|
||||||
|
|
||||||
fn post_exec_all(&mut self, _emulator: &Emulator, input: &S::Input, _exit_kind: &mut ExitKind);
|
fn post_exec_all<OT>(
|
||||||
|
&mut self,
|
||||||
|
_emulator: &Emulator,
|
||||||
|
input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
|
_exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> QemuHelperTuple<S> for ()
|
impl<S> QemuHelperTuple<S> for ()
|
||||||
@ -71,12 +90,15 @@ where
|
|||||||
|
|
||||||
fn pre_exec_all(&mut self, _emulator: &Emulator, _input: &S::Input) {}
|
fn pre_exec_all(&mut self, _emulator: &Emulator, _input: &S::Input) {}
|
||||||
|
|
||||||
fn post_exec_all(
|
fn post_exec_all<OT>(
|
||||||
&mut self,
|
&mut self,
|
||||||
_emulator: &Emulator,
|
_emulator: &Emulator,
|
||||||
_input: &S::Input,
|
_input: &S::Input,
|
||||||
|
_observers: &mut OT,
|
||||||
_exit_kind: &mut ExitKind,
|
_exit_kind: &mut ExitKind,
|
||||||
) {
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,9 +131,17 @@ where
|
|||||||
self.1.pre_exec_all(emulator, input);
|
self.1.pre_exec_all(emulator, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_exec_all(&mut self, emulator: &Emulator, input: &S::Input, exit_kind: &mut ExitKind) {
|
fn post_exec_all<OT>(
|
||||||
self.0.post_exec(emulator, input, exit_kind);
|
&mut self,
|
||||||
self.1.post_exec_all(emulator, input, exit_kind);
|
emulator: &Emulator,
|
||||||
|
input: &S::Input,
|
||||||
|
observers: &mut OT,
|
||||||
|
exit_kind: &mut ExitKind,
|
||||||
|
) where
|
||||||
|
OT: ObserversTuple<S>,
|
||||||
|
{
|
||||||
|
self.0.post_exec(emulator, input, observers, exit_kind);
|
||||||
|
self.1.post_exec_all(emulator, input, observers, exit_kind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
// all kinds of hooks
|
// all kinds of hooks
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
enum Hook {
|
pub(crate) enum Hook {
|
||||||
Function(*const c_void),
|
Function(*const c_void),
|
||||||
Closure(FatPtr),
|
Closure(FatPtr),
|
||||||
#[cfg(emulation_mode = "usermode")]
|
#[cfg(emulation_mode = "usermode")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user