Cmplog instrumentation for Frida (#99)
* libafl_targets: refactor sancov trace-pc * cmp observer * libaf_targets: new structure to isolate sancov * fix C warning * combined executor * cmp observer and feedback * I2SRandReplace mutator * impl CmpMap for CmpLogMap in libafl_targets * cmplog observer * clippy * TracingStage * working random cmplog mutations * enable cmplog for libfuzzer_stb_image * re-enable new testcase stats print * fix update stats display * bump 0.3.1 * clippy * clippy * no clippy for fuzzers/ * fix * add cmplog runtime instrumentation * test cmplog against value profile feature * fix compile error * add target arch aarch64 for is_interesting_cmplog_instruction * add cfg target aarch64 on cmplog related code within stalker loop * revert changes in cargo.toml * align code with 'main' branch * revert accidently changed Cargo.toml file * update cmplog runtime code to work with the cmplog backend implementation * change magic to 8 bytes * cmplog runs with observer- no crashes * clippy fixes * add cmplog_runtime as feature * set cmplog command-line argument to false by default * setup cmplog observer and mutator correctly * decrease emitted code opcode count * add cmplog testing to the harness * get rid of irrelevant changes and unused code, add comments, change feature name to "cmplog" * get rid of some unessecery whitespaces and new lines * fix clippy errors Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com> Co-authored-by: Dominik Maier <domenukk@gmail.com> Co-authored-by: Omree <you@example.com>
This commit is contained in:
parent
9e9425c622
commit
7abd7c8162
@ -21,10 +21,11 @@ num_cpus = "1.0"
|
|||||||
which = "4.1"
|
which = "4.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libafl = { path = "../../libafl/", features = [ "std", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]}
|
libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]}
|
||||||
libafl_frida = { path = "../../libafl_frida" }
|
|
||||||
capstone = "0.8.0"
|
capstone = "0.8.0"
|
||||||
frida-gum = { version = "0.5.2", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
|
frida-gum = { version = "0.5.2", features = [ "auto-download", "backtrace", "event-sink", "invocation-listener"] }
|
||||||
|
libafl_frida = { path = "../../libafl_frida", version = "0.3.2", features = ["cmplog"] }
|
||||||
|
libafl_targets = { path = "../../libafl_targets", version = "0.3.2" , features = ["sancov_cmplog"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
libloading = "0.7.0"
|
libloading = "0.7.0"
|
||||||
|
@ -108,6 +108,10 @@ void func1() {
|
|||||||
// Roughly follows the libpng book example:
|
// Roughly follows the libpng book example:
|
||||||
// http://www.libpng.org/pub/png/book/chapter13.html
|
// http://www.libpng.org/pub/png/book/chapter13.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||||
|
|
||||||
|
if (size >= 8 && *(uint64_t*)data == 0xABCDEFAA8F1324AA){
|
||||||
|
abort();
|
||||||
|
}
|
||||||
if (size < kPngHeaderSize) {
|
if (size < kPngHeaderSize) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -186,11 +190,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||||||
if (width && height > 100000000 / width) {
|
if (width && height > 100000000 / width) {
|
||||||
PNG_CLEANUP
|
PNG_CLEANUP
|
||||||
#ifdef HAS_DUMMY_CRASH
|
#ifdef HAS_DUMMY_CRASH
|
||||||
#ifdef __aarch64__
|
#ifdef __aarch64__
|
||||||
asm volatile (".word 0xf7f0a000\n");
|
asm volatile (".word 0xf7f0a000\n");
|
||||||
#else
|
#else
|
||||||
asm("ud2");
|
asm("ud2");
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,11 @@ use libafl::{
|
|||||||
inputs::{BytesInput, HasTargetBytes, Input},
|
inputs::{BytesInput, HasTargetBytes, Input},
|
||||||
mutators::{
|
mutators::{
|
||||||
scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
|
scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator},
|
||||||
|
token_mutations::I2SRandReplace,
|
||||||
token_mutations::Tokens,
|
token_mutations::Tokens,
|
||||||
},
|
},
|
||||||
observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver, TimeObserver},
|
observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver, TimeObserver},
|
||||||
stages::mutational::StdMutationalStage,
|
stages::{StdMutationalStage, TracingStage},
|
||||||
state::{HasCorpus, HasMetadata, StdState},
|
state::{HasCorpus, HasMetadata, StdState},
|
||||||
stats::MultiStats,
|
stats::MultiStats,
|
||||||
Error,
|
Error,
|
||||||
@ -57,6 +58,7 @@ use libafl_frida::{
|
|||||||
helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE},
|
helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE},
|
||||||
FridaOptions,
|
FridaOptions,
|
||||||
};
|
};
|
||||||
|
use libafl_targets::cmplog::{CmpLogObserver, CMPLOG_MAP};
|
||||||
|
|
||||||
struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT, S>
|
struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT, S>
|
||||||
where
|
where
|
||||||
@ -300,7 +302,7 @@ unsafe fn fuzz(
|
|||||||
unsafe extern "C" fn(data: *const u8, size: usize) -> i32,
|
unsafe extern "C" fn(data: *const u8, size: usize) -> i32,
|
||||||
> = lib.get(symbol_name.as_bytes()).unwrap();
|
> = lib.get(symbol_name.as_bytes()).unwrap();
|
||||||
|
|
||||||
let mut frida_harness = move |input: &BytesInput| {
|
let mut frida_harness = |input: &BytesInput| {
|
||||||
let target = input.target_bytes();
|
let target = input.target_bytes();
|
||||||
let buf = target.as_slice();
|
let buf = target.as_slice();
|
||||||
(target_func)(buf.as_ptr(), buf.len());
|
(target_func)(buf.as_ptr(), buf.len());
|
||||||
@ -316,6 +318,9 @@ unsafe fn fuzz(
|
|||||||
&modules_to_instrument,
|
&modules_to_instrument,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Create an observation channel using cmplog map
|
||||||
|
let cmplog_observer = CmpLogObserver::new("cmplog", &mut CMPLOG_MAP, true);
|
||||||
|
|
||||||
// Create an observation channel using the coverage map
|
// Create an observation channel using the coverage map
|
||||||
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr(
|
let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr(
|
||||||
"edges",
|
"edges",
|
||||||
@ -378,7 +383,6 @@ unsafe fn fuzz(
|
|||||||
|
|
||||||
// Setup a basic mutator with a mutational stage
|
// Setup a basic mutator with a mutational stage
|
||||||
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
let mutator = StdScheduledMutator::new(havoc_mutations().merge(tokens_mutations()));
|
||||||
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
|
||||||
|
|
||||||
// A minimization+queue policy to get testcasess from the corpus
|
// A minimization+queue policy to get testcasess from the corpus
|
||||||
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new());
|
||||||
@ -413,7 +417,54 @@ unsafe fn fuzz(
|
|||||||
println!("We imported {} inputs from disk.", state.corpus().count());
|
println!("We imported {} inputs from disk.", state.corpus().count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if frida_options.cmplog_enabled() {
|
||||||
|
// Secondary harness due to mut ownership
|
||||||
|
let mut frida_harness = |input: &BytesInput| {
|
||||||
|
let target = input.target_bytes();
|
||||||
|
let buf = target.as_slice();
|
||||||
|
(target_func)(buf.as_ptr(), buf.len());
|
||||||
|
ExitKind::Ok
|
||||||
|
};
|
||||||
|
|
||||||
|
// Secondary helper due to mut ownership
|
||||||
|
let mut frida_helper = FridaInstrumentationHelper::new(
|
||||||
|
&gum,
|
||||||
|
&frida_options,
|
||||||
|
module_name,
|
||||||
|
&modules_to_instrument,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Setup a tracing stage in which we log comparisons
|
||||||
|
let tracing = TracingStage::new(FridaInProcessExecutor::new(
|
||||||
|
&gum,
|
||||||
|
InProcessExecutor::new(
|
||||||
|
&mut frida_harness,
|
||||||
|
tuple_list!(cmplog_observer, AsanErrorsObserver::new(&ASAN_ERRORS)),
|
||||||
|
&mut fuzzer,
|
||||||
|
&mut state,
|
||||||
|
&mut mgr,
|
||||||
|
)?,
|
||||||
|
&mut frida_helper,
|
||||||
|
Duration::new(10, 0),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Setup a randomic Input2State stage
|
||||||
|
let i2s = StdMutationalStage::new(StdScheduledMutator::new(tuple_list!(
|
||||||
|
I2SRandReplace::new()
|
||||||
|
)));
|
||||||
|
|
||||||
|
// Setup a basic mutator
|
||||||
|
let mutational = StdMutationalStage::new(mutator);
|
||||||
|
|
||||||
|
// The order of the stages matter!
|
||||||
|
let mut stages = tuple_list!(tracing, i2s, mutational);
|
||||||
|
|
||||||
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
|
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
|
||||||
|
} else {
|
||||||
|
let mut stages = tuple_list!(StdMutationalStage::new(mutator));
|
||||||
|
|
||||||
|
fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?;
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ use alloc::{
|
|||||||
string::{String, ToString},
|
string::{String, ToString},
|
||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -10,12 +10,17 @@ license = "MIT OR Apache-2.0"
|
|||||||
keywords = ["fuzzing", "frida", "instrumentation"]
|
keywords = ["fuzzing", "frida", "instrumentation"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
cmplog = []
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = { version = "1.0", features = ["parallel"] }
|
cc = { version = "1.0", features = ["parallel"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libafl = { path = "../libafl", version = "0.3.1", features = ["std", "libafl_derive"] }
|
libafl = { path = "../libafl", version = "0.3.1", features = ["std", "libafl_derive"] }
|
||||||
libafl_targets = { path = "../libafl_targets", version = "0.3.2" }
|
libafl_targets = { path = "../libafl_targets", version = "0.3.2", features = ["sancov_cmplog"] }
|
||||||
nix = "0.20.0"
|
nix = "0.20.0"
|
||||||
libc = "0.2.92"
|
libc = "0.2.92"
|
||||||
hashbrown = "0.11"
|
hashbrown = "0.11"
|
||||||
|
101
libafl_frida/src/cmplog_rt.rs
Normal file
101
libafl_frida/src/cmplog_rt.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi};
|
||||||
|
use libafl_targets::cmplog::CMPLOG_MAP_W;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
extern crate libafl_targets;
|
||||||
|
extern "C" {
|
||||||
|
pub fn libafl_targets_cmplog_wrapper(k: u64, shape: u8, arg1: u64, arg2: u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CmpLogRuntime {
|
||||||
|
ops_save_register_and_blr_to_populate: Option<Box<[u8]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmpLogRuntime {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> CmpLogRuntime {
|
||||||
|
Self {
|
||||||
|
ops_save_register_and_blr_to_populate: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the external function that populates the `cmplog_map` with the relevant values
|
||||||
|
extern "C" fn populate_lists(&mut self, op1: u64, op2: u64, retaddr: u64) {
|
||||||
|
// println!(
|
||||||
|
// "entered populate_lists with: {:#02x}, {:#02x}, {:#02x}",
|
||||||
|
// op1, op2, retaddr
|
||||||
|
// );
|
||||||
|
let mut k = (retaddr >> 4) ^ (retaddr << 8);
|
||||||
|
|
||||||
|
k &= (CMPLOG_MAP_W as u64) - 1;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
libafl_targets_cmplog_wrapper(k, 8, op1, op2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate the instrumentation blobs for the current arch.
|
||||||
|
fn generate_instrumentation_blobs(&mut self) {
|
||||||
|
macro_rules! blr_to_populate {
|
||||||
|
($ops:ident) => {dynasm!($ops
|
||||||
|
; .arch aarch64
|
||||||
|
; stp x2, x3, [sp, #-0x10]!
|
||||||
|
; stp x4, x5, [sp, #-0x10]!
|
||||||
|
; stp x6, x7, [sp, #-0x10]!
|
||||||
|
; stp x8, x9, [sp, #-0x10]!
|
||||||
|
; stp x10, x11, [sp, #-0x10]!
|
||||||
|
; stp x12, x13, [sp, #-0x10]!
|
||||||
|
; stp x14, x15, [sp, #-0x10]!
|
||||||
|
; stp x29, x30, [sp, #-0x10]!
|
||||||
|
// jump to rust based population of the lists
|
||||||
|
; mov x2, x0
|
||||||
|
; adr x3, >done
|
||||||
|
; ldr x4, >populate_lists
|
||||||
|
; ldr x0, >self_addr
|
||||||
|
; blr x4
|
||||||
|
// restore the reg state before returning to the caller
|
||||||
|
; ldp x29, x30, [sp], #0x10
|
||||||
|
; ldp x14, x15, [sp], #0x10
|
||||||
|
; ldp x12, x13, [sp], #0x10
|
||||||
|
; ldp x10, x11, [sp], #0x10
|
||||||
|
; ldp x8, x9, [sp], #0x10
|
||||||
|
; ldp x6, x7, [sp], #0x10
|
||||||
|
; ldp x4, x5, [sp], #0x10
|
||||||
|
; ldp x2, x3, [sp], #0x10
|
||||||
|
; b >done
|
||||||
|
; self_addr:
|
||||||
|
; .qword self as *mut _ as *mut c_void as i64
|
||||||
|
; populate_lists:
|
||||||
|
; .qword CmpLogRuntime::populate_lists as *mut c_void as i64
|
||||||
|
; done:
|
||||||
|
);};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ops_save_register_and_blr_to_populate =
|
||||||
|
dynasmrt::VecAssembler::<dynasmrt::aarch64::Aarch64Relocation>::new(0);
|
||||||
|
blr_to_populate!(ops_save_register_and_blr_to_populate);
|
||||||
|
|
||||||
|
self.ops_save_register_and_blr_to_populate = Some(
|
||||||
|
ops_save_register_and_blr_to_populate
|
||||||
|
.finalize()
|
||||||
|
.unwrap()
|
||||||
|
.into_boxed_slice(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
pub fn init(&mut self) {
|
||||||
|
self.generate_instrumentation_blobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the blob which saves the context, jumps to the populate function and restores the context
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn ops_save_register_and_blr_to_populate(&self) -> &[u8] {
|
||||||
|
self.ops_save_register_and_blr_to_populate.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CmpLogRuntime {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
@ -33,9 +33,18 @@ use rangemap::RangeMap;
|
|||||||
|
|
||||||
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
|
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
|
||||||
|
|
||||||
use crate::FridaOptions;
|
use crate::{asan_rt::AsanRuntime, FridaOptions};
|
||||||
|
|
||||||
use crate::asan_rt::AsanRuntime;
|
#[cfg(feature = "cmplog")]
|
||||||
|
use crate::cmplog_rt::CmpLogRuntime;
|
||||||
|
|
||||||
|
#[cfg(feature = "cmplog")]
|
||||||
|
enum CmplogOperandType {
|
||||||
|
Regid(capstone::RegId),
|
||||||
|
Imm(u64),
|
||||||
|
Cimm(u64),
|
||||||
|
Mem(capstone::RegId, capstone::RegId, i32, u32),
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON;
|
const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON;
|
||||||
@ -80,6 +89,8 @@ pub struct FridaInstrumentationHelper<'a> {
|
|||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
capstone: Capstone,
|
capstone: Capstone,
|
||||||
asan_runtime: AsanRuntime,
|
asan_runtime: AsanRuntime,
|
||||||
|
#[cfg(feature = "cmplog")]
|
||||||
|
cmplog_runtime: CmpLogRuntime,
|
||||||
ranges: RangeMap<usize, (u16, String)>,
|
ranges: RangeMap<usize, (u16, String)>,
|
||||||
module_map: ModuleMap,
|
module_map: ModuleMap,
|
||||||
options: &'a FridaOptions,
|
options: &'a FridaOptions,
|
||||||
@ -265,6 +276,8 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
|||||||
.build()
|
.build()
|
||||||
.expect("Failed to create Capstone object"),
|
.expect("Failed to create Capstone object"),
|
||||||
asan_runtime: AsanRuntime::new(options.clone()),
|
asan_runtime: AsanRuntime::new(options.clone()),
|
||||||
|
#[cfg(feature = "cmplog")]
|
||||||
|
cmplog_runtime: CmpLogRuntime::new(),
|
||||||
ranges: RangeMap::new(),
|
ranges: RangeMap::new(),
|
||||||
module_map: ModuleMap::new_from_names(modules_to_instrument),
|
module_map: ModuleMap::new_from_names(modules_to_instrument),
|
||||||
options,
|
options,
|
||||||
@ -327,7 +340,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
|||||||
todo!("Implement ASAN for non-aarch64 targets");
|
todo!("Implement ASAN for non-aarch64 targets");
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
if let Ok((basereg, indexreg, displacement, width, shift, extender)) =
|
if let Ok((basereg, indexreg, displacement, width, shift, extender)) =
|
||||||
helper.is_interesting_instruction(address, instr)
|
helper.asan_is_interesting_instruction(address, instr)
|
||||||
{
|
{
|
||||||
helper.emit_shadow_check(
|
helper.emit_shadow_check(
|
||||||
address,
|
address,
|
||||||
@ -341,6 +354,19 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if helper.options().cmplog_enabled() {
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
|
todo!("Implement cmplog for non-aarch64 targets");
|
||||||
|
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
|
||||||
|
// check if this instruction is a compare instruction and if so save the registers values
|
||||||
|
if let Ok((op1, op2)) =
|
||||||
|
helper.cmplog_is_interesting_instruction(address, instr)
|
||||||
|
{
|
||||||
|
//emit code that saves the relevant data in runtime(passes it to x0, x1)
|
||||||
|
helper.emit_comparison_handling(address, &output, op1, op2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
|
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
|
||||||
helper.asan_runtime.add_stalked_address(
|
helper.asan_runtime.add_stalked_address(
|
||||||
output.writer().pc() as usize - 4,
|
output.writer().pc() as usize - 4,
|
||||||
@ -355,6 +381,10 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
|||||||
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
|
if helper.options().asan_enabled() || helper.options().drcov_enabled() {
|
||||||
helper.asan_runtime.init(gum, modules_to_instrument);
|
helper.asan_runtime.init(gum, modules_to_instrument);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "cmplog")]
|
||||||
|
if helper.options.cmplog_enabled() {
|
||||||
|
helper.cmplog_runtime.init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
helper
|
helper
|
||||||
}
|
}
|
||||||
@ -370,6 +400,312 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
|||||||
Aarch64Register::from_u32(regint as u32).unwrap()
|
Aarch64Register::from_u32(regint as u32).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
|
||||||
|
#[inline]
|
||||||
|
/// Emit the instrumentation code which is responsible for opernads value extraction and cmplog map population
|
||||||
|
fn emit_comparison_handling(
|
||||||
|
&self,
|
||||||
|
_address: u64,
|
||||||
|
output: &StalkerOutput,
|
||||||
|
op1: CmplogOperandType,
|
||||||
|
op2: CmplogOperandType,
|
||||||
|
) {
|
||||||
|
let writer = output.writer();
|
||||||
|
|
||||||
|
// Preserve x0, x1:
|
||||||
|
writer.put_stp_reg_reg_reg_offset(
|
||||||
|
Aarch64Register::X0,
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
-(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64,
|
||||||
|
IndexMode::PreAdjust,
|
||||||
|
);
|
||||||
|
|
||||||
|
// make sure operand1 value is saved into x0
|
||||||
|
match op1 {
|
||||||
|
CmplogOperandType::Imm(value) | CmplogOperandType::Cimm(value) => {
|
||||||
|
writer.put_ldr_reg_u64(Aarch64Register::X0, value);
|
||||||
|
}
|
||||||
|
CmplogOperandType::Regid(reg) => {
|
||||||
|
let reg = self.writer_register(reg);
|
||||||
|
match reg {
|
||||||
|
Aarch64Register::X0 | Aarch64Register::W0 => {}
|
||||||
|
Aarch64Register::X1 | Aarch64Register::W1 => {
|
||||||
|
writer.put_mov_reg_reg(Aarch64Register::X0, Aarch64Register::X1);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !writer.put_mov_reg_reg(Aarch64Register::X0, reg) {
|
||||||
|
writer.put_mov_reg_reg(Aarch64Register::W0, reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CmplogOperandType::Mem(basereg, indexreg, displacement, _width) => {
|
||||||
|
let basereg = self.writer_register(basereg);
|
||||||
|
let indexreg = if indexreg.0 != 0 {
|
||||||
|
Some(self.writer_register(indexreg))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// calculate base+index+displacment into x0
|
||||||
|
let displacement = displacement
|
||||||
|
+ if basereg == Aarch64Register::Sp {
|
||||||
|
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if indexreg.is_some() {
|
||||||
|
if let Some(indexreg) = indexreg {
|
||||||
|
writer.put_add_reg_reg_reg(Aarch64Register::X0, basereg, indexreg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match basereg {
|
||||||
|
Aarch64Register::X0 | Aarch64Register::W0 => {}
|
||||||
|
Aarch64Register::X1 | Aarch64Register::W1 => {
|
||||||
|
writer.put_mov_reg_reg(Aarch64Register::X0, Aarch64Register::X1);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !writer.put_mov_reg_reg(Aarch64Register::X0, basereg) {
|
||||||
|
writer.put_mov_reg_reg(Aarch64Register::W0, basereg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//add displacement
|
||||||
|
writer.put_add_reg_reg_imm(
|
||||||
|
Aarch64Register::X0,
|
||||||
|
Aarch64Register::X0,
|
||||||
|
displacement as u64,
|
||||||
|
);
|
||||||
|
|
||||||
|
//deref into x0 to get the real value
|
||||||
|
writer.put_ldr_reg_reg_offset(Aarch64Register::X0, Aarch64Register::X0, 0u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure operand2 value is saved into x1
|
||||||
|
match op2 {
|
||||||
|
CmplogOperandType::Imm(value) | CmplogOperandType::Cimm(value) => {
|
||||||
|
writer.put_ldr_reg_u64(Aarch64Register::X1, value);
|
||||||
|
}
|
||||||
|
CmplogOperandType::Regid(reg) => {
|
||||||
|
let reg = self.writer_register(reg);
|
||||||
|
match reg {
|
||||||
|
Aarch64Register::X1 | Aarch64Register::W1 => {}
|
||||||
|
Aarch64Register::X0 | Aarch64Register::W0 => {
|
||||||
|
writer.put_ldr_reg_reg_offset(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
0u64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if !writer.put_mov_reg_reg(Aarch64Register::X1, reg) {
|
||||||
|
writer.put_mov_reg_reg(Aarch64Register::W1, reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CmplogOperandType::Mem(basereg, indexreg, displacement, _width) => {
|
||||||
|
let basereg = self.writer_register(basereg);
|
||||||
|
let indexreg = if indexreg.0 != 0 {
|
||||||
|
Some(self.writer_register(indexreg))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// calculate base+index+displacment into x1
|
||||||
|
let displacement = displacement
|
||||||
|
+ if basereg == Aarch64Register::Sp {
|
||||||
|
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if indexreg.is_some() {
|
||||||
|
if let Some(indexreg) = indexreg {
|
||||||
|
match indexreg {
|
||||||
|
Aarch64Register::X0 | Aarch64Register::W0 => {
|
||||||
|
match basereg {
|
||||||
|
Aarch64Register::X1 | Aarch64Register::W1 => {
|
||||||
|
// x0 is overwrittern indexreg by op1 value.
|
||||||
|
// x1 is basereg
|
||||||
|
|
||||||
|
// Preserve x2, x3:
|
||||||
|
writer.put_stp_reg_reg_reg_offset(
|
||||||
|
Aarch64Register::X2,
|
||||||
|
Aarch64Register::X3,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
-(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64,
|
||||||
|
IndexMode::PreAdjust,
|
||||||
|
);
|
||||||
|
|
||||||
|
//reload indexreg to x2
|
||||||
|
writer.put_ldr_reg_reg_offset(
|
||||||
|
Aarch64Register::X2,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
0u64,
|
||||||
|
);
|
||||||
|
//add them into basereg==x1
|
||||||
|
writer.put_add_reg_reg_reg(
|
||||||
|
basereg,
|
||||||
|
basereg,
|
||||||
|
Aarch64Register::X2,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore x2, x3
|
||||||
|
assert!(writer.put_ldp_reg_reg_reg_offset(
|
||||||
|
Aarch64Register::X2,
|
||||||
|
Aarch64Register::X3,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64,
|
||||||
|
IndexMode::PostAdjust,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// x0 is overwrittern indexreg by op1 value.
|
||||||
|
// basereg is not x1 nor x0
|
||||||
|
|
||||||
|
//reload indexreg to x1
|
||||||
|
writer.put_ldr_reg_reg_offset(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
0u64,
|
||||||
|
);
|
||||||
|
//add basereg into indexreg==x1
|
||||||
|
writer.put_add_reg_reg_reg(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
basereg,
|
||||||
|
Aarch64Register::X1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Aarch64Register::X1 | Aarch64Register::W1 => {
|
||||||
|
match basereg {
|
||||||
|
Aarch64Register::X0 | Aarch64Register::W0 => {
|
||||||
|
// x0 is overwrittern basereg by op1 value.
|
||||||
|
// x1 is indexreg
|
||||||
|
|
||||||
|
// Preserve x2, x3:
|
||||||
|
writer.put_stp_reg_reg_reg_offset(
|
||||||
|
Aarch64Register::X2,
|
||||||
|
Aarch64Register::X3,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
-(16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i32) as i64,
|
||||||
|
IndexMode::PreAdjust,
|
||||||
|
);
|
||||||
|
|
||||||
|
//reload basereg to x2
|
||||||
|
writer.put_ldr_reg_reg_offset(
|
||||||
|
Aarch64Register::X2,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
0u64,
|
||||||
|
);
|
||||||
|
//add basereg into indexreg==x1
|
||||||
|
writer.put_add_reg_reg_reg(
|
||||||
|
indexreg,
|
||||||
|
Aarch64Register::X2,
|
||||||
|
indexreg,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Restore x2, x3
|
||||||
|
assert!(writer.put_ldp_reg_reg_reg_offset(
|
||||||
|
Aarch64Register::X2,
|
||||||
|
Aarch64Register::X3,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64,
|
||||||
|
IndexMode::PostAdjust,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// indexreg is x1
|
||||||
|
// basereg is not x0 and not x1
|
||||||
|
|
||||||
|
//add them into x1
|
||||||
|
writer.put_add_reg_reg_reg(indexreg, basereg, indexreg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
match basereg {
|
||||||
|
Aarch64Register::X0 | Aarch64Register::W0 => {
|
||||||
|
//basereg is overwrittern by op1 value
|
||||||
|
//index reg is not x0 nor x1
|
||||||
|
|
||||||
|
//reload basereg to x1
|
||||||
|
writer.put_ldr_reg_reg_offset(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
0u64,
|
||||||
|
);
|
||||||
|
//add indexreg to basereg==x1
|
||||||
|
writer.put_add_reg_reg_reg(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::X1,
|
||||||
|
indexreg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
//basereg is not x0, can be x1
|
||||||
|
//index reg is not x0 nor x1
|
||||||
|
|
||||||
|
//add them into x1
|
||||||
|
writer.put_add_reg_reg_reg(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
basereg,
|
||||||
|
indexreg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match basereg {
|
||||||
|
Aarch64Register::X1 | Aarch64Register::W1 => {}
|
||||||
|
Aarch64Register::X0 | Aarch64Register::W0 => {
|
||||||
|
// x0 is overwrittern basereg by op1 value.
|
||||||
|
//reload basereg to x1
|
||||||
|
writer.put_ldr_reg_reg_offset(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
0u64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
writer.put_mov_reg_reg(Aarch64Register::W1, basereg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add displacement
|
||||||
|
writer.put_add_reg_reg_imm(
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::X1,
|
||||||
|
displacement as u64,
|
||||||
|
);
|
||||||
|
//deref into x1 to get the real value
|
||||||
|
writer.put_ldr_reg_reg_offset(Aarch64Register::X1, Aarch64Register::X1, 0u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//call cmplog runtime to populate the values map
|
||||||
|
writer.put_bytes(&self.cmplog_runtime.ops_save_register_and_blr_to_populate());
|
||||||
|
|
||||||
|
// Restore x0, x1
|
||||||
|
assert!(writer.put_ldp_reg_reg_reg_offset(
|
||||||
|
Aarch64Register::X0,
|
||||||
|
Aarch64Register::X1,
|
||||||
|
Aarch64Register::Sp,
|
||||||
|
16 + frida_gum_sys::GUM_RED_ZONE_SIZE as i64,
|
||||||
|
IndexMode::PostAdjust,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_shadow_check(
|
fn emit_shadow_check(
|
||||||
@ -647,7 +983,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
|||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_interesting_instruction(
|
fn asan_is_interesting_instruction(
|
||||||
&self,
|
&self,
|
||||||
_address: u64,
|
_address: u64,
|
||||||
instr: &Insn,
|
instr: &Insn,
|
||||||
@ -697,6 +1033,70 @@ impl<'a> FridaInstrumentationHelper<'a> {
|
|||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
|
||||||
|
#[inline]
|
||||||
|
/// Check if the current instruction is cmplog relevant one(any opcode which sets the flags)
|
||||||
|
fn cmplog_is_interesting_instruction(
|
||||||
|
&self,
|
||||||
|
_address: u64,
|
||||||
|
instr: &Insn,
|
||||||
|
) -> Result<(CmplogOperandType, CmplogOperandType), ()> {
|
||||||
|
// We only care for compare instrunctions - aka instructions which set the flags
|
||||||
|
match instr.mnemonic().unwrap() {
|
||||||
|
"cmp" | "ands" | "subs" | "adds" | "negs" | "ngcs" | "sbcs" | "bics" | "cls" => (),
|
||||||
|
_ => return Err(()),
|
||||||
|
}
|
||||||
|
let operands = self
|
||||||
|
.capstone
|
||||||
|
.insn_detail(instr)
|
||||||
|
.unwrap()
|
||||||
|
.arch_detail()
|
||||||
|
.operands();
|
||||||
|
if operands.len() != 2 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let operand1 = if let Arm64Operand(arm64operand) = operands.first().unwrap() {
|
||||||
|
match arm64operand.op_type {
|
||||||
|
Arm64OperandType::Reg(regid) => Some(CmplogOperandType::Regid(regid)),
|
||||||
|
Arm64OperandType::Imm(val) => Some(CmplogOperandType::Imm(val as u64)),
|
||||||
|
Arm64OperandType::Mem(opmem) => Some(CmplogOperandType::Mem(
|
||||||
|
opmem.base(),
|
||||||
|
opmem.index(),
|
||||||
|
opmem.disp(),
|
||||||
|
self.instruction_width(instr, &operands),
|
||||||
|
)),
|
||||||
|
Arm64OperandType::Cimm(val) => Some(CmplogOperandType::Cimm(val as u64)),
|
||||||
|
_ => return Err(()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let operand2 = if let Arm64Operand(arm64operand2) = operands.last().unwrap() {
|
||||||
|
match arm64operand2.op_type {
|
||||||
|
Arm64OperandType::Reg(regid) => Some(CmplogOperandType::Regid(regid)),
|
||||||
|
Arm64OperandType::Imm(val) => Some(CmplogOperandType::Imm(val as u64)),
|
||||||
|
Arm64OperandType::Mem(opmem) => Some(CmplogOperandType::Mem(
|
||||||
|
opmem.base(),
|
||||||
|
opmem.index(),
|
||||||
|
opmem.disp(),
|
||||||
|
self.instruction_width(instr, &operands),
|
||||||
|
)),
|
||||||
|
Arm64OperandType::Cimm(val) => Some(CmplogOperandType::Cimm(val as u64)),
|
||||||
|
_ => return Err(()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if operand1.is_some() && operand2.is_some() {
|
||||||
|
Ok((operand1.unwrap(), operand2.unwrap()))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn emit_coverage_mapping(&mut self, address: u64, output: &StalkerOutput) {
|
fn emit_coverage_mapping(&mut self, address: u64, output: &StalkerOutput) {
|
||||||
let writer = output.writer();
|
let writer = output.writer();
|
||||||
|
@ -9,7 +9,12 @@ pub mod alloc;
|
|||||||
pub mod asan_errors;
|
pub mod asan_errors;
|
||||||
/// The frida address sanitizer runtime
|
/// The frida address sanitizer runtime
|
||||||
pub mod asan_rt;
|
pub mod asan_rt;
|
||||||
/// The `LibAFL` frida helper
|
|
||||||
|
#[cfg(feature = "cmplog")]
|
||||||
|
/// The frida cmplog runtime
|
||||||
|
pub mod cmplog_rt;
|
||||||
|
|
||||||
|
/// The `LibAFL` firda helper
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
|
|
||||||
// for parsing asan cores
|
// for parsing asan cores
|
||||||
@ -28,6 +33,7 @@ pub struct FridaOptions {
|
|||||||
enable_coverage: bool,
|
enable_coverage: bool,
|
||||||
enable_drcov: bool,
|
enable_drcov: bool,
|
||||||
instrument_suppress_locations: Option<Vec<(String, usize)>>,
|
instrument_suppress_locations: Option<Vec<(String, usize)>>,
|
||||||
|
enable_cmplog: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FridaOptions {
|
impl FridaOptions {
|
||||||
@ -100,6 +106,12 @@ impl FridaOptions {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"cmplog" => {
|
||||||
|
options.enable_cmplog = value.parse().unwrap();
|
||||||
|
if !cfg!(feature = "cmplog") && options.enable_cmplog {
|
||||||
|
panic!("cmplog feature is disabled!")
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!("unknown FRIDA option: '{}'", option);
|
panic!("unknown FRIDA option: '{}'", option);
|
||||||
}
|
}
|
||||||
@ -144,6 +156,13 @@ impl FridaOptions {
|
|||||||
self.enable_drcov
|
self.enable_drcov
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is `CmpLog` enabled?
|
||||||
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
|
pub fn cmplog_enabled(&self) -> bool {
|
||||||
|
self.enable_cmplog
|
||||||
|
}
|
||||||
|
|
||||||
/// Should ASAN detect leaks
|
/// Should ASAN detect leaks
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -190,6 +209,7 @@ impl Default for FridaOptions {
|
|||||||
enable_coverage: true,
|
enable_coverage: true,
|
||||||
enable_drcov: false,
|
enable_drcov: false,
|
||||||
instrument_suppress_locations: None,
|
instrument_suppress_locations: None,
|
||||||
|
enable_cmplog: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,12 @@ void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SANCOV_CMPLOG
|
||||||
|
void libafl_targets_cmplog_wrapper(uintptr_t k, uint8_t shape, uint64_t arg1, uint64_t arg2){
|
||||||
|
return __libafl_targets_cmplog(k, shape, arg1, arg2);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {
|
void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases) {
|
||||||
|
|
||||||
uintptr_t rt = RETADDR;
|
uintptr_t rt = RETADDR;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user