diff --git a/libafl_frida/src/cmplog_rt.rs b/libafl_frida/src/cmplog_rt.rs new file mode 100644 index 0000000000..8bd66fac73 --- /dev/null +++ b/libafl_frida/src/cmplog_rt.rs @@ -0,0 +1,187 @@ +use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; +use nix::{ + libc::{memmove, memset}, + sys::mman::{mmap, MapFlags, ProtFlags}, +}; +use std::ffi::c_void; + +// TODO compile time flag +//pub const CMPLOG_MAP_W: usize = 65536; +pub const CMPLOG_MAP_W: usize = 8000; +pub const CMPLOG_MAP_H: usize = 32; +pub const CMPLOG_MAP_SIZE: usize = CMPLOG_MAP_W * CMPLOG_MAP_H; + +pub const CMPLOG_KIND_INS: u8 = 0; +pub const CMPLOG_KIND_RTN: u8 = 1; + +extern crate libafl_targets; +extern "C" { + pub fn __sanitizer_cov_trace_cmp8(arg1: u64, arg2: u64); +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct CmpLogHeader { + hits: u16, + shape: u8, + kind: u8, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct CmpLogOperands(u64, u64); + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct CmpLogMap { + headers: [CmpLogHeader; CMPLOG_MAP_W], + operands: [CmpLogOperands; CMPLOG_MAP_W], +} + +#[no_mangle] +pub static mut libafl_cmplog_map: CmpLogMap = CmpLogMap { + headers: [CmpLogHeader { + hits: 0, + shape: 0, + kind: 0, + }; CMPLOG_MAP_W], + operands: [CmpLogOperands(0, 0); CMPLOG_MAP_W], +}; + +pub use libafl_cmplog_map as CMPLOG_MAP; + +#[no_mangle] +pub static mut libafl_cmplog_enabled: u8 = 0; + +pub use libafl_cmplog_enabled as CMPLOG_ENABLED; + +pub struct CmpLogRuntime { + regs: [u64; 2], + cmp_idx: usize, + cmplog_map: CmpLogMap, + ops_save_register_and_blr_to_populate: Option>, +} + +impl CmpLogRuntime { + pub fn new() -> CmpLogRuntime { + Self { + regs: [0; 2], + cmp_idx: 0, + cmplog_map: CmpLogMap { + headers: [CmpLogHeader { + hits: 0, + shape: 0, + kind: 0, + }; CMPLOG_MAP_W], + operands: [CmpLogOperands(0, 0); CMPLOG_MAP_W], + }, + ops_save_register_and_blr_to_populate: None, + } + } + + extern "C" fn populate_lists(&mut self) { + let op1 = self.regs[0]; + let op2 = self.regs[1]; + + self.cmplog_map.headers[self.cmp_idx].hits += 1; + self.cmplog_map.headers[self.cmp_idx].shape = 8; + let cmplog_ops: CmpLogOperands = CmpLogOperands(op1, op2); + self.cmplog_map.operands[self.cmp_idx] = cmplog_ops; + self.cmp_idx += 1; + } + + /// Generate the instrumentation blobs for the current arch. + fn generate_instrumentation_blobs(&mut self) { + macro_rules! blr_to_populate { + ($ops:ident, $bit:expr) => {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 x16, x17, [sp, #-0x10]! + ; stp x18, x19, [sp, #-0x10]! + ; stp x20, x21, [sp, #-0x10]! + ; stp x22, x23, [sp, #-0x10]! + ; stp x24, x25, [sp, #-0x10]! + ; stp x26, x27, [sp, #-0x10]! + ; stp x28, x29, [sp, #-0x10]! + ; stp x30, xzr, [sp, #-0x10]! + // jump to rust based population of the lists + // ; ldr x2, >self_regs_addr + // ; stp x0, x1, [x2] + // jump to c implementation of cmplog pupolation + ; ldr x2, >populate_lists + ; blr x2 + ; ldp x30, xzr, [sp], #0x10 + ; ldp x28, x29, [sp], #0x10 + ; ldp x26, x27, [sp], #0x10 + ; ldp x24, x25, [sp], #0x10 + ; ldp x22, x23, [sp], #0x10 + ; ldp x20, x21, [sp], #0x10 + ; ldp x18, x19, [sp], #0x10 + ; ldp x16, x17, [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_regs_addr: //for rust based population of the lists.. + ; .qword &mut self.regs as *mut _ as *mut c_void as i64 + ; populate_lists: + ; .qword __sanitizer_cov_trace_cmp8 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, 0); + + 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) { + // workaround frida's frida-gum-allocate-near bug: + unsafe { + for _ in 0..64 { + mmap( + std::ptr::null_mut(), + 128 * 1024, + ProtFlags::PROT_NONE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, + -1, + 0, + ) + .expect("Failed to map dummy regions for frida workaround"); + mmap( + std::ptr::null_mut(), + 4 * 1024 * 1024, + ProtFlags::PROT_NONE, + MapFlags::MAP_ANONYMOUS | MapFlags::MAP_PRIVATE, + -1, + 0, + ) + .expect("Failed to map dummy regions for frida workaround"); + } + } + + self.generate_instrumentation_blobs(); + } + + /// Get the blob which saves the context, jumps to the populate function and restores the context + #[inline] + pub fn ops_save_register_and_blr_to_populate(&self) -> &[u8] { + self.ops_save_register_and_blr_to_populate.as_ref().unwrap() + } +} diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index be3046cc79..daf44cdaab 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -37,7 +37,14 @@ use std::path::PathBuf; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; -use crate::{asan_rt::AsanRuntime, FridaOptions}; +use crate::{asan_rt::AsanRuntime, cmplog_rt::CmpLogRuntime, FridaOptions}; + +enum CmplogOperandType { + Regid(capstone::RegId), + Imm(u64), + Cimm(u64), + Mem(capstone::RegId, capstone::RegId, i32, u32), +} /// An helper that feeds [`FridaInProcessExecutor`] with user-supplied instrumentation pub trait FridaHelper<'a> { @@ -76,7 +83,8 @@ pub struct FridaInstrumentationHelper<'a> { transformer: Option>, #[cfg(target_arch = "aarch64")] capstone: Capstone, - asan_runtime: AsanRuntime, + asan_runtime: Rc>, + cmplog_runtime: CmpLogRuntime, ranges: RangeMap, options: &'a FridaOptions, drcov_basic_blocks: Vec, @@ -261,6 +269,7 @@ impl<'a> FridaInstrumentationHelper<'a> { .build() .expect("Failed to create Capstone object"), asan_runtime: AsanRuntime::new(options.clone()), + cmplog_runtime: CmpLogRuntime::new(), ranges: RangeMap::new(), options, drcov_basic_blocks: vec![], @@ -331,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.is_interesting_asan_instruction(address, instr) { helper.emit_shadow_check( address, @@ -345,6 +354,18 @@ impl<'a> FridaInstrumentationHelper<'a> { ); } } + + if helper.options().cmplog_enabled() { + // check if this instruction is a compare instruction and if so save the registers values + if let Ok((op1, op2)) = + helper.is_interesting_cmplog_instruction(address, instr) + { + println!("emmiting at {} => {}", 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, @@ -359,6 +380,9 @@ impl<'a> FridaInstrumentationHelper<'a> { if helper.options().asan_enabled() || helper.options().drcov_enabled() { helper.asan_runtime.init(gum, modules_to_instrument); } + if helper.options.cmplog_enabled() { + helper.cmplog_runtime.init(); + } } helper } @@ -374,6 +398,311 @@ impl<'a> FridaInstrumentationHelper<'a> { Aarch64Register::from_u32(regint as u32).unwrap() } + #[cfg(target_arch = "aarch64")] + #[inline] + 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.get_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.get_writer_register(basereg); + let indexreg = if indexreg.0 != 0 { + Some(self.get_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.get_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.get_writer_register(basereg); + let indexreg = if indexreg.0 != 0 { + Some(self.get_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( @@ -651,7 +980,7 @@ impl<'a> FridaInstrumentationHelper<'a> { #[cfg(target_arch = "aarch64")] #[inline] - fn is_interesting_instruction( + fn is_interesting_asan_instruction( &self, _address: u64, instr: &Insn, @@ -701,6 +1030,68 @@ impl<'a> FridaInstrumentationHelper<'a> { Err(()) } + #[inline] + fn is_interesting_cmplog_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.get_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.get_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..876a8713c1 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -9,7 +9,11 @@ pub mod alloc; pub mod asan_errors; /// The frida address sanitizer runtime pub mod asan_rt; -/// The `LibAFL` frida helper + +/// The frida cmplog runtime +pub mod cmplog_rt; + +/// The `LibAFL` firda helper pub mod helper; // for parsing asan cores @@ -28,6 +32,7 @@ pub struct FridaOptions { enable_coverage: bool, enable_drcov: bool, instrument_suppress_locations: Option>, + enable_cmplog: bool, } impl FridaOptions { @@ -100,6 +105,9 @@ impl FridaOptions { ); } } + "cmplog" => { + options.enable_cmplog = value.parse().unwrap(); + } _ => { panic!("unknown FRIDA option: '{}'", option); } @@ -144,6 +152,12 @@ impl FridaOptions { self.enable_drcov } + /// Is CmpLog enabled? + #[inline] + pub fn cmplog_enabled(&self) -> bool { + self.enable_cmplog + } + /// Should ASAN detect leaks #[must_use] #[inline] @@ -190,6 +204,7 @@ impl Default for FridaOptions { enable_coverage: true, enable_drcov: false, instrument_suppress_locations: None, + enable_cmplog: true, } } }