Hook using frida gum interceptor instead of gothook (#112)
* Move from gothook to frida-based hooks * Force link against libc++ * Clippy + cleanup prints * exclude ranges * Add back guard pages; Implement libc hooks * Bump frida-rust version * Add hooks for mmap/munmap, as per issue #105 * Refactor to get rid of global allocator singleton * Cleanup imports; Fix free out-of-range; Move to fixed addresses for asan allocatoins * use frida-rust from crates.io now that it has caught up * cargo fmt * Clippy fixes * Better clippy fix * More clippy fix * Formatting * Review changes
This commit is contained in:
parent
d4410c072a
commit
3a21ad59a4
@ -21,11 +21,10 @@ num_cpus = "1.0"
|
||||
which = "4.1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]}
|
||||
libafl = { path = "../../libafl/", features = [ "std", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]}
|
||||
libafl_frida = { path = "../../libafl_frida" }
|
||||
capstone = "0.8.0"
|
||||
frida-gum = { version = "0.5.0", git = "https://github.com/frida/frida-rust", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"], rev = "69f5b8236ab4b66296507803b4b7bfec79700e84" }
|
||||
#frida-gum = { version = "0.5.0", path = "../../../frida-rust/frida-gum", features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
frida-gum = { version = "0.5.1", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
libloading = "0.7.0"
|
||||
|
@ -40,7 +40,7 @@ use libafl::{
|
||||
|
||||
use frida_gum::{
|
||||
stalker::{NoneEventSink, Stalker},
|
||||
Gum, NativePointer,
|
||||
Gum, MemoryRange, NativePointer,
|
||||
};
|
||||
|
||||
use std::{
|
||||
@ -53,7 +53,7 @@ use std::{
|
||||
};
|
||||
|
||||
use libafl_frida::{
|
||||
asan_rt::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS},
|
||||
asan_errors::{AsanErrorsFeedback, AsanErrorsObserver, ASAN_ERRORS},
|
||||
helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE},
|
||||
FridaOptions,
|
||||
};
|
||||
@ -190,17 +190,15 @@ where
|
||||
helper: &'c mut FH,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
let stalker = Stalker::new(gum);
|
||||
let mut stalker = Stalker::new(gum);
|
||||
|
||||
// Let's exclude the main module and libc.so at least:
|
||||
//stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address(&env::args().next().unwrap()),
|
||||
//get_module_size(&env::args().next().unwrap()),
|
||||
//));
|
||||
//stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address("libc.so"),
|
||||
//get_module_size("libc.so"),
|
||||
//));
|
||||
for range in helper.ranges().gaps(&(0..usize::MAX)) {
|
||||
println!("excluding range: {:x}-{:x}", range.start, range.end);
|
||||
stalker.exclude(&MemoryRange::new(
|
||||
NativePointer(range.start as *mut c_void),
|
||||
range.end - range.start,
|
||||
));
|
||||
}
|
||||
|
||||
Self {
|
||||
base: TimeoutExecutor::new(base, timeout),
|
||||
@ -385,7 +383,8 @@ unsafe fn fuzz(
|
||||
// RNG
|
||||
StdRand::with_seed(current_nanos()),
|
||||
// Corpus that will be evolved, we keep it in memory for performance
|
||||
InMemoryCorpus::new(),
|
||||
OnDiskCorpus::new(PathBuf::from("./corpus_discovered")).unwrap(),
|
||||
//InMemoryCorpus::new(),
|
||||
// Corpus in which we store solutions (crashes in this example),
|
||||
// on disk so the user can get them after stopping the fuzzer
|
||||
OnDiskCorpus::new_save_meta(
|
||||
@ -440,15 +439,6 @@ unsafe fn fuzz(
|
||||
&mut frida_helper,
|
||||
Duration::new(10, 0),
|
||||
);
|
||||
// Let's exclude the main module and libc.so at least:
|
||||
//executor.stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address(&env::args().next().unwrap()),
|
||||
//get_module_size(&env::args().next().unwrap()),
|
||||
//));
|
||||
//executor.stalker.exclude(&MemoryRange::new(
|
||||
//Module::find_base_address("libc.so"),
|
||||
//get_module_size("libc.so"),
|
||||
//));
|
||||
|
||||
// In case the corpus is empty (on first run), reset
|
||||
if state.corpus().count() < 1 {
|
||||
|
@ -851,10 +851,6 @@ where
|
||||
|
||||
println!("We're a client, let's fuzz :)");
|
||||
|
||||
for (var, val) in std::env::vars() {
|
||||
println!("ENV VARS: {:?}: {:?}", var, val);
|
||||
}
|
||||
|
||||
// If we're restarting, deserialize the old state.
|
||||
let (state, mut mgr) = match receiver.recv_buf()? {
|
||||
None => {
|
||||
|
@ -430,8 +430,7 @@ mod unix_signal_handler {
|
||||
{
|
||||
println!("Double crash\n");
|
||||
#[cfg(target_os = "android")]
|
||||
let si_addr =
|
||||
{ ((_info._pad[0] as usize) | ((_info._pad[1] as usize) << 32)) as usize };
|
||||
let si_addr = (_info._pad[0] as i64) | ((_info._pad[1] as i64) << 32);
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let si_addr = { _info.si_addr() as usize };
|
||||
|
||||
@ -466,6 +465,7 @@ mod unix_signal_handler {
|
||||
#[cfg(feature = "std")]
|
||||
println!("Child crashed!");
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
#[cfg(all(
|
||||
feature = "std",
|
||||
any(target_os = "linux", target_os = "android"),
|
||||
|
@ -21,10 +21,8 @@ libc = "0.2.92"
|
||||
hashbrown = "0.11"
|
||||
libloading = "0.7.0"
|
||||
rangemap = "0.1.10"
|
||||
frida-gum = { version = "0.5.0", git = "https://github.com/frida/frida-rust", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"], rev = "69f5b8236ab4b66296507803b4b7bfec79700e84" }
|
||||
frida-gum-sys = { version = "0.3.0", git = "https://github.com/frida/frida-rust", features = [ "auto-download", "event-sink", "invocation-listener"], rev = "69f5b8236ab4b66296507803b4b7bfec79700e84" }
|
||||
#frida-gum = { version = "0.5.0", path = "../../frida-rust/frida-gum", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
|
||||
#frida-gum-sys = { version = "0.3.0", path = "../../frida-rust/frida-gum-sys", features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
frida-gum-sys = { version = "0.3", features = [ "auto-download", "event-sink", "invocation-listener"] }
|
||||
frida-gum = { version = "0.5.1", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
|
||||
core_affinity = { version = "0.5", git = "https://github.com/s1341/core_affinity_rs" }
|
||||
regex = "1.4"
|
||||
dynasmrt = "1.0.1"
|
||||
@ -35,6 +33,4 @@ serde = "1.0"
|
||||
backtrace = { version = "0.3.58", default-features = false, features = ["std", "serde"] }
|
||||
num-traits = "0.2.14"
|
||||
ahash = "0.7"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
gothook = { version = "0.1" }
|
||||
paste = "1.0"
|
||||
|
@ -2,4 +2,7 @@
|
||||
|
||||
fn main() {
|
||||
cc::Build::new().file("src/gettls.c").compile("libgettls.a");
|
||||
|
||||
// Force linking against libc++
|
||||
println!("cargo:rustc-link-lib=dylib=c++");
|
||||
}
|
||||
|
408
libafl_frida/src/alloc.rs
Normal file
408
libafl_frida/src/alloc.rs
Normal file
@ -0,0 +1,408 @@
|
||||
use hashbrown::HashMap;
|
||||
use libafl::bolts::os::walk_self_maps;
|
||||
use nix::{
|
||||
libc::memset,
|
||||
sys::mman::{mmap, MapFlags, ProtFlags},
|
||||
};
|
||||
|
||||
use backtrace::Backtrace;
|
||||
#[cfg(unix)]
|
||||
use libc::{sysconf, _SC_PAGESIZE};
|
||||
use rangemap::RangeSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{ffi::c_void, io};
|
||||
|
||||
use crate::{
|
||||
asan_errors::{AsanError, AsanErrors},
|
||||
FridaOptions,
|
||||
};
|
||||
|
||||
pub(crate) struct Allocator {
|
||||
options: FridaOptions,
|
||||
page_size: usize,
|
||||
shadow_offset: usize,
|
||||
shadow_bit: usize,
|
||||
pre_allocated_shadow: bool,
|
||||
allocations: HashMap<usize, AllocationMetadata>,
|
||||
shadow_pages: RangeSet<usize>,
|
||||
allocation_queue: HashMap<usize, Vec<AllocationMetadata>>,
|
||||
largest_allocation: usize,
|
||||
base_mapping_addr: usize,
|
||||
current_mapping_addr: usize,
|
||||
}
|
||||
|
||||
macro_rules! map_to_shadow {
|
||||
($self:expr, $address:expr) => {
|
||||
(($address >> 3) + $self.shadow_offset) & ((1 << ($self.shadow_bit + 1)) - 1)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub(crate) struct AllocationMetadata {
|
||||
pub address: usize,
|
||||
pub size: usize,
|
||||
pub actual_size: usize,
|
||||
pub allocation_site_backtrace: Option<Backtrace>,
|
||||
pub release_site_backtrace: Option<Backtrace>,
|
||||
pub freed: bool,
|
||||
pub is_malloc_zero: bool,
|
||||
}
|
||||
|
||||
impl Allocator {
|
||||
pub fn new(options: FridaOptions) -> Self {
|
||||
let ret = unsafe { sysconf(_SC_PAGESIZE) };
|
||||
if ret < 0 {
|
||||
panic!("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: usize = 0;
|
||||
for try_shadow_bit in &[46usize, 36usize] {
|
||||
let addr: usize = 1 << try_shadow_bit;
|
||||
if unsafe {
|
||||
mmap(
|
||||
addr as *mut c_void,
|
||||
page_size,
|
||||
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
|
||||
MapFlags::MAP_PRIVATE
|
||||
| MapFlags::MAP_ANONYMOUS
|
||||
| MapFlags::MAP_FIXED
|
||||
| MapFlags::MAP_NORESERVE,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
}
|
||||
.is_ok()
|
||||
{
|
||||
shadow_bit = *try_shadow_bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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(
|
||||
addr as *mut c_void,
|
||||
addr + addr,
|
||||
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
|
||||
MapFlags::MAP_ANONYMOUS
|
||||
| MapFlags::MAP_FIXED
|
||||
| MapFlags::MAP_PRIVATE
|
||||
| MapFlags::MAP_NORESERVE,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
}
|
||||
.is_ok();
|
||||
|
||||
Self {
|
||||
options,
|
||||
page_size,
|
||||
pre_allocated_shadow,
|
||||
shadow_offset: 1 << shadow_bit,
|
||||
shadow_bit,
|
||||
allocations: HashMap::new(),
|
||||
shadow_pages: RangeSet::new(),
|
||||
allocation_queue: HashMap::new(),
|
||||
largest_allocation: 0,
|
||||
base_mapping_addr: addr + addr + addr,
|
||||
current_mapping_addr: addr + addr + addr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retreive the shadow bit used by this allocator.
|
||||
pub fn shadow_bit(&self) -> u32 {
|
||||
self.shadow_bit as u32
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn round_up_to_page(&self, size: usize) -> usize {
|
||||
((size + self.page_size) / self.page_size) * self.page_size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn round_down_to_page(&self, value: usize) -> usize {
|
||||
(value / self.page_size) * self.page_size
|
||||
}
|
||||
|
||||
fn find_smallest_fit(&mut self, size: usize) -> Option<AllocationMetadata> {
|
||||
let mut current_size = size;
|
||||
while current_size <= self.largest_allocation {
|
||||
if self.allocation_queue.contains_key(¤t_size) {
|
||||
if let Some(metadata) = self.allocation_queue.entry(current_size).or_default().pop()
|
||||
{
|
||||
return Some(metadata);
|
||||
}
|
||||
}
|
||||
current_size *= 2;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub unsafe fn alloc(&mut self, size: usize, _alignment: usize) -> *mut c_void {
|
||||
let mut is_malloc_zero = false;
|
||||
let size = if size == 0 {
|
||||
println!("zero-sized allocation!");
|
||||
is_malloc_zero = true;
|
||||
16
|
||||
} else {
|
||||
size
|
||||
};
|
||||
if size > (1 << 30) {
|
||||
panic!("Allocation is too large: 0x{:x}", size);
|
||||
}
|
||||
let rounded_up_size = self.round_up_to_page(size) + 2 * self.page_size;
|
||||
|
||||
let metadata = if let Some(mut metadata) = self.find_smallest_fit(rounded_up_size) {
|
||||
//println!("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.size = size;
|
||||
if self.options.enable_asan_allocation_backtraces {
|
||||
metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved());
|
||||
}
|
||||
metadata
|
||||
} else {
|
||||
let mapping = match mmap(
|
||||
self.current_mapping_addr as *mut c_void,
|
||||
rounded_up_size,
|
||||
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
|
||||
MapFlags::MAP_ANONYMOUS
|
||||
| MapFlags::MAP_PRIVATE
|
||||
| MapFlags::MAP_FIXED
|
||||
| MapFlags::MAP_NORESERVE,
|
||||
-1,
|
||||
0,
|
||||
) {
|
||||
Ok(mapping) => mapping as usize,
|
||||
Err(err) => {
|
||||
println!("An error occurred while mapping memory: {:?}", err);
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
self.current_mapping_addr += rounded_up_size;
|
||||
|
||||
self.map_shadow_for_region(mapping, mapping + rounded_up_size, false);
|
||||
|
||||
let mut metadata = AllocationMetadata {
|
||||
address: mapping,
|
||||
size,
|
||||
actual_size: rounded_up_size,
|
||||
..AllocationMetadata::default()
|
||||
};
|
||||
|
||||
if self.options.enable_asan_allocation_backtraces {
|
||||
metadata.allocation_site_backtrace = Some(Backtrace::new_unresolved());
|
||||
}
|
||||
|
||||
metadata
|
||||
};
|
||||
|
||||
self.largest_allocation = std::cmp::max(self.largest_allocation, metadata.actual_size);
|
||||
// unpoison the shadow memory for the allocation itself
|
||||
Self::unpoison(
|
||||
map_to_shadow!(self, metadata.address + self.page_size),
|
||||
size,
|
||||
);
|
||||
let address = (metadata.address + self.page_size) as *mut c_void;
|
||||
|
||||
self.allocations
|
||||
.insert(metadata.address + self.page_size, metadata);
|
||||
//println!("serving address: {:?}, size: {:x}", address, size);
|
||||
address
|
||||
}
|
||||
|
||||
pub unsafe fn release(&mut self, ptr: *mut c_void) {
|
||||
let mut metadata = if let Some(metadata) = self.allocations.get_mut(&(ptr as usize)) {
|
||||
metadata
|
||||
} else {
|
||||
if !ptr.is_null() {
|
||||
AsanErrors::get_mut().report_error(
|
||||
AsanError::UnallocatedFree((ptr as usize, Backtrace::new())),
|
||||
None,
|
||||
);
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
if metadata.freed {
|
||||
AsanErrors::get_mut().report_error(
|
||||
AsanError::DoubleFree((ptr as usize, metadata.clone(), Backtrace::new())),
|
||||
None,
|
||||
);
|
||||
}
|
||||
let shadow_mapping_start = map_to_shadow!(self, ptr as usize);
|
||||
|
||||
metadata.freed = true;
|
||||
if self.options.enable_asan_allocation_backtraces {
|
||||
metadata.release_site_backtrace = Some(Backtrace::new_unresolved());
|
||||
}
|
||||
|
||||
// poison the shadow memory for the allocation
|
||||
Self::poison(shadow_mapping_start, metadata.size);
|
||||
}
|
||||
|
||||
pub fn find_metadata(
|
||||
&mut self,
|
||||
ptr: usize,
|
||||
hint_base: usize,
|
||||
) -> Option<&mut AllocationMetadata> {
|
||||
let mut metadatas: Vec<&mut AllocationMetadata> = self.allocations.values_mut().collect();
|
||||
metadatas.sort_by(|a, b| a.address.cmp(&b.address));
|
||||
let mut offset_to_closest = i64::max_value();
|
||||
let mut closest = None;
|
||||
for metadata in metadatas {
|
||||
let new_offset = if hint_base == metadata.address {
|
||||
(ptr as i64 - metadata.address as i64).abs()
|
||||
} else {
|
||||
std::cmp::min(
|
||||
offset_to_closest,
|
||||
(ptr as i64 - metadata.address as i64).abs(),
|
||||
)
|
||||
};
|
||||
if new_offset < offset_to_closest {
|
||||
offset_to_closest = new_offset;
|
||||
closest = Some(metadata);
|
||||
}
|
||||
}
|
||||
closest
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
for (address, mut allocation) in self.allocations.drain() {
|
||||
// First poison the memory.
|
||||
Self::poison(map_to_shadow!(self, address), allocation.size);
|
||||
|
||||
// Reset the allocaiton metadata object
|
||||
allocation.size = 0;
|
||||
allocation.freed = false;
|
||||
allocation.allocation_site_backtrace = None;
|
||||
allocation.release_site_backtrace = None;
|
||||
|
||||
// Move the allocation from the allocations to the to-be-allocated queues
|
||||
self.allocation_queue
|
||||
.entry(allocation.actual_size)
|
||||
.or_default()
|
||||
.push(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_usable_size(&self, ptr: *mut c_void) -> usize {
|
||||
match self.allocations.get(&(ptr as usize)) {
|
||||
Some(metadata) => metadata.size,
|
||||
None => {
|
||||
panic!(
|
||||
"Attempted to get_usable_size on a pointer ({:?}) which was not allocated!",
|
||||
ptr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unpoison(start: usize, size: usize) {
|
||||
//println!("unpoisoning {:x} for {:x}", start, size / 8 + 1);
|
||||
unsafe {
|
||||
//println!("memset: {:?}", start as *mut c_void);
|
||||
memset(start as *mut c_void, 0xff, size / 8);
|
||||
|
||||
let remainder = size % 8;
|
||||
if remainder > 0 {
|
||||
//println!("remainder: {:x}, offset: {:x}", remainder, start + size / 8);
|
||||
memset(
|
||||
(start + size / 8) as *mut c_void,
|
||||
(0xff << (8 - remainder)) & 0xff,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poison(start: usize, size: usize) {
|
||||
//println!("poisoning {:x} for {:x}", start, size / 8 + 1);
|
||||
unsafe {
|
||||
//println!("memset: {:?}", start as *mut c_void);
|
||||
memset(start as *mut c_void, 0x00, size / 8);
|
||||
|
||||
let remainder = size % 8;
|
||||
if remainder > 0 {
|
||||
//println!("remainder: {:x}, offset: {:x}", remainder, start + size / 8);
|
||||
memset((start + size / 8) as *mut c_void, 0x00, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Map shadow memory for a region, and optionally unpoison it
|
||||
pub fn map_shadow_for_region(
|
||||
&mut self,
|
||||
start: usize,
|
||||
end: usize,
|
||||
unpoison: bool,
|
||||
) -> (usize, usize) {
|
||||
//println!("start: {:x}, end {:x}, size {:x}", start, end, end - start);
|
||||
|
||||
let shadow_mapping_start = map_to_shadow!(self, start);
|
||||
|
||||
if !self.pre_allocated_shadow {
|
||||
let shadow_start = self.round_down_to_page(shadow_mapping_start);
|
||||
let shadow_end =
|
||||
self.round_up_to_page((end - start) / 8) + self.page_size + shadow_start;
|
||||
for range in self.shadow_pages.gaps(&(shadow_start..shadow_end)) {
|
||||
//println!("range: {:x}-{:x}, pagesize: {}", range.start, range.end, self.page_size);
|
||||
unsafe {
|
||||
mmap(
|
||||
range.start as *mut c_void,
|
||||
range.end - range.start,
|
||||
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
|
||||
MapFlags::MAP_ANONYMOUS | MapFlags::MAP_FIXED | MapFlags::MAP_PRIVATE,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
.expect("An error occurred while mapping shadow memory");
|
||||
}
|
||||
}
|
||||
|
||||
self.shadow_pages.insert(shadow_start..shadow_end);
|
||||
}
|
||||
|
||||
//println!("shadow_mapping_start: {:x}, shadow_size: {:x}", shadow_mapping_start, (end - start) / 8);
|
||||
if unpoison {
|
||||
Self::unpoison(shadow_mapping_start, end - start);
|
||||
}
|
||||
|
||||
(shadow_mapping_start, (end - start) / 8)
|
||||
}
|
||||
|
||||
pub fn map_to_shadow(&self, start: usize) -> usize {
|
||||
map_to_shadow!(self, start)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_managed(&self, ptr: *mut c_void) -> bool {
|
||||
//self.allocations.contains_key(&(ptr as usize))
|
||||
self.base_mapping_addr <= ptr as usize && (ptr as usize) < self.current_mapping_addr
|
||||
}
|
||||
|
||||
pub fn check_for_leaks(&self) {
|
||||
for metadata in self.allocations.values() {
|
||||
if !metadata.freed {
|
||||
AsanErrors::get_mut()
|
||||
.report_error(AsanError::Leak((metadata.address, metadata.clone())), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unpoison all the memory that is currently mapped with read/write permissions.
|
||||
pub fn unpoison_all_existing_memory(&mut self) {
|
||||
walk_self_maps(&mut |start, end, permissions, _path| {
|
||||
if permissions.as_bytes()[0] == b'r' || permissions.as_bytes()[1] == b'w' {
|
||||
if self.pre_allocated_shadow && start == 1 << self.shadow_bit {
|
||||
return false;
|
||||
}
|
||||
self.map_shadow_for_region(start, end, true);
|
||||
}
|
||||
false
|
||||
});
|
||||
}
|
||||
}
|
569
libafl_frida/src/asan_errors.rs
Normal file
569
libafl_frida/src/asan_errors.rs
Normal file
@ -0,0 +1,569 @@
|
||||
use backtrace::Backtrace;
|
||||
use capstone::{arch::BuildsCapstone, Capstone};
|
||||
use color_backtrace::{default_output_stream, BacktracePrinter, Verbosity};
|
||||
use frida_gum::interceptor::Interceptor;
|
||||
use libafl::{
|
||||
bolts::{os::find_mapping_for_address, ownedref::OwnedPtr, tuples::Named},
|
||||
corpus::Testcase,
|
||||
events::EventFirer,
|
||||
executors::{ExitKind, HasExecHooks},
|
||||
feedbacks::Feedback,
|
||||
inputs::{HasTargetBytes, Input},
|
||||
observers::{Observer, ObserversTuple},
|
||||
state::HasMetadata,
|
||||
Error, SerdeAny,
|
||||
};
|
||||
use rangemap::RangeMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
use termcolor::{Color, ColorSpec, WriteColor};
|
||||
|
||||
use crate::{alloc::AllocationMetadata, FridaOptions};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct AsanReadWriteError {
|
||||
pub registers: [usize; 32],
|
||||
pub pc: usize,
|
||||
pub fault: (u16, u16, usize, usize),
|
||||
pub metadata: AllocationMetadata,
|
||||
pub backtrace: Backtrace,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)]
|
||||
pub(crate) enum AsanError {
|
||||
OobRead(AsanReadWriteError),
|
||||
OobWrite(AsanReadWriteError),
|
||||
ReadAfterFree(AsanReadWriteError),
|
||||
WriteAfterFree(AsanReadWriteError),
|
||||
DoubleFree((usize, AllocationMetadata, Backtrace)),
|
||||
UnallocatedFree((usize, Backtrace)),
|
||||
Unknown(([usize; 32], usize, (u16, u16, usize, usize), Backtrace)),
|
||||
Leak((usize, AllocationMetadata)),
|
||||
StackOobRead(([usize; 32], usize, (u16, u16, usize, usize), Backtrace)),
|
||||
StackOobWrite(([usize; 32], usize, (u16, u16, usize, usize), Backtrace)),
|
||||
BadFuncArgRead((String, usize, usize, Backtrace)),
|
||||
BadFuncArgWrite((String, usize, usize, Backtrace)),
|
||||
}
|
||||
|
||||
impl AsanError {
|
||||
fn description(&self) -> &str {
|
||||
match self {
|
||||
AsanError::OobRead(_) => "heap out-of-bounds read",
|
||||
AsanError::OobWrite(_) => "heap out-of-bounds write",
|
||||
AsanError::DoubleFree(_) => "double-free",
|
||||
AsanError::UnallocatedFree(_) => "unallocated-free",
|
||||
AsanError::WriteAfterFree(_) => "heap use-after-free write",
|
||||
AsanError::ReadAfterFree(_) => "heap use-after-free read",
|
||||
AsanError::Unknown(_) => "heap unknown",
|
||||
AsanError::Leak(_) => "memory-leak",
|
||||
AsanError::StackOobRead(_) => "stack out-of-bounds read",
|
||||
AsanError::StackOobWrite(_) => "stack out-of-bounds write",
|
||||
AsanError::BadFuncArgRead(_) => "function arg resulting in bad read",
|
||||
AsanError::BadFuncArgWrite(_) => "function arg resulting in bad write",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct holding errors that occurred during frida address sanitizer runs
|
||||
#[allow(clippy::unsafe_derive_deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, SerdeAny)]
|
||||
pub struct AsanErrors {
|
||||
options: FridaOptions,
|
||||
errors: Vec<AsanError>,
|
||||
}
|
||||
|
||||
impl AsanErrors {
|
||||
/// Creates a new `AsanErrors` struct
|
||||
#[must_use]
|
||||
pub fn new(options: FridaOptions) -> Self {
|
||||
Self {
|
||||
options,
|
||||
errors: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears this `AsanErrors` struct
|
||||
pub fn clear(&mut self) {
|
||||
self.errors.clear()
|
||||
}
|
||||
|
||||
/// Gets the amount of `AsanErrors` in this struct
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.errors.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if no errors occurred
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.errors.is_empty()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the global [`AsanErrors`] object
|
||||
pub fn get_mut<'a>() -> &'a mut Self {
|
||||
unsafe { ASAN_ERRORS.as_mut().unwrap() }
|
||||
}
|
||||
|
||||
/// Report an error
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn report_error(
|
||||
&mut self,
|
||||
error: AsanError,
|
||||
instrumented_ranges: Option<&RangeMap<usize, String>>,
|
||||
) {
|
||||
self.errors.push(error.clone());
|
||||
|
||||
let mut out_stream = default_output_stream();
|
||||
let output = out_stream.as_mut();
|
||||
|
||||
let backtrace_printer = BacktracePrinter::new()
|
||||
.clear_frame_filters()
|
||||
.print_addresses(true)
|
||||
.verbosity(Verbosity::Full)
|
||||
.add_frame_filter(Box::new(|frames| {
|
||||
frames.retain(
|
||||
|x| matches!(&x.name, Some(n) if !n.starts_with("libafl_frida::asan_rt::")),
|
||||
)
|
||||
}));
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " Memory error detected! ").unwrap();
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Red)))
|
||||
.unwrap();
|
||||
write!(output, "{}", error.description()).unwrap();
|
||||
match error {
|
||||
AsanError::OobRead(mut error)
|
||||
| AsanError::OobWrite(mut error)
|
||||
| AsanError::ReadAfterFree(mut error)
|
||||
| AsanError::WriteAfterFree(mut error) => {
|
||||
let (basereg, indexreg, _displacement, fault_address) = error.fault;
|
||||
|
||||
if let Some((range, path)) = instrumented_ranges.unwrap().get_key_value(&error.pc) {
|
||||
writeln!(
|
||||
output,
|
||||
" at 0x{:x} ({}@0x{:04x}), faulting address 0x{:x}",
|
||||
error.pc,
|
||||
path,
|
||||
error.pc - range.start,
|
||||
fault_address
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" at 0x{:x}, faulting address 0x{:x}",
|
||||
error.pc, fault_address
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
output.reset().unwrap();
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " REGISTERS ").unwrap();
|
||||
for reg in 0..=30 {
|
||||
if reg == basereg {
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Red)))
|
||||
.unwrap();
|
||||
} else if reg == indexreg {
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))
|
||||
.unwrap();
|
||||
}
|
||||
write!(
|
||||
output,
|
||||
"x{:02}: 0x{:016x} ",
|
||||
reg, error.registers[reg as usize]
|
||||
)
|
||||
.unwrap();
|
||||
output.reset().unwrap();
|
||||
if reg % 4 == 3 {
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(output, "pc : 0x{:016x} ", error.pc).unwrap();
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " CODE ").unwrap();
|
||||
let mut cs = Capstone::new()
|
||||
.arm64()
|
||||
.mode(capstone::arch::arm64::ArchMode::Arm)
|
||||
.build()
|
||||
.unwrap();
|
||||
cs.set_skipdata(true).expect("failed to set skipdata");
|
||||
|
||||
let start_pc = error.pc - 4 * 5;
|
||||
for insn in cs
|
||||
.disasm_count(
|
||||
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 4 * 11) },
|
||||
start_pc as u64,
|
||||
11,
|
||||
)
|
||||
.expect("failed to disassemble instructions")
|
||||
.iter()
|
||||
{
|
||||
if insn.address() as usize == error.pc {
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Red)))
|
||||
.unwrap();
|
||||
writeln!(output, "\t => {}", insn).unwrap();
|
||||
output.reset().unwrap();
|
||||
} else {
|
||||
writeln!(output, "\t {}", insn).unwrap();
|
||||
}
|
||||
}
|
||||
backtrace_printer
|
||||
.print_trace(&error.backtrace, output)
|
||||
.unwrap();
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " ALLOCATION INFO ").unwrap();
|
||||
let offset: i64 = fault_address as i64 - error.metadata.address as i64;
|
||||
let direction = if offset > 0 { "right" } else { "left" };
|
||||
writeln!(
|
||||
output,
|
||||
"access is {} to the {} of the 0x{:x} byte allocation at 0x{:x}",
|
||||
offset, direction, error.metadata.size, error.metadata.address
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if error.metadata.is_malloc_zero {
|
||||
writeln!(output, "allocation was zero-sized").unwrap();
|
||||
}
|
||||
|
||||
if let Some(backtrace) = error.metadata.allocation_site_backtrace.as_mut() {
|
||||
writeln!(output, "allocation site backtrace:").unwrap();
|
||||
backtrace.resolve();
|
||||
backtrace_printer.print_trace(backtrace, output).unwrap();
|
||||
}
|
||||
|
||||
if error.metadata.freed {
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " FREE INFO ").unwrap();
|
||||
if let Some(backtrace) = error.metadata.release_site_backtrace.as_mut() {
|
||||
writeln!(output, "free site backtrace:").unwrap();
|
||||
backtrace.resolve();
|
||||
backtrace_printer.print_trace(backtrace, output).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
AsanError::BadFuncArgRead((name, address, size, backtrace))
|
||||
| AsanError::BadFuncArgWrite((name, address, size, backtrace)) => {
|
||||
writeln!(
|
||||
output,
|
||||
" in call to {}, argument {:#016x}, size: {:#x}",
|
||||
name, address, size
|
||||
)
|
||||
.unwrap();
|
||||
let invocation = Interceptor::current_invocation();
|
||||
let cpu_context = invocation.cpu_context();
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " REGISTERS ").unwrap();
|
||||
for reg in 0..29 {
|
||||
let val = cpu_context.reg(reg);
|
||||
if val as usize == address {
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Red)))
|
||||
.unwrap();
|
||||
}
|
||||
write!(output, "x{:02}: 0x{:016x} ", reg, val).unwrap();
|
||||
output.reset().unwrap();
|
||||
if reg % 4 == 3 {
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
}
|
||||
write!(output, "sp : 0x{:016x} ", cpu_context.sp()).unwrap();
|
||||
write!(output, "lr : 0x{:016x} ", cpu_context.lr()).unwrap();
|
||||
writeln!(output, "pc : 0x{:016x} ", cpu_context.pc()).unwrap();
|
||||
}
|
||||
|
||||
backtrace_printer.print_trace(&backtrace, output).unwrap();
|
||||
}
|
||||
AsanError::DoubleFree((ptr, mut metadata, backtrace)) => {
|
||||
writeln!(output, " of {:?}", ptr).unwrap();
|
||||
output.reset().unwrap();
|
||||
backtrace_printer.print_trace(&backtrace, output).unwrap();
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " ALLOCATION INFO ").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"allocation at 0x{:x}, with size 0x{:x}",
|
||||
metadata.address, metadata.size
|
||||
)
|
||||
.unwrap();
|
||||
if metadata.is_malloc_zero {
|
||||
writeln!(output, "allocation was zero-sized").unwrap();
|
||||
}
|
||||
|
||||
if let Some(backtrace) = metadata.allocation_site_backtrace.as_mut() {
|
||||
writeln!(output, "allocation site backtrace:").unwrap();
|
||||
backtrace.resolve();
|
||||
backtrace_printer.print_trace(backtrace, output).unwrap();
|
||||
}
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " FREE INFO ").unwrap();
|
||||
if let Some(backtrace) = metadata.release_site_backtrace.as_mut() {
|
||||
writeln!(output, "previous free site backtrace:").unwrap();
|
||||
backtrace.resolve();
|
||||
backtrace_printer.print_trace(backtrace, output).unwrap();
|
||||
}
|
||||
}
|
||||
AsanError::UnallocatedFree((ptr, backtrace)) => {
|
||||
writeln!(output, " of {:#016x}", ptr).unwrap();
|
||||
output.reset().unwrap();
|
||||
backtrace_printer.print_trace(&backtrace, output).unwrap();
|
||||
}
|
||||
AsanError::Leak((ptr, mut metadata)) => {
|
||||
writeln!(output, " of {:#016x}", ptr).unwrap();
|
||||
output.reset().unwrap();
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " ALLOCATION INFO ").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"allocation at 0x{:x}, with size 0x{:x}",
|
||||
metadata.address, metadata.size
|
||||
)
|
||||
.unwrap();
|
||||
if metadata.is_malloc_zero {
|
||||
writeln!(output, "allocation was zero-sized").unwrap();
|
||||
}
|
||||
|
||||
if let Some(backtrace) = metadata.allocation_site_backtrace.as_mut() {
|
||||
writeln!(output, "allocation site backtrace:").unwrap();
|
||||
backtrace.resolve();
|
||||
backtrace_printer.print_trace(backtrace, output).unwrap();
|
||||
}
|
||||
}
|
||||
AsanError::Unknown((registers, pc, fault, backtrace))
|
||||
| AsanError::StackOobRead((registers, pc, fault, backtrace))
|
||||
| AsanError::StackOobWrite((registers, pc, fault, backtrace)) => {
|
||||
let (basereg, indexreg, _displacement, fault_address) = fault;
|
||||
|
||||
if let Ok((start, _, _, path)) = find_mapping_for_address(pc) {
|
||||
writeln!(
|
||||
output,
|
||||
" at 0x{:x} ({}:0x{:04x}), faulting address 0x{:x}",
|
||||
pc,
|
||||
path,
|
||||
pc - start,
|
||||
fault_address
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" at 0x{:x}, faulting address 0x{:x}",
|
||||
pc, fault_address
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
output.reset().unwrap();
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " REGISTERS ").unwrap();
|
||||
for reg in 0..=30 {
|
||||
if reg == basereg {
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Red)))
|
||||
.unwrap();
|
||||
} else if reg == indexreg {
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))
|
||||
.unwrap();
|
||||
}
|
||||
write!(output, "x{:02}: 0x{:016x} ", reg, registers[reg as usize]).unwrap();
|
||||
output.reset().unwrap();
|
||||
if reg % 4 == 3 {
|
||||
writeln!(output).unwrap();
|
||||
}
|
||||
}
|
||||
writeln!(output, "pc : 0x{:016x} ", pc).unwrap();
|
||||
|
||||
#[allow(clippy::non_ascii_literal)]
|
||||
writeln!(output, "{:━^100}", " CODE ").unwrap();
|
||||
let mut cs = Capstone::new()
|
||||
.arm64()
|
||||
.mode(capstone::arch::arm64::ArchMode::Arm)
|
||||
.build()
|
||||
.unwrap();
|
||||
cs.set_skipdata(true).expect("failed to set skipdata");
|
||||
|
||||
let start_pc = pc - 4 * 5;
|
||||
for insn in cs
|
||||
.disasm_count(
|
||||
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 4 * 11) },
|
||||
start_pc as u64,
|
||||
11,
|
||||
)
|
||||
.expect("failed to disassemble instructions")
|
||||
.iter()
|
||||
{
|
||||
if insn.address() as usize == pc {
|
||||
output
|
||||
.set_color(ColorSpec::new().set_fg(Some(Color::Red)))
|
||||
.unwrap();
|
||||
writeln!(output, "\t => {}", insn).unwrap();
|
||||
output.reset().unwrap();
|
||||
} else {
|
||||
writeln!(output, "\t {}", insn).unwrap();
|
||||
}
|
||||
}
|
||||
backtrace_printer.print_trace(&backtrace, output).unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
if !self.options.asan_continue_after_error() {
|
||||
panic!("Crashing target!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// static field for `AsanErrors` for a run
|
||||
pub static mut ASAN_ERRORS: Option<AsanErrors> = None;
|
||||
|
||||
/// An observer for frida address sanitizer `AsanError`s for a frida executor run
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[allow(clippy::unsafe_derive_deserialize)]
|
||||
pub struct AsanErrorsObserver {
|
||||
errors: OwnedPtr<Option<AsanErrors>>,
|
||||
}
|
||||
|
||||
impl Observer for AsanErrorsObserver {}
|
||||
|
||||
impl<EM, I, S, Z> HasExecHooks<EM, I, S, Z> for AsanErrorsObserver {
|
||||
fn pre_exec(
|
||||
&mut self,
|
||||
_fuzzer: &mut Z,
|
||||
_state: &mut S,
|
||||
_mgr: &mut EM,
|
||||
_input: &I,
|
||||
) -> Result<(), Error> {
|
||||
unsafe {
|
||||
if ASAN_ERRORS.is_some() {
|
||||
ASAN_ERRORS.as_mut().unwrap().clear();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for AsanErrorsObserver {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"AsanErrors"
|
||||
}
|
||||
}
|
||||
|
||||
impl AsanErrorsObserver {
|
||||
/// Creates a new `AsanErrorsObserver`, pointing to a constant `AsanErrors` field
|
||||
#[must_use]
|
||||
pub fn new(errors: &'static Option<AsanErrors>) -> Self {
|
||||
Self {
|
||||
errors: OwnedPtr::Ptr(errors as *const Option<AsanErrors>),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `AsanErrorsObserver`, owning the `AsanErrors`
|
||||
#[must_use]
|
||||
pub fn new_owned(errors: Option<AsanErrors>) -> Self {
|
||||
Self {
|
||||
errors: OwnedPtr::Owned(Box::new(errors)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `AsanErrorsObserver` from a raw ptr
|
||||
#[must_use]
|
||||
pub fn new_from_ptr(errors: *const Option<AsanErrors>) -> Self {
|
||||
Self {
|
||||
errors: OwnedPtr::Ptr(errors),
|
||||
}
|
||||
}
|
||||
|
||||
/// gets the [`AsanErrors`] from the previous run
|
||||
#[must_use]
|
||||
pub fn errors(&self) -> Option<&AsanErrors> {
|
||||
match &self.errors {
|
||||
OwnedPtr::Ptr(p) => unsafe { p.as_ref().unwrap().as_ref() },
|
||||
OwnedPtr::Owned(b) => b.as_ref().as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A feedback reporting potential [`AsanErrors`] from an `AsanErrorsObserver`
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct AsanErrorsFeedback {
|
||||
errors: Option<AsanErrors>,
|
||||
}
|
||||
|
||||
impl<I, S> Feedback<I, S> for AsanErrorsFeedback
|
||||
where
|
||||
I: Input + HasTargetBytes,
|
||||
{
|
||||
fn is_interesting<EM, OT>(
|
||||
&mut self,
|
||||
_state: &mut S,
|
||||
_manager: &mut EM,
|
||||
_input: &I,
|
||||
observers: &OT,
|
||||
_exit_kind: &ExitKind,
|
||||
) -> Result<bool, Error>
|
||||
where
|
||||
EM: EventFirer<I, S>,
|
||||
OT: ObserversTuple,
|
||||
{
|
||||
let observer = observers
|
||||
.match_name::<AsanErrorsObserver>("AsanErrors")
|
||||
.expect("An AsanErrorsFeedback needs an AsanErrorsObserver");
|
||||
match observer.errors() {
|
||||
None => Ok(false),
|
||||
Some(errors) => {
|
||||
if errors.errors.is_empty() {
|
||||
Ok(false)
|
||||
} else {
|
||||
self.errors = Some(errors.clone());
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn append_metadata(&mut self, _state: &mut S, testcase: &mut Testcase<I>) -> Result<(), Error> {
|
||||
if let Some(errors) = &self.errors {
|
||||
testcase.add_metadata(errors.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn discard_metadata(&mut self, _state: &mut S, _input: &I) -> Result<(), Error> {
|
||||
self.errors = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Named for AsanErrorsFeedback {
|
||||
#[inline]
|
||||
fn name(&self) -> &str {
|
||||
"AsanErrors"
|
||||
}
|
||||
}
|
||||
|
||||
impl AsanErrorsFeedback {
|
||||
/// Create a new `AsanErrorsFeedback`
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self { errors: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AsanErrorsFeedback {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,6 @@ use capstone::{
|
||||
Capstone, Insn,
|
||||
};
|
||||
|
||||
use core::cell::RefCell;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use frida_gum::instruction_writer::X86Register;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@ -34,7 +33,7 @@ use frida_gum::{Gum, Module, PageProtection};
|
||||
use num_traits::cast::FromPrimitive;
|
||||
|
||||
use rangemap::RangeMap;
|
||||
use std::{path::PathBuf, rc::Rc};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
|
||||
|
||||
@ -46,7 +45,7 @@ pub trait FridaHelper<'a> {
|
||||
fn transformer(&self) -> &Transformer<'a>;
|
||||
|
||||
/// Register a new thread with this `FridaHelper`
|
||||
fn register_thread(&self);
|
||||
fn register_thread(&mut self);
|
||||
|
||||
/// Called prior to execution of an input
|
||||
fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I);
|
||||
@ -59,6 +58,8 @@ pub trait FridaHelper<'a> {
|
||||
|
||||
/// pointer to the frida coverage map
|
||||
fn map_ptr(&mut self) -> *mut u8;
|
||||
|
||||
fn ranges(&self) -> &RangeMap<usize, (u16, &str)>;
|
||||
}
|
||||
|
||||
/// (Default) map size for frida coverage reporting
|
||||
@ -75,7 +76,7 @@ pub struct FridaInstrumentationHelper<'a> {
|
||||
transformer: Option<Transformer<'a>>,
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
capstone: Capstone,
|
||||
asan_runtime: Rc<RefCell<AsanRuntime>>,
|
||||
asan_runtime: AsanRuntime,
|
||||
ranges: RangeMap<usize, (u16, &'a str)>,
|
||||
options: &'a FridaOptions,
|
||||
drcov_basic_blocks: Vec<DrCovBasicBlock>,
|
||||
@ -87,8 +88,8 @@ impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> {
|
||||
}
|
||||
|
||||
/// Register the current thread with the [`FridaInstrumentationHelper`]
|
||||
fn register_thread(&self) {
|
||||
self.asan_runtime.borrow().register_thread();
|
||||
fn register_thread(&mut self) {
|
||||
self.asan_runtime.register_thread();
|
||||
}
|
||||
|
||||
fn pre_exec<I: Input + HasTargetBytes>(&mut self, input: &I) {
|
||||
@ -96,7 +97,6 @@ impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> {
|
||||
let slice = target_bytes.as_slice();
|
||||
//println!("target_bytes: {:02x?}", slice);
|
||||
self.asan_runtime
|
||||
.borrow()
|
||||
.unpoison(slice.as_ptr() as usize, slice.len());
|
||||
}
|
||||
|
||||
@ -111,9 +111,9 @@ impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> {
|
||||
|
||||
if self.options.asan_enabled() {
|
||||
if self.options.asan_detect_leaks() {
|
||||
self.asan_runtime.borrow_mut().check_for_leaks();
|
||||
self.asan_runtime.check_for_leaks();
|
||||
}
|
||||
self.asan_runtime.borrow_mut().reset_allocations();
|
||||
self.asan_runtime.reset_allocations();
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +124,10 @@ impl<'a> FridaHelper<'a> for FridaInstrumentationHelper<'a> {
|
||||
fn map_ptr(&mut self) -> *mut u8 {
|
||||
self.map.as_mut_ptr()
|
||||
}
|
||||
|
||||
fn ranges(&self) -> &RangeMap<usize, (u16, &str)> {
|
||||
&self.ranges
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to get the size of a module's CODE section from frida
|
||||
@ -312,7 +316,6 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
||||
instruction.put_callout(|context| {
|
||||
let real_address = match helper
|
||||
.asan_runtime
|
||||
.borrow()
|
||||
.real_address_for_stalked(pc(&context))
|
||||
{
|
||||
Some(address) => *address,
|
||||
@ -347,7 +350,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
||||
}
|
||||
}
|
||||
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
|
||||
helper.asan_runtime.borrow_mut().add_stalked_address(
|
||||
helper.asan_runtime.add_stalked_address(
|
||||
output.writer().pc() as usize - 4,
|
||||
address as usize,
|
||||
);
|
||||
@ -358,7 +361,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
||||
});
|
||||
helper.transformer = Some(transformer);
|
||||
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
|
||||
helper.asan_runtime.borrow_mut().init(modules_to_instrument);
|
||||
helper.asan_runtime.init(gum, modules_to_instrument);
|
||||
}
|
||||
}
|
||||
helper
|
||||
@ -410,7 +413,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
||||
writer.put_b_label(after_report_impl);
|
||||
|
||||
self.current_report_impl = writer.pc();
|
||||
writer.put_bytes(self.asan_runtime.borrow().blob_report());
|
||||
writer.put_bytes(self.asan_runtime.blob_report());
|
||||
|
||||
writer.put_label(after_report_impl);
|
||||
}
|
||||
@ -547,18 +550,18 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
||||
}
|
||||
// Insert the check_shadow_mem code blob
|
||||
match width {
|
||||
1 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_byte()),
|
||||
2 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_halfword()),
|
||||
3 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_3bytes()),
|
||||
4 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_dword()),
|
||||
6 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_6bytes()),
|
||||
8 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_qword()),
|
||||
12 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_12bytes()),
|
||||
16 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_16bytes()),
|
||||
24 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_24bytes()),
|
||||
32 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_32bytes()),
|
||||
48 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_48bytes()),
|
||||
64 => writer.put_bytes(&self.asan_runtime.borrow().blob_check_mem_64bytes()),
|
||||
1 => writer.put_bytes(&self.asan_runtime.blob_check_mem_byte()),
|
||||
2 => writer.put_bytes(&self.asan_runtime.blob_check_mem_halfword()),
|
||||
3 => writer.put_bytes(&self.asan_runtime.blob_check_mem_3bytes()),
|
||||
4 => writer.put_bytes(&self.asan_runtime.blob_check_mem_dword()),
|
||||
6 => writer.put_bytes(&self.asan_runtime.blob_check_mem_6bytes()),
|
||||
8 => writer.put_bytes(&self.asan_runtime.blob_check_mem_qword()),
|
||||
12 => writer.put_bytes(&self.asan_runtime.blob_check_mem_12bytes()),
|
||||
16 => writer.put_bytes(&self.asan_runtime.blob_check_mem_16bytes()),
|
||||
24 => writer.put_bytes(&self.asan_runtime.blob_check_mem_24bytes()),
|
||||
32 => writer.put_bytes(&self.asan_runtime.blob_check_mem_32bytes()),
|
||||
48 => writer.put_bytes(&self.asan_runtime.blob_check_mem_48bytes()),
|
||||
64 => writer.put_bytes(&self.asan_runtime.blob_check_mem_64bytes()),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
@ -3,17 +3,22 @@ The frida executor is a binary-only mode for `LibAFL`.
|
||||
It can report coverage and, on supported architecutres, even reports memory access errors.
|
||||
*/
|
||||
|
||||
/// The frida-asan allocator
|
||||
pub mod alloc;
|
||||
/// Handling of ASAN errors
|
||||
pub mod asan_errors;
|
||||
/// The frida address sanitizer runtime
|
||||
pub mod asan_rt;
|
||||
/// The `LibAFL` frida helper
|
||||
pub mod helper;
|
||||
|
||||
// for parsing asan cores
|
||||
use libafl::bolts::os::parse_core_bind_arg;
|
||||
// for getting current core_id
|
||||
use core_affinity::get_core_ids;
|
||||
|
||||
/// A representation of the various Frida options
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct FridaOptions {
|
||||
enable_asan: bool,
|
||||
|
Loading…
x
Reference in New Issue
Block a user