libafl_frida: Add FridaInstrumentationHelperBuilder, don't rely on Clap options (#1523)

* impr(frida): Don't keep FuzzerOptions in Helper

Instead, keep the actual values that are needed. This allows us to make
a builder for FridaInstrumentationBuilder in a subsequent commit.

* refactor(frida): Move workaround to separate method

This is just code movement.

* refactor(frida): move transformer initialization

Mostly code movement here, sets up replacing `new` with a builder. The
one exception is the introduction of a lifetime bound on RT, which needs
to outlive the transformer. This could be generic, but there's probably
no reason to introduce an additional lifetime.

However, because of this lifetime introduction, this is _technically_ a
breaking change.

* impr(frida): Pass module map to runtimes

Instead of passing a slice of modules to instrument, and re-building the
modulemap, pass a Ref-counted module map directly to the initialization.

* feat(frida): Builder for InstrumentationHelper

Co-authored-by: Dominik Maier <domenukk@gmail.com>

* impr(frida/alloc): optional options in allocator

Move all the initialization into Default::default with sensible defaults
and override parameters set from options in new.

* impr(frida): remove options from AsanError

The only option AsanError uses is whether to continue on error. Instead
of keeping a whole clone of the options around, just store that single
boolean value.

* impr(frida/asan): Use less FuzzerOptions

* Implement Default::default to get a good default AsanRuntime

---------

Co-authored-by: Dominik Maier <domenukk@gmail.com>
This commit is contained in:
Fabian Freyer 2023-09-20 11:08:59 +02:00 committed by GitHub
parent fdd2f53871
commit 7f0a4f1d7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 700 additions and 412 deletions

View File

@ -104,7 +104,7 @@ unsafe fn fuzz(
let coverage = CoverageRuntime::new(); let coverage = CoverageRuntime::new();
#[cfg(unix)] #[cfg(unix)]
let asan = AsanRuntime::new(options.clone()); let asan = AsanRuntime::new(&options);
#[cfg(unix)] #[cfg(unix)]
let mut frida_helper = let mut frida_helper =

View File

@ -99,7 +99,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let coverage = CoverageRuntime::new(); let coverage = CoverageRuntime::new();
#[cfg(unix)] #[cfg(unix)]
let asan = AsanRuntime::new(options.clone()); let asan = AsanRuntime::new(&options);
#[cfg(unix)] #[cfg(unix)]
let mut frida_helper = let mut frida_helper =

View File

@ -94,7 +94,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let coverage = CoverageRuntime::new(); let coverage = CoverageRuntime::new();
#[cfg(unix)] #[cfg(unix)]
let asan = AsanRuntime::new(options.clone()); let asan = AsanRuntime::new(&options);
#[cfg(unix)] #[cfg(unix)]
let mut frida_helper = let mut frida_helper =

View File

@ -28,9 +28,10 @@ use crate::asan::errors::{AsanError, AsanErrors};
/// An allocator wrapper with binary-only address sanitization /// An allocator wrapper with binary-only address sanitization
#[derive(Debug)] #[derive(Debug)]
pub struct Allocator { pub struct Allocator {
/// The fuzzer options max_allocation: usize,
#[allow(dead_code)] max_total_allocation: usize,
options: FuzzerOptions, max_allocation_panics: bool,
allocation_backtraces: bool,
/// The page size /// The page size
page_size: usize, page_size: usize,
/// The shadow offsets /// The shadow offsets
@ -104,130 +105,13 @@ impl Allocator {
all(target_arch = "aarch64", target_os = "android") all(target_arch = "aarch64", target_os = "android")
))] ))]
#[must_use] #[must_use]
#[allow(clippy::too_many_lines)] pub fn new(options: &FuzzerOptions) -> Self {
pub fn new(options: FuzzerOptions) -> Self {
let ret = unsafe { sysconf(_SC_PAGESIZE) };
assert!(
ret >= 0,
"Failed to read pagesize {:?}",
io::Error::last_os_error()
);
#[allow(clippy::cast_sign_loss)]
let page_size = ret as usize;
// probe to find a usable shadow bit:
let mut shadow_bit = 0;
let mut occupied_ranges: Vec<(usize, usize)> = vec![];
// max(userspace address) this is usually 0x8_0000_0000_0000 - 1 on x64 linux.
let mut userspace_max: usize = 0;
// Enumerate memory ranges that are already occupied.
for prot in [
PageProtection::Read,
PageProtection::Write,
PageProtection::Execute,
] {
RangeDetails::enumerate_with_prot(prot, &mut |details| {
let start = details.memory_range().base_address().0 as usize;
let end = start + details.memory_range().size();
occupied_ranges.push((start, end));
// log::trace!("{:x} {:x}", start, end);
let base: usize = 2;
// On x64, if end > 2**48, then that's in vsyscall or something.
#[cfg(target_arch = "x86_64")]
if end <= base.pow(48) && end > userspace_max {
userspace_max = end;
}
// On x64, if end > 2**52, then range is not in userspace
#[cfg(target_arch = "aarch64")]
if end <= base.pow(52) && end > userspace_max {
userspace_max = end;
}
true
});
}
let mut maxbit = 0;
for power in 1..64 {
let base: usize = 2;
if base.pow(power) > userspace_max {
maxbit = power;
break;
}
}
{
for try_shadow_bit in &[maxbit - 4, maxbit - 3, maxbit - 2] {
let addr: usize = 1 << try_shadow_bit;
let shadow_start = addr;
let shadow_end = addr + addr + addr;
// check if the proposed shadow bit overlaps with occupied ranges.
for (start, end) in &occupied_ranges {
if (shadow_start <= *end) && (*start <= shadow_end) {
// log::trace!("{:x} {:x}, {:x} {:x}",shadow_start,shadow_end,start,end);
log::warn!("shadow_bit {try_shadow_bit:x} is not suitable");
break;
}
}
if unsafe {
mmap(
NonZeroUsize::new(addr),
NonZeroUsize::new_unchecked(page_size),
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_PRIVATE
| ANONYMOUS_FLAG
| MapFlags::MAP_FIXED
| MapFlags::MAP_NORESERVE,
-1,
0,
)
}
.is_ok()
{
shadow_bit = (*try_shadow_bit).try_into().unwrap();
break;
}
}
}
log::warn!("shadow_bit {shadow_bit:x} is suitable");
assert!(shadow_bit != 0);
// attempt to pre-map the entire shadow-memory space
let addr: usize = 1 << shadow_bit;
let pre_allocated_shadow = unsafe {
mmap(
NonZeroUsize::new(addr),
NonZeroUsize::new_unchecked(addr + addr),
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
ANONYMOUS_FLAG
| MapFlags::MAP_FIXED
| MapFlags::MAP_PRIVATE
| MapFlags::MAP_NORESERVE,
-1,
0,
)
}
.is_ok();
Self { Self {
options, max_allocation: options.max_allocation,
page_size, max_allocation_panics: options.max_allocation_panics,
pre_allocated_shadow, max_total_allocation: options.max_total_allocation,
shadow_offset: 1 << shadow_bit, allocation_backtraces: options.allocation_backtraces,
shadow_bit, ..Self::default()
allocations: HashMap::new(),
shadow_pages: RangeSet::new(),
allocation_queue: BTreeMap::new(),
largest_allocation: 0,
total_allocation_size: 0,
base_mapping_addr: addr + addr + addr,
current_mapping_addr: addr + addr + addr,
} }
} }
@ -272,9 +156,9 @@ impl Allocator {
} else { } else {
size size
}; };
if size > self.options.max_allocation { if size > self.max_allocation {
#[allow(clippy::manual_assert)] #[allow(clippy::manual_assert)]
if self.options.max_allocation_panics { if self.max_allocation_panics {
panic!("ASAN: Allocation is too large: 0x{size:x}"); panic!("ASAN: Allocation is too large: 0x{size:x}");
} }
@ -282,7 +166,7 @@ impl Allocator {
} }
let rounded_up_size = self.round_up_to_page(size) + 2 * self.page_size; let rounded_up_size = self.round_up_to_page(size) + 2 * self.page_size;
if self.total_allocation_size + rounded_up_size > self.options.max_total_allocation { if self.total_allocation_size + rounded_up_size > self.max_total_allocation {
return std::ptr::null_mut(); return std::ptr::null_mut();
} }
self.total_allocation_size += rounded_up_size; self.total_allocation_size += rounded_up_size;
@ -291,7 +175,7 @@ impl Allocator {
//log::trace!("reusing allocation at {:x}, (actual mapping starts at {:x}) size {:x}", metadata.address, metadata.address - self.page_size, size); //log::trace!("reusing allocation at {:x}, (actual mapping starts at {:x}) size {:x}", metadata.address, metadata.address - self.page_size, size);
metadata.is_malloc_zero = is_malloc_zero; metadata.is_malloc_zero = is_malloc_zero;
metadata.size = size; metadata.size = size;
if self.options.allocation_backtraces { if self.allocation_backtraces {
metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved()); metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved());
} }
metadata metadata
@ -324,7 +208,7 @@ impl Allocator {
actual_size: rounded_up_size, actual_size: rounded_up_size,
..AllocationMetadata::default() ..AllocationMetadata::default()
}; };
if self.options.allocation_backtraces { if self.allocation_backtraces {
metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved()); metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved());
} }
@ -367,7 +251,7 @@ impl Allocator {
let shadow_mapping_start = map_to_shadow!(self, ptr as usize); let shadow_mapping_start = map_to_shadow!(self, ptr as usize);
metadata.freed = true; metadata.freed = true;
if self.options.allocation_backtraces { if self.allocation_backtraces {
metadata.release_site_backtrace = Some(Backtrace::new_unresolved()); metadata.release_site_backtrace = Some(Backtrace::new_unresolved());
} }
@ -563,3 +447,145 @@ impl Allocator {
}); });
} }
} }
impl Default for Allocator {
/// Creates a new [`Allocator`] (not supported on this platform!)
#[cfg(not(any(
target_os = "linux",
target_vendor = "apple",
all(target_arch = "aarch64", target_os = "android")
)))]
fn default() -> Self {
todo!("Shadow region not yet supported for this platform!");
}
#[allow(clippy::too_many_lines)]
fn default() -> Self {
let ret = unsafe { sysconf(_SC_PAGESIZE) };
assert!(
ret >= 0,
"Failed to read pagesize {:?}",
io::Error::last_os_error()
);
#[allow(clippy::cast_sign_loss)]
let page_size = ret as usize;
// probe to find a usable shadow bit:
let mut shadow_bit = 0;
let mut occupied_ranges: Vec<(usize, usize)> = vec![];
// max(userspace address) this is usually 0x8_0000_0000_0000 - 1 on x64 linux.
let mut userspace_max: usize = 0;
// Enumerate memory ranges that are already occupied.
for prot in [
PageProtection::Read,
PageProtection::Write,
PageProtection::Execute,
] {
RangeDetails::enumerate_with_prot(prot, &mut |details| {
let start = details.memory_range().base_address().0 as usize;
let end = start + details.memory_range().size();
occupied_ranges.push((start, end));
// log::trace!("{:x} {:x}", start, end);
let base: usize = 2;
// On x64, if end > 2**48, then that's in vsyscall or something.
#[cfg(target_arch = "x86_64")]
if end <= base.pow(48) && end > userspace_max {
userspace_max = end;
}
// On x64, if end > 2**52, then range is not in userspace
#[cfg(target_arch = "aarch64")]
if end <= base.pow(52) && end > userspace_max {
userspace_max = end;
}
true
});
}
let mut maxbit = 0;
for power in 1..64 {
let base: usize = 2;
if base.pow(power) > userspace_max {
maxbit = power;
break;
}
}
{
for try_shadow_bit in &[maxbit - 4, maxbit - 3, maxbit - 2] {
let addr: usize = 1 << try_shadow_bit;
let shadow_start = addr;
let shadow_end = addr + addr + addr;
// check if the proposed shadow bit overlaps with occupied ranges.
for (start, end) in &occupied_ranges {
if (shadow_start <= *end) && (*start <= shadow_end) {
// log::trace!("{:x} {:x}, {:x} {:x}",shadow_start,shadow_end,start,end);
log::warn!("shadow_bit {try_shadow_bit:x} is not suitable");
break;
}
}
if unsafe {
mmap(
NonZeroUsize::new(addr),
NonZeroUsize::new_unchecked(page_size),
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
MapFlags::MAP_PRIVATE
| ANONYMOUS_FLAG
| MapFlags::MAP_FIXED
| MapFlags::MAP_NORESERVE,
-1,
0,
)
}
.is_ok()
{
shadow_bit = (*try_shadow_bit).try_into().unwrap();
break;
}
}
}
log::warn!("shadow_bit {shadow_bit:x} is suitable");
assert!(shadow_bit != 0);
// attempt to pre-map the entire shadow-memory space
let addr: usize = 1 << shadow_bit;
let pre_allocated_shadow = unsafe {
mmap(
NonZeroUsize::new(addr),
NonZeroUsize::new_unchecked(addr + addr),
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
ANONYMOUS_FLAG
| MapFlags::MAP_FIXED
| MapFlags::MAP_PRIVATE
| MapFlags::MAP_NORESERVE,
-1,
0,
)
}
.is_ok();
Self {
max_allocation: 1 << 30,
max_allocation_panics: false,
max_total_allocation: 1 << 32,
allocation_backtraces: false,
page_size,
pre_allocated_shadow,
shadow_offset: 1 << shadow_bit,
shadow_bit,
allocations: HashMap::new(),
shadow_pages: RangeSet::new(),
allocation_queue: BTreeMap::new(),
largest_allocation: 0,
total_allocation_size: 0,
base_mapping_addr: addr + addr + addr,
current_mapping_addr: addr + addr + addr,
}
}
}

View File

@ -10,7 +10,7 @@ use core::{
fmt::{self, Debug, Formatter}, fmt::{self, Debug, Formatter},
ptr::addr_of_mut, ptr::addr_of_mut,
}; };
use std::{ffi::c_void, num::NonZeroUsize, ptr::write_volatile}; use std::{ffi::c_void, num::NonZeroUsize, ptr::write_volatile, rc::Rc};
use backtrace::Backtrace; use backtrace::Backtrace;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
@ -57,7 +57,7 @@ use crate::utils::instruction_width;
use crate::{ use crate::{
alloc::Allocator, alloc::Allocator,
asan::errors::{AsanError, AsanErrors, AsanReadWriteError, ASAN_ERRORS}, asan::errors::{AsanError, AsanErrors, AsanReadWriteError, ASAN_ERRORS},
helper::FridaRuntime, helper::{FridaRuntime, SkipRange},
utils::writer_register, utils::writer_register,
}; };
@ -139,9 +139,10 @@ pub struct AsanRuntime {
blob_check_mem_48bytes: Option<Box<[u8]>>, blob_check_mem_48bytes: Option<Box<[u8]>>,
blob_check_mem_64bytes: Option<Box<[u8]>>, blob_check_mem_64bytes: Option<Box<[u8]>>,
stalked_addresses: HashMap<usize, usize>, stalked_addresses: HashMap<usize, usize>,
options: FuzzerOptions, module_map: Option<Rc<ModuleMap>>,
module_map: Option<ModuleMap>,
suppressed_addresses: Vec<usize>, suppressed_addresses: Vec<usize>,
skip_ranges: Vec<SkipRange>,
continue_on_error: bool,
shadow_check_func: Option<extern "C" fn(*const c_void, usize) -> bool>, shadow_check_func: Option<extern "C" fn(*const c_void, usize) -> bool>,
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
@ -152,8 +153,9 @@ impl Debug for AsanRuntime {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("AsanRuntime") f.debug_struct("AsanRuntime")
.field("stalked_addresses", &self.stalked_addresses) .field("stalked_addresses", &self.stalked_addresses)
.field("options", &self.options) .field("continue_on_error", &self.continue_on_error)
.field("module_map", &"<ModuleMap>") .field("module_map", &"<ModuleMap>")
.field("skip_ranges", &self.skip_ranges)
.field("suppressed_addresses", &self.suppressed_addresses) .field("suppressed_addresses", &self.suppressed_addresses)
.finish_non_exhaustive() .finish_non_exhaustive()
} }
@ -167,10 +169,10 @@ impl FridaRuntime for AsanRuntime {
&mut self, &mut self,
gum: &Gum, gum: &Gum,
_ranges: &RangeMap<usize, (u16, String)>, _ranges: &RangeMap<usize, (u16, String)>,
modules_to_instrument: &[&str], module_map: &Rc<ModuleMap>,
) { ) {
unsafe { unsafe {
ASAN_ERRORS = Some(AsanErrors::new(self.options.clone())); ASAN_ERRORS = Some(AsanErrors::new(self.continue_on_error));
} }
self.generate_instrumentation_blobs(); self.generate_instrumentation_blobs();
@ -178,14 +180,16 @@ impl FridaRuntime for AsanRuntime {
self.generate_shadow_check_function(); self.generate_shadow_check_function();
self.unpoison_all_existing_memory(); self.unpoison_all_existing_memory();
self.module_map = Some(ModuleMap::new_from_names(gum, modules_to_instrument)); self.module_map = Some(module_map.clone());
if !self.options.dont_instrument.is_empty() { self.suppressed_addresses
for (module_name, offset) in self.options.dont_instrument.clone() { .extend(self.skip_ranges.iter().map(|skip| match skip {
let module_details = ModuleDetails::with_name(module_name).unwrap(); SkipRange::Absolute(range) => range.start,
SkipRange::ModuleRelative { name, range } => {
let module_details = ModuleDetails::with_name(name.clone()).unwrap();
let lib_start = module_details.range().base_address().0 as usize; let lib_start = module_details.range().base_address().0 as usize;
self.suppressed_addresses.push(lib_start + offset); lib_start + range.start
}
} }
}));
self.hook_functions(gum); self.hook_functions(gum);
/* /*
@ -295,33 +299,22 @@ impl FridaRuntime for AsanRuntime {
impl AsanRuntime { impl AsanRuntime {
/// Create a new `AsanRuntime` /// Create a new `AsanRuntime`
#[must_use] #[must_use]
pub fn new(options: FuzzerOptions) -> AsanRuntime { pub fn new(options: &FuzzerOptions) -> AsanRuntime {
let skip_ranges = options
.dont_instrument
.iter()
.map(|(name, offset)| SkipRange::ModuleRelative {
name: name.clone(),
range: *offset..*offset + 4,
})
.collect();
let continue_on_error = options.continue_on_error;
Self { Self {
check_for_leaks_enabled: options.detect_leaks, check_for_leaks_enabled: options.detect_leaks,
current_report_impl: 0, allocator: Allocator::new(options),
allocator: Allocator::new(options.clone()), skip_ranges,
regs: [0; ASAN_SAVE_REGISTER_COUNT], continue_on_error,
blob_report: None, ..Self::default()
blob_check_mem_byte: None,
blob_check_mem_halfword: None,
blob_check_mem_dword: None,
blob_check_mem_qword: None,
blob_check_mem_16bytes: None,
blob_check_mem_3bytes: None,
blob_check_mem_6bytes: None,
blob_check_mem_12bytes: None,
blob_check_mem_24bytes: None,
blob_check_mem_32bytes: None,
blob_check_mem_48bytes: None,
blob_check_mem_64bytes: None,
stalked_addresses: HashMap::new(),
options,
module_map: None,
suppressed_addresses: Vec::new(),
shadow_check_func: None,
#[cfg(target_arch = "aarch64")]
eh_frame: [0; ASAN_EH_FRAME_DWORD_COUNT],
} }
} }
@ -2716,3 +2709,35 @@ impl AsanRuntime {
)); ));
} }
} }
impl Default for AsanRuntime {
fn default() -> Self {
Self {
check_for_leaks_enabled: false,
current_report_impl: 0,
allocator: Allocator::default(),
regs: [0; ASAN_SAVE_REGISTER_COUNT],
blob_report: None,
blob_check_mem_byte: None,
blob_check_mem_halfword: None,
blob_check_mem_dword: None,
blob_check_mem_qword: None,
blob_check_mem_16bytes: None,
blob_check_mem_3bytes: None,
blob_check_mem_6bytes: None,
blob_check_mem_12bytes: None,
blob_check_mem_24bytes: None,
blob_check_mem_32bytes: None,
blob_check_mem_48bytes: None,
blob_check_mem_64bytes: None,
stalked_addresses: HashMap::new(),
module_map: None,
suppressed_addresses: Vec::new(),
skip_ranges: Vec::new(),
continue_on_error: false,
shadow_check_func: None,
#[cfg(target_arch = "aarch64")]
eh_frame: [0; ASAN_EH_FRAME_DWORD_COUNT],
}
}
}

View File

@ -17,7 +17,7 @@ use libafl::{
state::{HasClientPerfMonitor, HasMetadata}, state::{HasClientPerfMonitor, HasMetadata},
Error, Error,
}; };
use libafl_bolts::{cli::FuzzerOptions, ownedref::OwnedPtr, Named, SerdeAny}; use libafl_bolts::{ownedref::OwnedPtr, Named, SerdeAny};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use termcolor::{Color, ColorSpec, WriteColor}; use termcolor::{Color, ColorSpec, WriteColor};
@ -95,17 +95,17 @@ impl AsanError {
#[allow(clippy::unsafe_derive_deserialize)] #[allow(clippy::unsafe_derive_deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)] #[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)]
pub struct AsanErrors { pub struct AsanErrors {
options: FuzzerOptions, continue_on_error: bool,
errors: Vec<AsanError>, errors: Vec<AsanError>,
} }
impl AsanErrors { impl AsanErrors {
/// Creates a new `AsanErrors` struct /// Creates a new `AsanErrors` struct
#[must_use] #[must_use]
pub fn new(options: FuzzerOptions) -> Self { pub fn new(continue_on_error: bool) -> Self {
Self { Self {
options,
errors: Vec::new(), errors: Vec::new(),
continue_on_error,
} }
} }
@ -529,7 +529,7 @@ impl AsanErrors {
}; };
#[allow(clippy::manual_assert)] #[allow(clippy::manual_assert)]
if !self.options.continue_on_error { if !self.continue_on_error {
panic!("ASAN: Crashing target!"); panic!("ASAN: Crashing target!");
} }
} }

View File

@ -19,6 +19,9 @@ extern "C" {
pub fn __libafl_targets_cmplog_instructions(k: u64, shape: u8, arg1: u64, arg2: u64); pub fn __libafl_targets_cmplog_instructions(k: u64, shape: u8, arg1: u64, arg2: u64);
} }
use frida_gum::ModuleMap;
use std::rc::Rc;
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use frida_gum::{ use frida_gum::{
instruction_writer::{Aarch64Register, IndexMode, InstructionWriter}, instruction_writer::{Aarch64Register, IndexMode, InstructionWriter},
@ -90,7 +93,7 @@ impl FridaRuntime for CmpLogRuntime {
&mut self, &mut self,
_gum: &frida_gum::Gum, _gum: &frida_gum::Gum,
_ranges: &RangeMap<usize, (u16, String)>, _ranges: &RangeMap<usize, (u16, String)>,
_modules_to_instrument: &[&str], _module_map: &Rc<ModuleMap>,
) { ) {
self.generate_instrumentation_blobs(); self.generate_instrumentation_blobs();
} }

View File

@ -5,7 +5,7 @@ use std::{cell::RefCell, marker::PhantomPinned, pin::Pin, rc::Rc};
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use dynasmrt::DynasmLabelApi; use dynasmrt::DynasmLabelApi;
use dynasmrt::{dynasm, DynasmApi}; use dynasmrt::{dynasm, DynasmApi};
use frida_gum::{instruction_writer::InstructionWriter, stalker::StalkerOutput}; use frida_gum::{instruction_writer::InstructionWriter, stalker::StalkerOutput, ModuleMap};
use libafl_bolts::math::xxh3_rrmxmx_mixer; use libafl_bolts::math::xxh3_rrmxmx_mixer;
use rangemap::RangeMap; use rangemap::RangeMap;
@ -38,7 +38,7 @@ impl FridaRuntime for CoverageRuntime {
&mut self, &mut self,
_gum: &frida_gum::Gum, _gum: &frida_gum::Gum,
_ranges: &RangeMap<usize, (u16, String)>, _ranges: &RangeMap<usize, (u16, String)>,
_modules_to_instrument: &[&str], _module_map: &Rc<ModuleMap>,
) { ) {
} }

View File

@ -2,9 +2,11 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
hash::{BuildHasher, Hasher}, hash::{BuildHasher, Hasher},
rc::Rc,
}; };
use ahash::RandomState; use ahash::RandomState;
use frida_gum::ModuleMap;
use libafl::{ use libafl::{
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
Error, Error,
@ -31,7 +33,7 @@ impl FridaRuntime for DrCovRuntime {
&mut self, &mut self,
_gum: &frida_gum::Gum, _gum: &frida_gum::Gum,
ranges: &RangeMap<usize, (u16, String)>, ranges: &RangeMap<usize, (u16, String)>,
_modules_to_instrument: &[&str], _module_map: &Rc<ModuleMap>,
) { ) {
self.ranges = ranges.clone(); self.ranges = ranges.clone();
std::fs::create_dir_all("./coverage") std::fs::create_dir_all("./coverage")

View File

@ -202,7 +202,7 @@ where
} }
} }
if !helper.options().disable_excludes { if !helper.disable_excludes {
for range in ranges.gaps(&(0..usize::MAX)) { for range in ranges.gaps(&(0..usize::MAX)) {
log::info!("excluding range: {:x}-{:x}", range.start, range.end); log::info!("excluding range: {:x}-{:x}", range.start, range.end);
stalker.exclude(&MemoryRange::new( stalker.exclude(&MemoryRange::new(

View File

@ -1,6 +1,8 @@
use core::fmt::{self, Debug, Formatter}; use core::fmt::{self, Debug, Formatter};
use std::{ use std::{
cell::{Ref, RefCell, RefMut}, cell::{Ref, RefCell, RefMut},
fs,
path::{Path, PathBuf},
rc::Rc, rc::Rc,
}; };
@ -13,7 +15,10 @@ use capstone::{
use frida_gum::instruction_writer::InstructionWriter; use frida_gum::instruction_writer::InstructionWriter;
#[cfg(unix)] #[cfg(unix)]
use frida_gum::CpuContext; use frida_gum::CpuContext;
use frida_gum::{stalker::Transformer, Gum, Module, ModuleDetails, ModuleMap, PageProtection}; use frida_gum::{
stalker::{StalkerIterator, StalkerOutput, Transformer},
Gum, Module, ModuleDetails, ModuleMap, PageProtection,
};
use libafl::{ use libafl::{
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
Error, Error,
@ -43,7 +48,7 @@ pub trait FridaRuntime: 'static + Debug {
&mut self, &mut self,
gum: &Gum, gum: &Gum,
ranges: &RangeMap<usize, (u16, String)>, ranges: &RangeMap<usize, (u16, String)>,
modules_to_instrument: &[&str], module_map: &Rc<ModuleMap>,
); );
/// Method called before execution /// Method called before execution
@ -60,7 +65,7 @@ pub trait FridaRuntimeTuple: MatchFirstType + Debug {
&mut self, &mut self,
gum: &Gum, gum: &Gum,
ranges: &RangeMap<usize, (u16, String)>, ranges: &RangeMap<usize, (u16, String)>,
modules_to_instrument: &[&str], module_map: &Rc<ModuleMap>,
); );
/// Method called before execution /// Method called before execution
@ -75,7 +80,7 @@ impl FridaRuntimeTuple for () {
&mut self, &mut self,
_gum: &Gum, _gum: &Gum,
_ranges: &RangeMap<usize, (u16, String)>, _ranges: &RangeMap<usize, (u16, String)>,
_modules_to_instrument: &[&str], _module_map: &Rc<ModuleMap>,
) { ) {
} }
fn pre_exec_all<I: Input + HasTargetBytes>(&mut self, _input: &I) -> Result<(), Error> { fn pre_exec_all<I: Input + HasTargetBytes>(&mut self, _input: &I) -> Result<(), Error> {
@ -95,10 +100,10 @@ where
&mut self, &mut self,
gum: &Gum, gum: &Gum,
ranges: &RangeMap<usize, (u16, String)>, ranges: &RangeMap<usize, (u16, String)>,
modules_to_instrument: &[&str], module_map: &Rc<ModuleMap>,
) { ) {
self.0.init(gum, ranges, modules_to_instrument); self.0.init(gum, ranges, module_map);
self.1.init_all(gum, ranges, modules_to_instrument); self.1.init_all(gum, ranges, module_map);
} }
fn pre_exec_all<I: Input + HasTargetBytes>(&mut self, input: &I) -> Result<(), Error> { fn pre_exec_all<I: Input + HasTargetBytes>(&mut self, input: &I) -> Result<(), Error> {
@ -112,12 +117,234 @@ where
} }
} }
/// Represents a range to be skipped for instrumentation
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SkipRange {
/// An absolute range
Absolute(std::ops::Range<usize>),
/// A range relative to the module with the given name
ModuleRelative {
/// The module name
name: String,
/// The address range
range: std::ops::Range<usize>,
},
}
/// Builder for [`FridaInstrumentationHelper`](FridaInstrumentationHelper)
pub struct FridaInstrumentationHelperBuilder {
stalker_enabled: bool,
disable_excludes: bool,
#[allow(clippy::type_complexity)]
instrument_module_predicate: Option<Box<dyn FnMut(&ModuleDetails) -> bool>>,
skip_module_predicate: Box<dyn FnMut(&ModuleDetails) -> bool>,
skip_ranges: Vec<SkipRange>,
}
impl FridaInstrumentationHelperBuilder {
/// Create a new `FridaInstrumentationHelperBuilder`
pub fn new() -> Self {
Self::default()
}
/// Enable or disable the Stalker
///
/// Required for coverage collection, ASAN, and `CmpLog`.
/// Enabled by default.
#[must_use]
pub fn enable_stalker(self, enabled: bool) -> Self {
Self {
stalker_enabled: enabled,
..self
}
}
/// Disable excludes
///
/// Don't use `stalker.exclude()`.
/// See <https://github.com/AFLplusplus/LibAFL/issues/830>
#[must_use]
pub fn disable_excludes(self, disabled: bool) -> Self {
Self {
disable_excludes: disabled,
..self
}
}
/// Modules for which the given predicate returns `true` will be instrumented.
///
/// Can be specified multiple times; a module will be instrumented if _any_ of the given predicates match.
/// [`skip_modules_if`](Self::skip_modules-if) will override these.
///
/// # Example
/// Instrument all modules in `/usr/lib` as well as `libfoo.so`:
/// ```
///# use libafl_frida::helper::FridaInstrumentationHelperBuilder;
/// let builder = FridaInstrumentationHelperBuilder::new()
/// .instrument_module_if(|module| module.name() == "libfoo.so")
/// .instrument_module_if(|module| module.path().starts_with("/usr/lib"));
/// ```
#[must_use]
pub fn instrument_module_if<F: FnMut(&ModuleDetails) -> bool + 'static>(
mut self,
mut predicate: F,
) -> Self {
let new = move |module: &_| match &mut self.instrument_module_predicate {
Some(existing) => existing(module) || predicate(module),
None => predicate(module),
};
Self {
instrument_module_predicate: Some(Box::new(new)),
..self
}
}
/// Modules for which the given predicate returns `true` will not be instrumented.
///
/// Can be specified multiple times; a module will be skipped if _any_ of the given predicates match.
/// Overrides modules included using [`instrument_module_if`](Self::instrument_module_if).
///
/// # Example
/// Instrument all modules in `/usr/lib`, but exclude `libfoo.so`.
///
/// ```
///# use libafl_frida::helper::FridaInstrumentationHelperBuilder;
/// let builder = FridaInstrumentationHelperBuilder::new()
/// .instrument_module_if(|module| module.path().starts_with("/usr/lib"))
/// .skip_module_if(|module| module.name() == "libfoo.so");
/// ```
#[must_use]
pub fn skip_module_if<F: FnMut(&ModuleDetails) -> bool + 'static>(
mut self,
mut predicate: F,
) -> Self {
let new = move |module: &_| (self.skip_module_predicate)(module) || predicate(module);
Self {
skip_module_predicate: Box::new(new),
..self
}
}
/// Skip a specific range
#[must_use]
pub fn skip_range(mut self, range: SkipRange) -> Self {
self.skip_ranges.push(range);
self
}
/// Skip a set of ranges
#[must_use]
pub fn skip_ranges<I: IntoIterator<Item = SkipRange>>(mut self, ranges: I) -> Self {
self.skip_ranges.extend(ranges);
self
}
/// Build a `FridaInstrumentationHelper`
pub fn build<RT: FridaRuntimeTuple>(
self,
gum: &Gum,
mut runtimes: RT,
) -> FridaInstrumentationHelper<'_, RT> {
let Self {
stalker_enabled,
disable_excludes,
mut instrument_module_predicate,
mut skip_module_predicate,
skip_ranges,
} = self;
let mut module_filter = Box::new(move |module| {
if let Some(instrument_module_predicate) = &mut instrument_module_predicate {
let skip = skip_module_predicate(&module);
let should_instrument = instrument_module_predicate(&module);
should_instrument && !skip
} else {
!skip_module_predicate(&module)
}
});
let module_map = Rc::new(ModuleMap::new_with_filter(gum, &mut module_filter));
let mut ranges = RangeMap::new();
if stalker_enabled {
for (i, module) in module_map.values().iter().enumerate() {
let range = module.range();
let start = range.base_address().0 as usize;
ranges.insert(start..(start + range.size()), (i as u16, module.path()));
}
for skip in skip_ranges {
match skip {
SkipRange::Absolute(range) => ranges.remove(range),
SkipRange::ModuleRelative { name, range } => {
let module_details = ModuleDetails::with_name(name).unwrap();
let lib_start = module_details.range().base_address().0 as usize;
ranges.remove((lib_start + range.start)..(lib_start + range.end));
}
}
}
runtimes.init_all(gum, &ranges, &module_map);
}
// Wrap ranges and runtimes in reference-counted refcells in order to move
// these references both into the struct that we return and the transformer callback
// that we pass to frida-gum.
let ranges = Rc::new(RefCell::new(ranges));
let runtimes = Rc::new(RefCell::new(runtimes));
let transformer = FridaInstrumentationHelper::build_transformer(gum, &ranges, &runtimes);
#[cfg(unix)]
FridaInstrumentationHelper::<'_, RT>::workaround_gum_allocate_near();
FridaInstrumentationHelper {
transformer,
ranges,
runtimes,
stalker_enabled,
disable_excludes,
}
}
}
impl Debug for FridaInstrumentationHelperBuilder {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut dbg_me = f.debug_struct("FridaInstrumentationHelper");
dbg_me
.field("stalker_enabled", &self.stalker_enabled)
.field("instrument_module_predicate", &"<closure>")
.field("skip_module_predicate", &"<closure>")
.field("skip_ranges", &self.skip_ranges)
.field("disable_excludes", &self.disable_excludes);
dbg_me.finish()
}
}
impl Default for FridaInstrumentationHelperBuilder {
fn default() -> Self {
Self {
stalker_enabled: true,
disable_excludes: true,
instrument_module_predicate: None,
skip_module_predicate: Box::new(|module| {
// Skip the instrumentation module to avoid recursion.
let range = module.range();
let start = range.base_address().0 as usize;
let range = start..(start + range.size());
range.contains(&(Self::new as usize))
}),
skip_ranges: Vec::new(),
}
}
}
/// An helper that feeds `FridaInProcessExecutor` with edge-coverage instrumentation /// An helper that feeds `FridaInProcessExecutor` with edge-coverage instrumentation
pub struct FridaInstrumentationHelper<'a, RT: 'a> { pub struct FridaInstrumentationHelper<'a, RT: 'a> {
options: &'a FuzzerOptions,
transformer: Transformer<'a>, transformer: Transformer<'a>,
ranges: Rc<RefCell<RangeMap<usize, (u16, String)>>>, ranges: Rc<RefCell<RangeMap<usize, (u16, String)>>>,
runtimes: Rc<RefCell<RT>>, runtimes: Rc<RefCell<RT>>,
stalker_enabled: bool,
pub(crate) disable_excludes: bool,
} }
impl<RT> Debug for FridaInstrumentationHelper<'_, RT> { impl<RT> Debug for FridaInstrumentationHelper<'_, RT> {
@ -126,7 +353,7 @@ impl<RT> Debug for FridaInstrumentationHelper<'_, RT> {
dbg_me dbg_me
.field("ranges", &self.ranges) .field("ranges", &self.ranges)
.field("module_map", &"<ModuleMap>") .field("module_map", &"<ModuleMap>")
.field("options", &self.options); .field("stalker_enabled", &self.stalker_enabled);
dbg_me.finish() dbg_me.finish()
} }
} }
@ -154,17 +381,231 @@ fn pc(context: &CpuContext) -> usize {
context.rip() as usize context.rip() as usize
} }
fn pathlist_contains_module<I, P>(list: I, module: &ModuleDetails) -> bool
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
{
let module_name = module.name();
let module_path = PathBuf::from(module.path());
let canonicalized_module_path = fs::canonicalize(&module_path).ok();
list.into_iter().any(|path| {
let path = path.as_ref();
path == Path::new(&module_name)
|| path == module_path
|| fs::canonicalize(path).ok() == canonicalized_module_path
})
}
impl<'a> FridaInstrumentationHelper<'a, ()> {
/// Create a builder to initialize a `FridaInstrumentationHelper`.
///
/// See the documentation of [`FridaInstrumentationHelperBuilder`](FridaInstrumentationHelperBuilder)
/// for more details.
pub fn builder() -> FridaInstrumentationHelperBuilder {
FridaInstrumentationHelperBuilder::default()
}
}
/// The implementation of the [`FridaInstrumentationHelper`] /// The implementation of the [`FridaInstrumentationHelper`]
impl<'a, RT> FridaInstrumentationHelper<'a, RT> impl<'a, RT> FridaInstrumentationHelper<'a, RT>
where where
RT: FridaRuntimeTuple, RT: FridaRuntimeTuple + 'a,
{ {
/// Constructor function to create a new [`FridaInstrumentationHelper`], given a `module_name`. /// Constructor function to create a new [`FridaInstrumentationHelper`], given CLI Options.
#[allow(clippy::too_many_lines)]
#[must_use] #[must_use]
pub fn new(gum: &'a Gum, options: &'a FuzzerOptions, mut runtimes: RT) -> Self { pub fn new<'b>(gum: &'a Gum, options: &'b FuzzerOptions, runtimes: RT) -> Self {
let harness = options.harness.clone();
let libs_to_instrument = options
.libs_to_instrument
.iter()
.map(PathBuf::from)
.collect::<Vec<_>>();
return FridaInstrumentationHelper::builder()
.enable_stalker(options.cmplog || options.asan || !options.disable_coverage)
.disable_excludes(options.disable_excludes)
.instrument_module_if(move |module| pathlist_contains_module(&harness, module))
.instrument_module_if(move |module| {
pathlist_contains_module(&libs_to_instrument, module)
})
.skip_ranges(options.dont_instrument.iter().map(|(name, offset)| {
SkipRange::ModuleRelative {
name: name.clone(),
range: *offset..*offset + 4,
}
}))
.build(gum, runtimes);
}
#[allow(clippy::too_many_lines)]
fn build_transformer(
gum: &'a Gum,
ranges: &Rc<RefCell<RangeMap<usize, (u16, String)>>>,
runtimes: &Rc<RefCell<RT>>,
) -> Transformer<'a> {
let ranges = Rc::clone(ranges);
let runtimes = Rc::clone(runtimes);
#[cfg(target_arch = "aarch64")]
let capstone = Capstone::new()
.arm64()
.mode(arch::arm64::ArchMode::Arm)
.detail(true)
.build()
.expect("Failed to create Capstone object");
#[cfg(all(target_arch = "x86_64", unix))]
let capstone = Capstone::new()
.x86()
.mode(arch::x86::ArchMode::Mode64)
.detail(true)
.build()
.expect("Failed to create Capstone object");
Transformer::from_callback(gum, move |basic_block, output| {
Self::transform(
basic_block,
&output,
&ranges,
&runtimes,
#[cfg(any(target_arch = "aarch64", all(target_arch = "x86_64", unix)))]
&capstone,
);
})
}
fn transform(
basic_block: StalkerIterator,
output: &StalkerOutput,
ranges: &Rc<RefCell<RangeMap<usize, (u16, String)>>>,
runtimes: &Rc<RefCell<RT>>,
#[cfg(any(target_arch = "aarch64", all(target_arch = "x86_64", unix)))] capstone: &Capstone,
) {
let mut first = true;
for instruction in basic_block {
let instr = instruction.instr();
#[cfg(unix)]
let instr_size = instr.bytes().len();
let address = instr.address();
//log::trace!("block @ {:x} transformed to {:x}", address, output.writer().pc());
if ranges.borrow().contains_key(&(address as usize)) {
let mut runtimes = (*runtimes).borrow_mut();
if first {
first = false;
// log::info!(
// "block @ {:x} transformed to {:x}",
// address,
// output.writer().pc()
// );
if let Some(rt) = runtimes.match_first_type_mut::<CoverageRuntime>() {
rt.emit_coverage_mapping(address, output);
}
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
instruction.put_callout(|context| {
let real_address = rt.real_address_for_stalked(pc(&context));
//let (range, (id, name)) = helper.ranges.get_key_value(&real_address).unwrap();
//log::trace!("{}:0x{:016x}", name, real_address - range.start);
rt.drcov_basic_blocks.push(DrCovBasicBlock::new(
real_address,
real_address + instr_size,
));
});
}
}
#[cfg(unix)]
let res = if let Some(_rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
AsanRuntime::asan_is_interesting_instruction(capstone, address, instr)
} else {
None
};
#[cfg(all(target_arch = "x86_64", unix))]
if let Some((segment, width, basereg, indexreg, scale, disp)) = res {
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address,
output,
segment,
width,
basereg,
indexreg,
scale,
disp.try_into().unwrap(),
);
}
}
#[cfg(target_arch = "aarch64")]
if let Some((basereg, indexreg, displacement, width, shift, extender)) = res {
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address,
&output,
basereg,
indexreg,
displacement,
width,
shift,
extender,
);
}
}
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
if let Some(rt) = runtimes.match_first_type_mut::<CmpLogRuntime>() {
if let Some((op1, op2, special_case)) =
CmpLogRuntime::cmplog_is_interesting_instruction(&capstone, address, instr)
{
//emit code that saves the relevant data in runtime(passes it to x0, x1)
rt.emit_comparison_handling(address, &output, &op1, &op2, special_case);
}
}
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
}
instruction.keep();
}
}
/*
/// Return the runtime
pub fn runtime<R>(&self) -> Option<&R>
where
R: FridaRuntime,
{
self.runtimes.borrow().match_first_type::<R>()
}
/// Return the mutable runtime
pub fn runtime_mut<R>(&mut self) -> Option<&mut R>
where
R: FridaRuntime,
{
(*self.runtimes).borrow_mut().match_first_type_mut::<R>()
}
*/
// workaround frida's frida-gum-allocate-near bug: // workaround frida's frida-gum-allocate-near bug:
#[cfg(unix)] #[cfg(unix)]
fn workaround_gum_allocate_near() {
unsafe { unsafe {
for _ in 0..512 { for _ in 0..512 {
mmap( mmap(
@ -187,210 +628,7 @@ where
.expect("Failed to map dummy regions for frida workaround"); .expect("Failed to map dummy regions for frida workaround");
} }
} }
let mut modules_to_instrument = vec![options
.harness
.as_ref()
.unwrap()
.to_string_lossy()
.to_string()];
modules_to_instrument.append(&mut options.libs_to_instrument.clone());
let modules_to_instrument: Vec<&str> =
modules_to_instrument.iter().map(AsRef::as_ref).collect();
let module_map = ModuleMap::new_from_names(gum, &modules_to_instrument);
let mut ranges = RangeMap::new();
if options.cmplog || options.asan || !options.disable_coverage {
for (i, module) in module_map.values().iter().enumerate() {
let range = module.range();
let start = range.base_address().0 as usize;
// log::trace!("start: {:x}", start);
ranges.insert(start..(start + range.size()), (i as u16, module.path()));
} }
if !options.dont_instrument.is_empty() {
for (module_name, offset) in options.dont_instrument.clone() {
let module_details = ModuleDetails::with_name(module_name).unwrap();
let lib_start = module_details.range().base_address().0 as usize;
// log::info!("removing address: {:#x}", lib_start + offset);
ranges.remove((lib_start + offset)..(lib_start + offset + 4));
}
}
// make sure we aren't in the instrumented list, as it would cause recursions
assert!(
!ranges.contains_key(&(Self::new as usize)),
"instrumented libraries must not include the fuzzer"
);
runtimes.init_all(gum, &ranges, &modules_to_instrument);
}
#[cfg(target_arch = "aarch64")]
let capstone = Capstone::new()
.arm64()
.mode(arch::arm64::ArchMode::Arm)
.detail(true)
.build()
.expect("Failed to create Capstone object");
#[cfg(all(target_arch = "x86_64", unix))]
let capstone = Capstone::new()
.x86()
.mode(arch::x86::ArchMode::Mode64)
.detail(true)
.build()
.expect("Failed to create Capstone object");
// Wrap ranges and runtimes in reference-counted refcells in order to move
// these references both into the struct that we return and the transformer callback
// that we pass to frida-gum.
let ranges = Rc::new(RefCell::new(ranges));
let runtimes = Rc::new(RefCell::new(runtimes));
let transformer = {
let ranges = Rc::clone(&ranges);
let runtimes = Rc::clone(&runtimes);
Transformer::from_callback(gum, move |basic_block, output| {
let mut first = true;
for instruction in basic_block {
let instr = instruction.instr();
#[cfg(unix)]
let instr_size = instr.bytes().len();
let address = instr.address();
//log::trace!("block @ {:x} transformed to {:x}", address, output.writer().pc());
if ranges.borrow().contains_key(&(address as usize)) {
let mut runtimes = (*runtimes).borrow_mut();
if first {
first = false;
// log::info!(
// "block @ {:x} transformed to {:x}",
// address,
// output.writer().pc()
// );
if let Some(rt) = runtimes.match_first_type_mut::<CoverageRuntime>() {
rt.emit_coverage_mapping(address, &output);
}
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
instruction.put_callout(|context| {
let real_address = rt.real_address_for_stalked(pc(&context));
//let (range, (id, name)) = helper.ranges.get_key_value(&real_address).unwrap();
//log::trace!("{}:0x{:016x}", name, real_address - range.start);
rt.drcov_basic_blocks.push(DrCovBasicBlock::new(
real_address,
real_address + instr_size,
));
});
}
}
#[cfg(unix)]
let res = if let Some(_rt) = runtimes.match_first_type_mut::<AsanRuntime>()
{
AsanRuntime::asan_is_interesting_instruction(&capstone, address, instr)
} else {
None
};
#[cfg(all(target_arch = "x86_64", unix))]
if let Some((segment, width, basereg, indexreg, scale, disp)) = res {
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address,
&output,
segment,
width,
basereg,
indexreg,
scale,
disp.try_into().unwrap(),
);
}
}
#[cfg(target_arch = "aarch64")]
if let Some((basereg, indexreg, displacement, width, shift, extender)) = res
{
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.emit_shadow_check(
address,
&output,
basereg,
indexreg,
displacement,
width,
shift,
extender,
);
}
}
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
if let Some(rt) = runtimes.match_first_type_mut::<CmpLogRuntime>() {
if let Some((op1, op2, special_case)) =
CmpLogRuntime::cmplog_is_interesting_instruction(
&capstone, address, instr,
)
{
//emit code that saves the relevant data in runtime(passes it to x0, x1)
rt.emit_comparison_handling(
address,
&output,
&op1,
&op2,
special_case,
);
}
}
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
#[cfg(unix)]
if let Some(rt) = runtimes.match_first_type_mut::<DrCovRuntime>() {
rt.add_stalked_address(
output.writer().pc() as usize - instr_size,
address as usize,
);
}
}
instruction.keep();
}
})
};
Self {
options,
transformer,
ranges,
runtimes,
}
}
/*
/// Return the runtime
pub fn runtime<R>(&self) -> Option<&R>
where
R: FridaRuntime,
{
self.runtimes.borrow().match_first_type::<R>()
}
/// Return the mutable runtime
pub fn runtime_mut<R>(&mut self) -> Option<&mut R>
where
R: FridaRuntime,
{
(*self.runtimes).borrow_mut().match_first_type_mut::<R>()
}
*/
/// Returns ref to the Transformer /// Returns ref to the Transformer
pub fn transformer(&self) -> &Transformer<'a> { pub fn transformer(&self) -> &Transformer<'a> {
@ -402,11 +640,11 @@ where
&mut self, &mut self,
gum: &'a Gum, gum: &'a Gum,
ranges: &RangeMap<usize, (u16, String)>, ranges: &RangeMap<usize, (u16, String)>,
modules_to_instrument: &'a [&str], module_map: &Rc<ModuleMap>,
) { ) {
(*self.runtimes) (*self.runtimes)
.borrow_mut() .borrow_mut()
.init_all(gum, ranges, modules_to_instrument); .init_all(gum, ranges, module_map);
} }
/// Method called before execution /// Method called before execution
@ -421,7 +659,7 @@ where
/// If stalker is enabled /// If stalker is enabled
pub fn stalker_enabled(&self) -> bool { pub fn stalker_enabled(&self) -> bool {
self.options.cmplog || self.options.asan || !self.options.disable_coverage self.stalker_enabled
} }
/// Pointer to coverage map /// Pointer to coverage map
@ -441,10 +679,4 @@ where
pub fn ranges_mut(&mut self) -> RefMut<RangeMap<usize, (u16, String)>> { pub fn ranges_mut(&mut self) -> RefMut<RangeMap<usize, (u16, String)>> {
(*self.ranges).borrow_mut() (*self.ranges).borrow_mut()
} }
/// Return the ref to options
#[inline]
pub fn options(&self) -> &FuzzerOptions {
self.options
}
} }