add cmplog runtime instrumentation

This commit is contained in:
Omree 2021-05-09 10:14:17 +03:00
parent e6b95c1a4c
commit 2bbff1b7ab
3 changed files with 598 additions and 5 deletions

View File

@ -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<Box<[u8]>>,
}
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::<dynasmrt::aarch64::Aarch64Relocation>::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()
}
}

View File

@ -37,7 +37,14 @@ use std::path::PathBuf;
use nix::sys::mman::{mmap, MapFlags, ProtFlags}; 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 /// An helper that feeds [`FridaInProcessExecutor`] with user-supplied instrumentation
pub trait FridaHelper<'a> { pub trait FridaHelper<'a> {
@ -76,7 +83,8 @@ pub struct FridaInstrumentationHelper<'a> {
transformer: Option<Transformer<'a>>, transformer: Option<Transformer<'a>>,
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
capstone: Capstone, capstone: Capstone,
asan_runtime: AsanRuntime, asan_runtime: Rc<RefCell<AsanRuntime>>,
cmplog_runtime: CmpLogRuntime,
ranges: RangeMap<usize, (u16, &'a str)>, ranges: RangeMap<usize, (u16, &'a str)>,
options: &'a FridaOptions, options: &'a FridaOptions,
drcov_basic_blocks: Vec<DrCovBasicBlock>, drcov_basic_blocks: Vec<DrCovBasicBlock>,
@ -261,6 +269,7 @@ 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()),
cmplog_runtime: CmpLogRuntime::new(),
ranges: RangeMap::new(), ranges: RangeMap::new(),
options, options,
drcov_basic_blocks: vec![], drcov_basic_blocks: vec![],
@ -331,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.is_interesting_asan_instruction(address, instr)
{ {
helper.emit_shadow_check( helper.emit_shadow_check(
address, 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() { 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,
@ -359,6 +380,9 @@ 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);
} }
if helper.options.cmplog_enabled() {
helper.cmplog_runtime.init();
}
} }
helper helper
} }
@ -374,6 +398,311 @@ impl<'a> FridaInstrumentationHelper<'a> {
Aarch64Register::from_u32(regint as u32).unwrap() 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")] #[cfg(target_arch = "aarch64")]
#[inline] #[inline]
fn emit_shadow_check( fn emit_shadow_check(
@ -651,7 +980,7 @@ impl<'a> FridaInstrumentationHelper<'a> {
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
#[inline] #[inline]
fn is_interesting_instruction( fn is_interesting_asan_instruction(
&self, &self,
_address: u64, _address: u64,
instr: &Insn, instr: &Insn,
@ -701,6 +1030,68 @@ impl<'a> FridaInstrumentationHelper<'a> {
Err(()) 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] #[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();

View File

@ -9,7 +9,11 @@ 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
/// 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 +32,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 +105,9 @@ impl FridaOptions {
); );
} }
} }
"cmplog" => {
options.enable_cmplog = value.parse().unwrap();
}
_ => { _ => {
panic!("unknown FRIDA option: '{}'", option); panic!("unknown FRIDA option: '{}'", option);
} }
@ -144,6 +152,12 @@ impl FridaOptions {
self.enable_drcov self.enable_drcov
} }
/// Is CmpLog enabled?
#[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 +204,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: true,
} }
} }
} }