From 7abd7c816278eb7ec1e77a3e620a1446da039628 Mon Sep 17 00:00:00 2001 From: OB Date: Wed, 9 Jun 2021 15:11:43 +0300 Subject: [PATCH] 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 Co-authored-by: Dominik Maier Co-authored-by: Omree --- fuzzers/frida_libpng/Cargo.toml | 5 +- fuzzers/frida_libpng/harness.cc | 222 ++++++++-------- fuzzers/frida_libpng/src/fuzzer.rs | 59 ++++- libafl/src/observers/cmp.rs | 1 + libafl_frida/Cargo.toml | 7 +- libafl_frida/src/cmplog_rt.rs | 101 +++++++ libafl_frida/src/helper.rs | 408 ++++++++++++++++++++++++++++- libafl_frida/src/lib.rs | 22 +- libafl_targets/src/sancov_cmp.c | 6 + scripts/build_all_fuzzers.sh | 2 +- 10 files changed, 711 insertions(+), 122 deletions(-) create mode 100644 libafl_frida/src/cmplog_rt.rs diff --git a/fuzzers/frida_libpng/Cargo.toml b/fuzzers/frida_libpng/Cargo.toml index 589c202717..cd4a2ee65c 100644 --- a/fuzzers/frida_libpng/Cargo.toml +++ b/fuzzers/frida_libpng/Cargo.toml @@ -21,10 +21,11 @@ num_cpus = "1.0" which = "4.1" [target.'cfg(unix)'.dependencies] -libafl = { path = "../../libafl/", features = [ "std", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]} -libafl_frida = { path = "../../libafl_frida" } +libafl = { path = "../../libafl/", features = [ "std", "llmp_compression", "llmp_bind_public" ] } #, "llmp_small_maps", "llmp_debug"]} capstone = "0.8.0" 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" libc = "0.2" libloading = "0.7.0" diff --git a/fuzzers/frida_libpng/harness.cc b/fuzzers/frida_libpng/harness.cc index ed74911b58..1ed473b50c 100644 --- a/fuzzers/frida_libpng/harness.cc +++ b/fuzzers/frida_libpng/harness.cc @@ -108,118 +108,122 @@ void func1() { // Roughly follows the libpng book example: // http://www.libpng.org/pub/png/book/chapter13.html extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size < kPngHeaderSize) { - return 0; - } - - func1(); - - std::vector v(data, data + size); - if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { - // not a PNG. - return 0; - } - - PngObjectHandler png_handler; - png_handler.png_ptr = nullptr; - png_handler.row_ptr = nullptr; - png_handler.info_ptr = nullptr; - png_handler.end_info_ptr = nullptr; - - png_handler.png_ptr = png_create_read_struct - (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (!png_handler.png_ptr) { - return 0; - } - - png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr); - if (!png_handler.info_ptr) { - PNG_CLEANUP - return 0; - } - - png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr); - if (!png_handler.end_info_ptr) { - PNG_CLEANUP - return 0; - } - - png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); -#ifdef PNG_IGNORE_ADLER32 - png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); -#endif - - // Setting up reading from buffer. - png_handler.buf_state = new BufState(); - png_handler.buf_state->data = data + kPngHeaderSize; - png_handler.buf_state->bytes_left = size - kPngHeaderSize; - png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data); - png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize); - - if (setjmp(png_jmpbuf(png_handler.png_ptr))) { - PNG_CLEANUP - return 0; - } - - // Reading. - png_read_info(png_handler.png_ptr, png_handler.info_ptr); - - // reset error handler to put png_deleter into scope. - if (setjmp(png_jmpbuf(png_handler.png_ptr))) { - PNG_CLEANUP - return 0; - } - - png_uint_32 width, height; - int bit_depth, color_type, interlace_type, compression_type; - int filter_type; - - if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, - &height, &bit_depth, &color_type, &interlace_type, - &compression_type, &filter_type)) { - PNG_CLEANUP - return 0; - } - - // This is going to be too slow. - if (width && height > 100000000 / width) { - PNG_CLEANUP -#ifdef HAS_DUMMY_CRASH - #ifdef __aarch64__ - asm volatile (".word 0xf7f0a000\n"); - #else - asm("ud2"); - #endif -#endif - return 0; - } - - // Set several transforms that browsers typically use: - png_set_gray_to_rgb(png_handler.png_ptr); - png_set_expand(png_handler.png_ptr); - png_set_packing(png_handler.png_ptr); - png_set_scale_16(png_handler.png_ptr); - png_set_tRNS_to_alpha(png_handler.png_ptr); - - int passes = png_set_interlace_handling(png_handler.png_ptr); - - png_read_update_info(png_handler.png_ptr, png_handler.info_ptr); - - png_handler.row_ptr = png_malloc( - png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr, - png_handler.info_ptr)); - - for (int pass = 0; pass < passes; ++pass) { - for (png_uint_32 y = 0; y < height; ++y) { - png_read_row(png_handler.png_ptr, - static_cast(png_handler.row_ptr), nullptr); + if (size >= 8 && *(uint64_t*)data == 0xABCDEFAA8F1324AA){ + abort(); + } + if (size < kPngHeaderSize) { + return 0; } - } - png_read_end(png_handler.png_ptr, png_handler.end_info_ptr); - PNG_CLEANUP - return 0; + func1(); + + std::vector v(data, data + size); + if (png_sig_cmp(v.data(), 0, kPngHeaderSize)) { + // not a PNG. + return 0; + } + + PngObjectHandler png_handler; + png_handler.png_ptr = nullptr; + png_handler.row_ptr = nullptr; + png_handler.info_ptr = nullptr; + png_handler.end_info_ptr = nullptr; + + png_handler.png_ptr = png_create_read_struct + (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_handler.png_ptr) { + return 0; + } + + png_handler.info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.info_ptr) { + PNG_CLEANUP + return 0; + } + + png_handler.end_info_ptr = png_create_info_struct(png_handler.png_ptr); + if (!png_handler.end_info_ptr) { + PNG_CLEANUP + return 0; + } + + png_set_crc_action(png_handler.png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); +#ifdef PNG_IGNORE_ADLER32 + png_set_option(png_handler.png_ptr, PNG_IGNORE_ADLER32, PNG_OPTION_ON); +#endif + + // Setting up reading from buffer. + png_handler.buf_state = new BufState(); + png_handler.buf_state->data = data + kPngHeaderSize; + png_handler.buf_state->bytes_left = size - kPngHeaderSize; + png_set_read_fn(png_handler.png_ptr, png_handler.buf_state, user_read_data); + png_set_sig_bytes(png_handler.png_ptr, kPngHeaderSize); + + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + // Reading. + png_read_info(png_handler.png_ptr, png_handler.info_ptr); + + // reset error handler to put png_deleter into scope. + if (setjmp(png_jmpbuf(png_handler.png_ptr))) { + PNG_CLEANUP + return 0; + } + + png_uint_32 width, height; + int bit_depth, color_type, interlace_type, compression_type; + int filter_type; + + if (!png_get_IHDR(png_handler.png_ptr, png_handler.info_ptr, &width, + &height, &bit_depth, &color_type, &interlace_type, + &compression_type, &filter_type)) { + PNG_CLEANUP + return 0; + } + + // This is going to be too slow. + if (width && height > 100000000 / width) { + PNG_CLEANUP +#ifdef HAS_DUMMY_CRASH +#ifdef __aarch64__ + asm volatile (".word 0xf7f0a000\n"); +#else + asm("ud2"); +#endif +#endif + return 0; + } + + // Set several transforms that browsers typically use: + png_set_gray_to_rgb(png_handler.png_ptr); + png_set_expand(png_handler.png_ptr); + png_set_packing(png_handler.png_ptr); + png_set_scale_16(png_handler.png_ptr); + png_set_tRNS_to_alpha(png_handler.png_ptr); + + int passes = png_set_interlace_handling(png_handler.png_ptr); + + png_read_update_info(png_handler.png_ptr, png_handler.info_ptr); + + png_handler.row_ptr = png_malloc( + png_handler.png_ptr, png_get_rowbytes(png_handler.png_ptr, + png_handler.info_ptr)); + + for (int pass = 0; pass < passes; ++pass) { + for (png_uint_32 y = 0; y < height; ++y) { + png_read_row(png_handler.png_ptr, + static_cast(png_handler.row_ptr), nullptr); + } + } + + png_read_end(png_handler.png_ptr, png_handler.end_info_ptr); + + PNG_CLEANUP + return 0; } diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index 76a5954d6e..a6f96c02eb 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -29,10 +29,11 @@ use libafl::{ inputs::{BytesInput, HasTargetBytes, Input}, mutators::{ scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator}, + token_mutations::I2SRandReplace, token_mutations::Tokens, }, observers::{HitcountsMapObserver, ObserversTuple, StdMapObserver, TimeObserver}, - stages::mutational::StdMutationalStage, + stages::{StdMutationalStage, TracingStage}, state::{HasCorpus, HasMetadata, StdState}, stats::MultiStats, Error, @@ -57,6 +58,7 @@ use libafl_frida::{ helper::{FridaHelper, FridaInstrumentationHelper, MAP_SIZE}, FridaOptions, }; +use libafl_targets::cmplog::{CmpLogObserver, CMPLOG_MAP}; struct FridaInProcessExecutor<'a, 'b, 'c, FH, H, I, OT, S> where @@ -300,7 +302,7 @@ unsafe fn fuzz( unsafe extern "C" fn(data: *const u8, size: usize) -> i32, > = 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 buf = target.as_slice(); (target_func)(buf.as_ptr(), buf.len()); @@ -316,6 +318,9 @@ unsafe fn fuzz( &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 let edges_observer = HitcountsMapObserver::new(StdMapObserver::new_from_ptr( "edges", @@ -378,7 +383,6 @@ unsafe fn fuzz( // Setup a basic mutator with a mutational stage 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 let scheduler = IndexesLenTimeMinimizerCorpusScheduler::new(QueueCorpusScheduler::new()); @@ -413,7 +417,54 @@ unsafe fn fuzz( println!("We imported {} inputs from disk.", state.corpus().count()); } - fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + 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)?; + } else { + let mut stages = tuple_list!(StdMutationalStage::new(mutator)); + + fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut mgr)?; + }; Ok(()) }; diff --git a/libafl/src/observers/cmp.rs b/libafl/src/observers/cmp.rs index 015487ab06..0c3d34e9a9 100644 --- a/libafl/src/observers/cmp.rs +++ b/libafl/src/observers/cmp.rs @@ -4,6 +4,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; + use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 016a8929e8..b8871b08de 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -10,12 +10,17 @@ license = "MIT OR Apache-2.0" keywords = ["fuzzing", "frida", "instrumentation"] edition = "2018" + +[features] +default = [] +cmplog = [] + [build-dependencies] cc = { version = "1.0", features = ["parallel"] } [dependencies] 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" libc = "0.2.92" hashbrown = "0.11" diff --git a/libafl_frida/src/cmplog_rt.rs b/libafl_frida/src/cmplog_rt.rs new file mode 100644 index 0000000000..ab93bb67b1 --- /dev/null +++ b/libafl_frida/src/cmplog_rt.rs @@ -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>, +} + +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::::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() + } +} diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index f294613376..adc5b1938b 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -33,9 +33,18 @@ use rangemap::RangeMap; 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"))] const ANONYMOUS_FLAG: MapFlags = MapFlags::MAP_ANON; @@ -80,6 +89,8 @@ pub struct FridaInstrumentationHelper<'a> { #[cfg(target_arch = "aarch64")] capstone: Capstone, asan_runtime: AsanRuntime, + #[cfg(feature = "cmplog")] + cmplog_runtime: CmpLogRuntime, ranges: RangeMap, module_map: ModuleMap, options: &'a FridaOptions, @@ -265,6 +276,8 @@ impl<'a> FridaInstrumentationHelper<'a> { .build() .expect("Failed to create Capstone object"), asan_runtime: AsanRuntime::new(options.clone()), + #[cfg(feature = "cmplog")] + cmplog_runtime: CmpLogRuntime::new(), ranges: RangeMap::new(), module_map: ModuleMap::new_from_names(modules_to_instrument), options, @@ -327,7 +340,7 @@ impl<'a> FridaInstrumentationHelper<'a> { todo!("Implement ASAN for non-aarch64 targets"); #[cfg(target_arch = "aarch64")] 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( 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() { helper.asan_runtime.add_stalked_address( output.writer().pc() as usize - 4, @@ -355,6 +381,10 @@ impl<'a> FridaInstrumentationHelper<'a> { if helper.options().asan_enabled() || helper.options().drcov_enabled() { helper.asan_runtime.init(gum, modules_to_instrument); } + #[cfg(feature = "cmplog")] + if helper.options.cmplog_enabled() { + helper.cmplog_runtime.init(); + } } helper } @@ -370,6 +400,312 @@ impl<'a> FridaInstrumentationHelper<'a> { 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")] #[inline] fn emit_shadow_check( @@ -647,7 +983,7 @@ impl<'a> FridaInstrumentationHelper<'a> { #[cfg(target_arch = "aarch64")] #[inline] - fn is_interesting_instruction( + fn asan_is_interesting_instruction( &self, _address: u64, instr: &Insn, @@ -697,6 +1033,70 @@ impl<'a> FridaInstrumentationHelper<'a> { 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] fn emit_coverage_mapping(&mut self, address: u64, output: &StalkerOutput) { let writer = output.writer(); diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index c6f0f47b06..31063c899b 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -9,7 +9,12 @@ pub mod alloc; pub mod asan_errors; /// The frida address sanitizer runtime 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; // for parsing asan cores @@ -28,6 +33,7 @@ pub struct FridaOptions { enable_coverage: bool, enable_drcov: bool, instrument_suppress_locations: Option>, + enable_cmplog: bool, } 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); } @@ -144,6 +156,13 @@ impl FridaOptions { self.enable_drcov } + /// Is `CmpLog` enabled? + #[must_use] + #[inline] + pub fn cmplog_enabled(&self) -> bool { + self.enable_cmplog + } + /// Should ASAN detect leaks #[must_use] #[inline] @@ -190,6 +209,7 @@ impl Default for FridaOptions { enable_coverage: true, enable_drcov: false, instrument_suppress_locations: None, + enable_cmplog: false, } } } diff --git a/libafl_targets/src/sancov_cmp.c b/libafl_targets/src/sancov_cmp.c index 33cbd31daf..8e4075e871 100644 --- a/libafl_targets/src/sancov_cmp.c +++ b/libafl_targets/src/sancov_cmp.c @@ -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) { uintptr_t rt = RETADDR; diff --git a/scripts/build_all_fuzzers.sh b/scripts/build_all_fuzzers.sh index 6c6586d176..f96de961e7 100755 --- a/scripts/build_all_fuzzers.sh +++ b/scripts/build_all_fuzzers.sh @@ -11,7 +11,7 @@ do echo "[+] Checking fmt and building $fuzzer" cd $fuzzer \ && cargo fmt --all -- --check \ - && cargo clippy \ + && cargo clippy \ && cargo build \ && cd .. \ || exit 1