Remove capstone from frida [aarch64] (#1723)

* Partially finish ASAN and CmpLog changes

* Fix handle_trap, report_error, and remove capstone

* Fix a few bugs. Can now detect UAFs properly

* Some small changes

* Make API more consistent with x86

* Fix printing

* Remove unneeded inputs, final changes

* formatting

* Fix x86 build

* Formatting
This commit is contained in:
Sharad Khanna 2023-12-16 02:10:40 -05:00 committed by GitHub
parent a0a4dd60bb
commit fce5fd9a2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 423 additions and 609 deletions

View File

@ -29,11 +29,10 @@ serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"]
cc = { version = "1.0", features = ["parallel"] } cc = { version = "1.0", features = ["parallel"] }
[target.'cfg(target_arch = "aarch64")'.dependencies] [target.'cfg(target_arch = "aarch64")'.dependencies]
capstone = "0.11.0" yaxpeax-arm = "0.2.4"
[target.'cfg(target_arch = "x86_64")'.dependencies] [target.'cfg(target_arch = "x86_64")'.dependencies]
yaxpeax-x86 = { git = "https://github.com/tokatoka/yaxpeax-x86/" } # replace this with origin later yaxpeax-x86 = { git = "https://github.com/tokatoka/yaxpeax-x86/" } # replace this with origin later
yaxpeax-arch = "0.2.7"
[dependencies] [dependencies]
libafl = { path = "../libafl", default-features = false, version = "0.11.1", features = [ libafl = { path = "../libafl", default-features = false, version = "0.11.1", features = [
@ -67,6 +66,7 @@ frida-gum = { version = "0.13.2", features = [
"module-names", "module-names",
] } ] }
dynasmrt = "2" dynasmrt = "2"
color-backtrace = { version = "0.6", features = ["resolve-modules"] } color-backtrace = { version = "0.6", features = ["resolve-modules"] }
termcolor = "1.1.3" termcolor = "1.1.3"
serde = "1.0" serde = "1.0"
@ -80,5 +80,7 @@ paste = "1.0"
log = "0.4.20" log = "0.4.20"
mmap-rs = "0.6.0" mmap-rs = "0.6.0"
yaxpeax-arch = "0.2.7"
[dev-dependencies] [dev-dependencies]
serial_test = { version = "2", default-features = false, features = ["logging"] } serial_test = { version = "2", default-features = false, features = ["logging"] }

View File

@ -13,15 +13,6 @@ use core::{
use std::{ffi::c_void, num::NonZeroUsize, ptr::write_volatile, rc::Rc}; use std::{ffi::c_void, num::NonZeroUsize, ptr::write_volatile, rc::Rc};
use backtrace::Backtrace; use backtrace::Backtrace;
#[cfg(target_arch = "aarch64")]
use capstone::{
arch::{
arm64::{Arm64Extender, Arm64OperandType, Arm64Shift},
ArchOperand::Arm64Operand,
BuildsCapstone,
},
Capstone,
};
use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use frida_gum::instruction_writer::X86Register; use frida_gum::instruction_writer::X86Register;
@ -31,7 +22,6 @@ use frida_gum::{
instruction_writer::InstructionWriter, interceptor::Interceptor, stalker::StalkerOutput, Gum, instruction_writer::InstructionWriter, interceptor::Interceptor, stalker::StalkerOutput, Gum,
Module, ModuleDetails, ModuleMap, NativePointer, RangeDetails, Module, ModuleDetails, ModuleMap, NativePointer, RangeDetails,
}; };
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use frida_gum_sys::Insn; use frida_gum_sys::Insn;
use hashbrown::HashMap; use hashbrown::HashMap;
use libafl_bolts::{cli::FuzzerOptions, AsSlice}; use libafl_bolts::{cli::FuzzerOptions, AsSlice};
@ -44,20 +34,24 @@ use libc::{getrlimit, rlimit};
use libc::{getrlimit64, rlimit64}; use libc::{getrlimit64, rlimit64};
use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::sys::mman::{mmap, MapFlags, ProtFlags};
use rangemap::RangeMap; use rangemap::RangeMap;
#[cfg(target_arch = "aarch64")]
use yaxpeax_arch::Arch;
#[cfg(target_arch = "aarch64")]
use yaxpeax_arm::armv8::a64::{ARMv8, InstDecoder, Opcode, Operand, ShiftStyle, SizeCode};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use yaxpeax_x86::amd64::{InstDecoder, Instruction, Opcode}; use yaxpeax_x86::amd64::{InstDecoder, Instruction, Opcode};
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] #[cfg(any(target_arch = "x86_64"))]
use crate::utils::frida_to_cs; use crate::utils::frida_to_cs;
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use crate::utils::instruction_width; use crate::utils::{instruction_width, writer_register};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use crate::utils::operand_details; use crate::utils::{operand_details, AccessType};
use crate::{ use crate::{
alloc::Allocator, alloc::Allocator,
asan::errors::{AsanError, AsanErrors, AsanReadWriteError, ASAN_ERRORS}, asan::errors::{AsanError, AsanErrors, AsanReadWriteError, ASAN_ERRORS},
helper::{FridaRuntime, SkipRange}, helper::{FridaRuntime, SkipRange},
utils::{disas_count, AccessType}, utils::disas_count,
}; };
extern "C" { extern "C" {
@ -193,15 +187,9 @@ impl FridaRuntime for AsanRuntime {
})); }));
self.hook_functions(gum); self.hook_functions(gum);
/*
unsafe { /* unsafe {
let mem = self.allocator.alloc(0xac + 2, 8); let mem = self.allocator.alloc(0xac + 2, 8);
mprotect(
(self.shadow_check_func.unwrap() as usize & 0xffffffffffff000) as *mut c_void,
0x1000,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC,
)
.unwrap();
log::info!("Test0"); log::info!("Test0");
/* /*
0x555555916ce9 <libafl_frida::asan_rt::AsanRuntime::init+13033> je libafl_frida::asan_rt::AsanRuntime::init+14852 <libafl_frida::asan_rt::AsanRuntime::init+14852> 0x555555916ce9 <libafl_frida::asan_rt::AsanRuntime::init+13033> je libafl_frida::asan_rt::AsanRuntime::init+14852 <libafl_frida::asan_rt::AsanRuntime::init+14852>
@ -265,8 +253,8 @@ impl FridaRuntime for AsanRuntime {
)); ));
} }
// assert!((self.shadow_check_func.unwrap())(((mem2 as usize) + 8875) as *const c_void, 4)); // assert!((self.shadow_check_func.unwrap())(((mem2 as usize) + 8875) as *const c_void, 4));
} }*/
*/
self.register_thread(); self.register_thread();
} }
fn pre_exec<I: libafl::inputs::Input + libafl::inputs::HasTargetBytes>( fn pre_exec<I: libafl::inputs::Input + libafl::inputs::HasTargetBytes>(
@ -1074,113 +1062,61 @@ impl AsanRuntime {
extern "C" fn handle_trap(&mut self) { extern "C" fn handle_trap(&mut self) {
let mut actual_pc = self.regs[31]; let mut actual_pc = self.regs[31];
actual_pc = match self.stalked_addresses.get(&actual_pc) { actual_pc = match self.stalked_addresses.get(&actual_pc) {
//get the pc associated with the trapped insn
Some(addr) => *addr, Some(addr) => *addr,
None => actual_pc, None => actual_pc,
}; };
let cs = Capstone::new() let decoder = <ARMv8 as Arch>::Decoder::default();
.arm64()
.mode(capstone::arch::arm64::ArchMode::Arm)
.detail(true)
.build()
.unwrap();
let instructions = cs let insn = disas_count(
.disasm_count( &decoder,
unsafe { std::slice::from_raw_parts(actual_pc as *mut u8, 24) }, unsafe { std::slice::from_raw_parts(actual_pc as *mut u8, 4) },
actual_pc as u64, 1,
3, )[0];
)
.unwrap(); if insn.opcode == Opcode::MSR && insn.operands[0] == Operand::SystemReg(23056) { //the first operand is nzcv
let instructions = instructions.iter().collect::<Vec<&capstone::Insn>>(); //What case is this for??
let mut insn = instructions.first().unwrap(); /*insn = instructions.get(2).unwrap();
if insn.mnemonic().unwrap() == "msr" && insn.op_str().unwrap() == "nzcv, x0" { actual_pc = insn.address() as usize;*/
insn = instructions.get(2).unwrap();
actual_pc = insn.address() as usize;
} }
let detail = cs.insn_detail(insn).unwrap(); let operands_len = insn
let arch_detail = detail.arch_detail(); .operands
let (mut base_reg, mut index_reg, displacement) = .iter()
if let Arm64Operand(arm64operand) = arch_detail.operands().last().unwrap() { .position(|item| *item == Operand::Nothing)
if let Arm64OperandType::Mem(opmem) = arm64operand.op_type { .unwrap_or_else(|| 4);
(opmem.base().0, opmem.index().0, opmem.disp())
} else { //the memory operand is always the last operand in aarch64
(0, 0, 0) let (base_reg, index_reg, displacement) = match insn.operands[operands_len - 1] {
Operand::RegRegOffset(reg1, reg2, _, _, _) => (reg1, Some(reg2), 0),
Operand::RegPreIndex(reg, disp, _) => (reg, None, disp),
Operand::RegPostIndex(reg, _) => {
//in post index the disp is applied after so it doesn't matter for this memory access
(reg, None, 0)
}
Operand::RegPostIndexReg(reg, _) => (reg, None, 0),
_ => {
return;
} }
} else {
(0, 0, 0)
}; };
if capstone::arch::arm64::Arm64Reg::ARM64_REG_X0 as u16 <= base_reg
&& base_reg <= capstone::arch::arm64::Arm64Reg::ARM64_REG_X28 as u16
{
base_reg -= capstone::arch::arm64::Arm64Reg::ARM64_REG_X0 as u16;
} else if base_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_X29 as u16 {
base_reg = 29u16;
} else if base_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_X30 as u16 {
base_reg = 30u16;
} else if base_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_SP as u16
|| base_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_WSP as u16
|| base_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_XZR as u16
|| base_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_WZR as u16
{
base_reg = 31u16;
} else if capstone::arch::arm64::Arm64Reg::ARM64_REG_W0 as u16 <= base_reg
&& base_reg <= capstone::arch::arm64::Arm64Reg::ARM64_REG_W30 as u16
{
base_reg -= capstone::arch::arm64::Arm64Reg::ARM64_REG_W0 as u16;
} else if capstone::arch::arm64::Arm64Reg::ARM64_REG_S0 as u16 <= base_reg
&& base_reg <= capstone::arch::arm64::Arm64Reg::ARM64_REG_S31 as u16
{
base_reg -= capstone::arch::arm64::Arm64Reg::ARM64_REG_S0 as u16;
}
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
let mut fault_address = let fault_address =
(self.regs[base_reg as usize] as isize + displacement as isize) as usize; (self.regs[base_reg as usize] as isize + displacement as isize) as usize;
if index_reg == 0 {
index_reg = 0xffff;
} else {
if capstone::arch::arm64::Arm64Reg::ARM64_REG_X0 as u16 <= index_reg
&& index_reg <= capstone::arch::arm64::Arm64Reg::ARM64_REG_X28 as u16
{
index_reg -= capstone::arch::arm64::Arm64Reg::ARM64_REG_X0 as u16;
} else if index_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_X29 as u16 {
index_reg = 29u16;
} else if index_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_X30 as u16 {
index_reg = 30u16;
} else if index_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_SP as u16
|| index_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_WSP as u16
|| index_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_XZR as u16
|| index_reg == capstone::arch::arm64::Arm64Reg::ARM64_REG_WZR as u16
{
index_reg = 31u16;
} else if capstone::arch::arm64::Arm64Reg::ARM64_REG_W0 as u16 <= index_reg
&& index_reg <= capstone::arch::arm64::Arm64Reg::ARM64_REG_W30 as u16
{
index_reg -= capstone::arch::arm64::Arm64Reg::ARM64_REG_W0 as u16;
} else if capstone::arch::arm64::Arm64Reg::ARM64_REG_S0 as u16 <= index_reg
&& index_reg <= capstone::arch::arm64::Arm64Reg::ARM64_REG_S31 as u16
{
index_reg -= capstone::arch::arm64::Arm64Reg::ARM64_REG_S0 as u16;
}
fault_address += self.regs[index_reg as usize];
}
let backtrace = Backtrace::new(); let backtrace = Backtrace::new();
let (stack_start, stack_end) = Self::current_stack(); let (stack_start, stack_end) = Self::current_stack();
#[allow(clippy::option_if_let_else)] #[allow(clippy::option_if_let_else)]
let error = if fault_address >= stack_start && fault_address < stack_end { let error = if fault_address >= stack_start && fault_address < stack_end {
if insn.mnemonic().unwrap().starts_with('l') { if insn.opcode.to_string().starts_with('l') {
AsanError::StackOobRead(( AsanError::StackOobRead((
self.regs, self.regs,
actual_pc, actual_pc,
( (
Some(base_reg), Some(base_reg),
Some(index_reg), Some(index_reg.unwrap_or_else(|| 0xffff)),
displacement as usize, displacement as usize,
fault_address, fault_address,
), ),
@ -1192,7 +1128,7 @@ impl AsanRuntime {
actual_pc, actual_pc,
( (
Some(base_reg), Some(base_reg),
Some(index_reg), Some(index_reg.unwrap_or_else(|| 0xffff)),
displacement as usize, displacement as usize,
fault_address, fault_address,
), ),
@ -1208,14 +1144,14 @@ impl AsanRuntime {
pc: actual_pc, pc: actual_pc,
fault: ( fault: (
Some(base_reg), Some(base_reg),
Some(index_reg), Some(index_reg.unwrap_or_else(|| 0xffff)),
displacement as usize, displacement as usize,
fault_address, fault_address,
), ),
metadata: metadata.clone(), metadata: metadata.clone(),
backtrace, backtrace,
}; };
if insn.mnemonic().unwrap().starts_with('l') { if insn.opcode.to_string().starts_with('l') {
if metadata.freed { if metadata.freed {
AsanError::ReadAfterFree(asan_readwrite_error) AsanError::ReadAfterFree(asan_readwrite_error)
} else { } else {
@ -1232,7 +1168,7 @@ impl AsanRuntime {
actual_pc, actual_pc,
( (
Some(base_reg), Some(base_reg),
Some(index_reg), Some(index_reg.unwrap_or_else(|| 0xffff)),
displacement as usize, displacement as usize,
fault_address, fault_address,
), ),
@ -2192,53 +2128,101 @@ impl AsanRuntime {
#[must_use] #[must_use]
#[inline] #[inline]
pub fn asan_is_interesting_instruction( pub fn asan_is_interesting_instruction(
capstone: &Capstone, decoder: InstDecoder,
_address: u64, _address: u64,
instr: &Insn, instr: &Insn,
) -> Option<( ) -> Option<(
capstone::RegId, u16, //reg1
capstone::RegId, Option<(u16, SizeCode)>, //size of reg2. This needs to be an option in the case that we don't have one
i32, i32, //displacement.
u32, u32, //load/store size
Arm64Shift, Option<(ShiftStyle, u8)>, //(shift type, shift size)
Arm64Extender,
)> { )> {
// We need to re-decode frida-internal capstone values to upstream capstone // We need to re-decode frida-internal capstone values to upstream capstone
let cs_instr = frida_to_cs(capstone, instr);
let cs_instr = cs_instr.first().unwrap();
let instr = disas_count(&decoder, instr.bytes(), 1)[0];
// We have to ignore these instructions. Simulating them with their side effects is // We have to ignore these instructions. Simulating them with their side effects is
// complex, to say the least. // complex, to say the least.
match cs_instr.mnemonic().unwrap() { match instr.opcode {
"ldaxr" | "stlxr" | "ldxr" | "stxr" | "ldar" | "stlr" | "ldarb" | "ldarh" | "ldaxp" Opcode::LDAXR
| "ldaxrb" | "ldaxrh" | "stlrb" | "stlrh" | "stlxp" | "stlxrb" | "stlxrh" | "ldxrb" | Opcode::STLXR
| "ldxrh" | "stxrb" | "stxrh" => return None, | Opcode::LDXR
| Opcode::LDAR
| Opcode::STLR
| Opcode::LDARB
| Opcode::LDAXP
| Opcode::LDAXRB
| Opcode::LDAXRH
| Opcode::STLRB
| Opcode::STLRH
| Opcode::STLXP
| Opcode::STLXRB
| Opcode::STLXRH
| Opcode::LDXRB
| Opcode::LDXRH
| Opcode::STXRB
| Opcode::STXRH => {
return None;
}
_ => (), _ => (),
} }
//we need to do this convuluted operation because operands in yaxpeax are in a constant slice of size 4,
let operands = capstone //and any unused operands are Operand::Nothing
.insn_detail(cs_instr) let operands_len = instr
.unwrap() .operands
.arch_detail() .iter()
.operands(); .position(|item| *item == Operand::Nothing)
if operands.len() < 2 { .unwrap_or_else(|| 4);
if operands_len < 2 {
return None; return None;
} }
if let Arm64Operand(arm64operand) = operands.last().unwrap() { /*if instr.opcode == Opcode::LDRSW || instr.opcode == Opcode::LDR {
if let Arm64OperandType::Mem(opmem) = arm64operand.op_type { //this is a special case for pc-relative loads. The only two opcodes capable of this are LDR and LDRSW
return Some(( // For more information on this, look up "literal" loads in the ARM docs.
opmem.base(), match instr.operands[1] {
opmem.index(), //this is safe because an ldr is guranteed to have at least 3 operands
opmem.disp(), Operand::PCOffset(off) => {
instruction_width(cs_instr, &operands), return Some((32, None, off, memory_access_size, None));
arm64operand.shift,
arm64operand.ext,
));
} }
_ => (),
} }
}*/
None // println!("{:?} {}", instr, memory_access_size);
//abuse the fact that the last operand is always the mem operand
match instr.operands[operands_len - 1] {
Operand::RegRegOffset(reg1, reg2, size, shift, shift_size) => {
let ret = Some((
reg1,
Some((reg2, size)),
0,
instruction_width(&instr),
Some((shift, shift_size)),
));
// log::trace!("Interesting instruction: {}, {:?}", instr.to_string(), ret);
return ret;
}
Operand::RegPreIndex(reg, disp, _) => {
let ret = Some((reg, None, disp, instruction_width(&instr), None));
// log::trace!("Interesting instruction: {}, {:?}", instr.to_string(), ret);
return ret;
}
Operand::RegPostIndex(reg, _) => {
//in post index the disp is applied after so it doesn't matter for this memory access
let ret = Some((reg, None, 0, instruction_width(&instr), None));
// log::trace!("Interesting instruction: {}, {:?}", instr.to_string(), ret);
return ret;
}
Operand::RegPostIndexReg(reg, _) => {
let ret = Some((reg, None, 0, instruction_width(&instr), None));
// log::trace!("Interesting instruction: {}, {:?}", instr.to_string(), ret);
return ret;
}
_ => {
return None;
}
}
} }
/// Checks if the current instruction is interesting for address sanitization. /// Checks if the current instruction is interesting for address sanitization.
@ -2474,12 +2458,11 @@ impl AsanRuntime {
&mut self, &mut self,
_address: u64, _address: u64,
output: &StalkerOutput, output: &StalkerOutput,
basereg: capstone::RegId, basereg: u16,
indexreg: capstone::RegId, indexreg: Option<(u16, SizeCode)>,
displacement: i32, displacement: i32,
width: u32, width: u32,
shift: Arm64Shift, shift: Option<(ShiftStyle, u8)>,
extender: Arm64Extender,
) { ) {
debug_assert!( debug_assert!(
i32::try_from(frida_gum_sys::GUM_RED_ZONE_SIZE).is_ok(), i32::try_from(frida_gum_sys::GUM_RED_ZONE_SIZE).is_ok(),
@ -2489,11 +2472,11 @@ impl AsanRuntime {
let redzone_size = frida_gum_sys::GUM_RED_ZONE_SIZE as i32; let redzone_size = frida_gum_sys::GUM_RED_ZONE_SIZE as i32;
let writer = output.writer(); let writer = output.writer();
let basereg = writer_register(basereg); let basereg = writer_register(basereg, SizeCode::X, false); //the writer register can never be zr and is always 64 bit
let indexreg = if indexreg.0 == 0 { let indexreg = if let Some((reg, sizecode)) = indexreg {
None Some(writer_register(reg, sizecode, true)) //the index register can be zr
} else { } else {
Some(writer_register(indexreg)) None
}; };
if self.current_report_impl == 0 if self.current_report_impl == 0
@ -2502,8 +2485,6 @@ impl AsanRuntime {
{ {
let after_report_impl = writer.code_offset() + 2; let after_report_impl = writer.code_offset() + 2;
#[cfg(target_arch = "x86_64")]
writer.put_jmp_near_label(after_report_impl);
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
writer.put_b_label(after_report_impl); writer.put_b_label(after_report_impl);
@ -2558,28 +2539,22 @@ impl AsanRuntime {
} }
} }
if let (Arm64Extender::ARM64_EXT_INVALID, Arm64Shift::Invalid) = (extender, shift) { if let Some((shift_type, amount)) = shift {
writer.put_add_reg_reg_reg( let extender_encoding: i32 = match shift_type {
Aarch64Register::X0, ShiftStyle::UXTB => 0b000,
Aarch64Register::X0, ShiftStyle::UXTH => 0b001,
Aarch64Register::X1, ShiftStyle::UXTW => 0b010,
); ShiftStyle::UXTX => 0b011,
} else { ShiftStyle::SXTB => 0b100,
let extender_encoding: i32 = match extender { ShiftStyle::SXTH => 0b101,
Arm64Extender::ARM64_EXT_UXTB => 0b000, ShiftStyle::SXTW => 0b110,
Arm64Extender::ARM64_EXT_UXTH => 0b001, ShiftStyle::SXTX => 0b111,
Arm64Extender::ARM64_EXT_UXTW => 0b010, _ => -1,
Arm64Extender::ARM64_EXT_UXTX => 0b011,
Arm64Extender::ARM64_EXT_SXTB => 0b100,
Arm64Extender::ARM64_EXT_SXTH => 0b101,
Arm64Extender::ARM64_EXT_SXTW => 0b110,
Arm64Extender::ARM64_EXT_SXTX => 0b111,
Arm64Extender::ARM64_EXT_INVALID => -1,
}; };
let (shift_encoding, shift_amount): (i32, u32) = match shift { let (shift_encoding, shift_amount): (i32, u32) = match shift_type {
Arm64Shift::Lsl(amount) => (0b00, amount), ShiftStyle::LSL => (0b00, amount as u32),
Arm64Shift::Lsr(amount) => (0b01, amount), ShiftStyle::LSR => (0b01, amount as u32),
Arm64Shift::Asr(amount) => (0b10, amount), ShiftStyle::ASR => (0b10, amount as u32),
_ => (-1, 0), _ => (-1, 0),
}; };
@ -2589,16 +2564,22 @@ impl AsanRuntime {
writer.put_bytes( writer.put_bytes(
&(0x8b210000 | ((extender_encoding as u32) << 13) | (shift_amount << 10)) &(0x8b210000 | ((extender_encoding as u32) << 13) | (shift_amount << 10))
.to_le_bytes(), .to_le_bytes(),
); ); //add x0, x0, w1, [shift] #[amount]
} else if shift_encoding != -1 { } else if shift_encoding != -1 {
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
writer.put_bytes( writer.put_bytes(
&(0x8b010000 | ((shift_encoding as u32) << 22) | (shift_amount << 10)) &(0x8b010000 | ((shift_encoding as u32) << 22) | (shift_amount << 10))
.to_le_bytes(), .to_le_bytes(),
); ); //add x0, x0, x1, [shift] #[amount]
} else { } else {
panic!("extender: {extender:?}, shift: {shift:?}"); panic!("shift_type: {shift_type:?}, shift: {shift:?}");
} }
} else {
writer.put_add_reg_reg_reg(
Aarch64Register::X0,
Aarch64Register::X0,
Aarch64Register::X1,
);
}; };
} }
@ -2625,12 +2606,12 @@ impl AsanRuntime {
let displacement = displacement.unsigned_abs(); let displacement = displacement.unsigned_abs();
let displacement_hi = displacement / 4096; let displacement_hi = displacement / 4096;
let displacement_lo = displacement % 4096; let displacement_lo = displacement % 4096;
writer.put_bytes(&(0xd1400000u32 | (displacement_hi << 10)).to_le_bytes()); writer.put_bytes(&(0xd1400000u32 | (displacement_hi << 10)).to_le_bytes()); //sub x0, x0, #[displacement / 4096] LSL#12
writer.put_sub_reg_reg_imm( writer.put_sub_reg_reg_imm(
Aarch64Register::X0, Aarch64Register::X0,
Aarch64Register::X0, Aarch64Register::X0,
u64::from(displacement_lo), u64::from(displacement_lo),
); ); //sub x0, x0, #[displacement & 4095]
} }
} else if displacement > 0 { } else if displacement > 0 {
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
@ -2670,7 +2651,7 @@ impl AsanRuntime {
64 => writer.put_bytes(self.blob_check_mem_64bytes()), 64 => writer.put_bytes(self.blob_check_mem_64bytes()),
_ => false, _ => false,
}; };
//Shouldn't there be some manipulation of the code_offset here?
// Add the branch to report // Add the branch to report
//writer.put_brk_imm(0x12); //writer.put_brk_imm(0x12);
writer.put_branch_address(self.current_report_impl); writer.put_branch_address(self.current_report_impl);

View File

@ -19,8 +19,11 @@ use libafl::{
use libafl_bolts::{ownedref::OwnedPtr, Named, SerdeAny}; use libafl_bolts::{ownedref::OwnedPtr, Named, SerdeAny};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use termcolor::{Color, ColorSpec, WriteColor}; use termcolor::{Color, ColorSpec, WriteColor};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "aarch64")]
use yaxpeax_arch::Arch;
use yaxpeax_arch::LengthedInstruction; use yaxpeax_arch::LengthedInstruction;
#[cfg(target_arch = "aarch64")]
use yaxpeax_arm::armv8::a64::ARMv8;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use yaxpeax_x86::amd64::InstDecoder; use yaxpeax_x86::amd64::InstDecoder;
@ -239,21 +242,26 @@ impl AsanErrors {
writeln!(output, "{:━^100}", " CODE ").unwrap(); writeln!(output, "{:━^100}", " CODE ").unwrap();
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
let mut cs = Capstone::new() let decoder = <ARMv8 as Arch>::Decoder::default();
.arm64()
.mode(capstone::arch::arm64::ArchMode::Arm)
.build()
.unwrap();
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
let decoder = InstDecoder::minimal(); let decoder = InstDecoder::minimal();
let start_pc = error.pc - 4 * 5; let start_pc = error.pc - 4 * 5;
#[cfg(target_arch = "x86_64")]
let insts = disas_count( let insts = disas_count(
&decoder, &decoder,
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 15 * 11) }, unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 15 * 11) },
11, 11,
); );
#[cfg(target_arch = "aarch64")]
let insts = disas_count(
&decoder,
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 4 * 11) },
11,
);
let mut inst_address = start_pc; let mut inst_address = start_pc;
for insn in insts { for insn in insts {
@ -489,22 +497,27 @@ impl AsanErrors {
writeln!(output, "{:━^100}", " CODE ").unwrap(); writeln!(output, "{:━^100}", " CODE ").unwrap();
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
let mut cs = Capstone::new() let decoder = <ARMv8 as Arch>::Decoder::default();
.arm64()
.mode(capstone::arch::arm64::ArchMode::Arm)
.build()
.unwrap();
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
let decoder = InstDecoder::minimal(); let decoder = InstDecoder::minimal();
let start_pc = pc; let start_pc = pc;
#[cfg(target_arch = "x86_64")]
let insts = disas_count( let insts = disas_count(
&decoder, &decoder,
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 15 * 11) }, unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 15 * 11) },
11, 11,
); );
#[cfg(target_arch = "aarch64")]
let insts = disas_count(
&decoder,
unsafe { std::slice::from_raw_parts(start_pc as *mut u8, 4 * 11) },
11,
);
let mut inst_address = start_pc; let mut inst_address = start_pc;
for insn in insts { for insn in insts {
if inst_address == pc { if inst_address == pc {

View File

@ -6,6 +6,8 @@
use std::ffi::c_void; use std::ffi::c_void;
use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi};
#[cfg(target_arch = "aarch64")]
use frida_gum_sys::Insn;
use libafl::{ use libafl::{
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
Error, Error,
@ -27,11 +29,9 @@ use frida_gum::{
instruction_writer::{Aarch64Register, IndexMode, InstructionWriter}, instruction_writer::{Aarch64Register, IndexMode, InstructionWriter},
stalker::StalkerOutput, stalker::StalkerOutput,
}; };
#[cfg(target_arch = "aarch64")]
use frida_gum_sys::Insn;
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
use crate::utils::{frida_to_cs, instruction_width, writer_register}; use crate::utils::{disas_count, writer_register};
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
/// Speciial `CmpLog` Cases for `aarch64` /// Speciial `CmpLog` Cases for `aarch64`
@ -44,10 +44,7 @@ pub enum SpecialCmpLogCase {
} }
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use capstone::{ use yaxpeax_arm::armv8::a64::{InstDecoder, Opcode, Operand, ShiftStyle};
arch::{arm64::Arm64OperandType, ArchOperand::Arm64Operand},
Capstone,
};
/// The [`frida_gum_sys::GUM_RED_ZONE_SIZE`] casted to [`i32`] /// The [`frida_gum_sys::GUM_RED_ZONE_SIZE`] casted to [`i32`]
/// ///
@ -68,13 +65,12 @@ fn gum_red_zone_size_i32() -> i32 {
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
pub enum CmplogOperandType { pub enum CmplogOperandType {
/// A Register /// A Register
Regid(capstone::RegId), Regid(Aarch64Register),
/// An immediate value /// An immediate value
Imm(u64), Imm(u64),
/// A constant immediate value /// A constant immediate value
Cimm(u64), Cimm(u64),
/// A memory operand // We don't need a memory type because you cannot directly compare with memory
Mem(capstone::RegId, capstone::RegId, i32, u32),
} }
/// `Frida`-based binary-only innstrumentation that logs compares to the fuzzer /// `Frida`-based binary-only innstrumentation that logs compares to the fuzzer
@ -276,8 +272,9 @@ impl CmpLogRuntime {
&self, &self,
_address: u64, _address: u64,
output: &StalkerOutput, output: &StalkerOutput,
op1: &CmplogOperandType, op1: &CmplogOperandType, //first operand of the comparsion
op2: &CmplogOperandType, op2: &CmplogOperandType, //second operand of the comparsion
_shift: Option<(ShiftStyle, u8)>,
special_case: Option<SpecialCmpLogCase>, special_case: Option<SpecialCmpLogCase>,
) { ) {
let writer = output.writer(); let writer = output.writer();
@ -296,67 +293,17 @@ impl CmpLogRuntime {
CmplogOperandType::Imm(value) | CmplogOperandType::Cimm(value) => { CmplogOperandType::Imm(value) | CmplogOperandType::Cimm(value) => {
writer.put_ldr_reg_u64(Aarch64Register::X0, *value); writer.put_ldr_reg_u64(Aarch64Register::X0, *value);
} }
CmplogOperandType::Regid(reg) => { CmplogOperandType::Regid(reg) => match *reg {
let reg = writer_register(*reg);
match reg {
Aarch64Register::X0 | Aarch64Register::W0 => {} Aarch64Register::X0 | Aarch64Register::W0 => {}
Aarch64Register::X1 | Aarch64Register::W1 => { Aarch64Register::X1 | Aarch64Register::W1 => {
writer.put_mov_reg_reg(Aarch64Register::X0, Aarch64Register::X1); writer.put_mov_reg_reg(Aarch64Register::X0, Aarch64Register::X1);
} }
_ => { _ => {
if !writer.put_mov_reg_reg(Aarch64Register::X0, reg) { if !writer.put_mov_reg_reg(Aarch64Register::X0, *reg) {
writer.put_mov_reg_reg(Aarch64Register::W0, reg); writer.put_mov_reg_reg(Aarch64Register::W0, *reg);
} }
} }
} },
}
CmplogOperandType::Mem(basereg, indexreg, displacement, _width) => {
let basereg = writer_register(*basereg);
let indexreg = if indexreg.0 == 0 {
None
} else {
Some(writer_register(*indexreg))
};
// calculate base+index+displacment into x0
let displacement = displacement
+ if basereg == Aarch64Register::Sp {
16 + gum_red_zone_size_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);
}
}
}
}
debug_assert!(displacement >= 0);
//add displacement
#[allow(clippy::cast_sign_loss)]
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 // make sure operand2 value is saved into x1
@ -374,207 +321,17 @@ impl CmpLogRuntime {
} }
} }
} }
CmplogOperandType::Regid(reg) => { CmplogOperandType::Regid(reg) => match *reg {
let reg = writer_register(*reg);
match reg {
Aarch64Register::X1 | Aarch64Register::W1 => {} Aarch64Register::X1 | Aarch64Register::W1 => {}
Aarch64Register::X0 | Aarch64Register::W0 => { Aarch64Register::X0 | Aarch64Register::W0 => {
writer.put_ldr_reg_reg_offset( writer.put_ldr_reg_reg_offset(Aarch64Register::X1, Aarch64Register::Sp, 0u64);
Aarch64Register::X1,
Aarch64Register::Sp,
0u64,
);
} }
_ => { _ => {
if !writer.put_mov_reg_reg(Aarch64Register::X1, reg) { if !writer.put_mov_reg_reg(Aarch64Register::X1, *reg) {
writer.put_mov_reg_reg(Aarch64Register::W1, reg); writer.put_mov_reg_reg(Aarch64Register::W1, *reg);
} }
} }
} },
}
CmplogOperandType::Mem(basereg, indexreg, displacement, _width) => {
let basereg = writer_register(*basereg);
let indexreg = if indexreg.0 == 0 {
None
} else {
Some(writer_register(*indexreg))
};
// calculate base+index+displacement into x1
let displacement = displacement
+ if basereg == Aarch64Register::Sp {
16 + gum_red_zone_size_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 overwritten indexreg by op1 value.
// x1 is basereg
// Preserve x2, x3:
writer.put_stp_reg_reg_reg_offset(
Aarch64Register::X2,
Aarch64Register::X3,
Aarch64Register::Sp,
i64::from(-(16 + gum_red_zone_size_i32())),
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 + i64::from(frida_gum_sys::GUM_RED_ZONE_SIZE),
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 overwritten basereg by op1 value.
// x1 is indexreg
// Preserve x2, x3:
writer.put_stp_reg_reg_reg_offset(
Aarch64Register::X2,
Aarch64Register::X3,
Aarch64Register::Sp,
i64::from(-(16 + gum_red_zone_size_i32())),
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 + i64::from(frida_gum_sys::GUM_RED_ZONE_SIZE),
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 overwritten 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
#[allow(clippy::cast_sign_loss)]
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 //call cmplog runtime to populate the values map
@ -596,97 +353,122 @@ impl CmpLogRuntime {
/// Check if the current instruction is cmplog relevant one(any opcode which sets the flags) /// Check if the current instruction is cmplog relevant one(any opcode which sets the flags)
#[must_use] #[must_use]
pub fn cmplog_is_interesting_instruction( pub fn cmplog_is_interesting_instruction(
capstone: &Capstone, decoder: InstDecoder,
_address: u64, _address: u64,
instr: &Insn, instr: &Insn,
) -> Option<( ) -> Option<(
CmplogOperandType, CmplogOperandType,
CmplogOperandType, CmplogOperandType,
Option<(ShiftStyle, u8)>, //possible shifts: everything except MSL
Option<SpecialCmpLogCase>, Option<SpecialCmpLogCase>,
)> { )> {
// We need to re-decode frida-internal capstone values to upstream capstone let mut instr = disas_count(&decoder, instr.bytes(), 1)[0];
let cs_instr = frida_to_cs(capstone, instr); let operands_len = instr
let cs_instr = cs_instr.first().unwrap(); .operands
.iter()
.position(|item| *item == Operand::Nothing)
.unwrap_or_else(|| 4);
// "cmp" | "ands" | "subs" | "adds" | "negs" | "ngcs" | "sbcs" | "bics" | "cbz"
// | "cbnz" | "tbz" | "tbnz" | "adcs" - yaxpeax aliases insns (i.e., cmp -> subs)
// We only care for compare instructions - aka instructions which set the flags // We only care for compare instructions - aka instructions which set the flags
match cs_instr.mnemonic().unwrap() { match instr.opcode {
"cmp" | "ands" | "subs" | "adds" | "negs" | "ngcs" | "sbcs" | "bics" | "cbz" Opcode::SUBS
| "cbnz" | "tbz" | "tbnz" | "adcs" => (), | Opcode::ANDS
| Opcode::ADDS
| Opcode::SBCS
| Opcode::BICS
| Opcode::CBZ
| Opcode::CBNZ
| Opcode::TBZ
| Opcode::TBNZ
| Opcode::ADC => (),
_ => return None, _ => return None,
} }
let mut operands = capstone
.insn_detail(cs_instr)
.unwrap()
.arch_detail()
.operands();
// cbz - 1 operand, tbz - 3 operands // cbz - 1 operand, everything else - 3 operands
let special_case = [ let special_case = [
"cbz", "cbnz", "tbz", "tbnz", "subs", "adds", "ands", "sbcs", "bics", "adcs", Opcode::CBZ,
Opcode::CBNZ,
Opcode::TBZ,
Opcode::TBNZ,
Opcode::SUBS,
Opcode::ADDS,
Opcode::ANDS,
Opcode::SBCS,
Opcode::BICS,
Opcode::ADCS,
] ]
.contains(&cs_instr.mnemonic().unwrap()); .contains(&instr.opcode);
if operands.len() != 2 && !special_case { //this check is to ensure that there are the right number of operands
if operands_len != 2 && !special_case {
return None; return None;
} }
// handle special opcodes case which have 3 operands, but the 1st(dest) is not important to us // handle special opcodes case which have 3 operands, but the 1st(dest) is not important to us
if ["subs", "adds", "ands", "sbcs", "bics", "adcs"].contains(&cs_instr.mnemonic().unwrap()) ////subs", "adds", "ands", "sbcs", "bics", "adcs"
if [
Opcode::SUBS,
Opcode::ADDS,
Opcode::ANDS,
Opcode::SBCS,
Opcode::BICS,
Opcode::ADCS,
]
.contains(&instr.opcode)
{ {
//remove the dest operand from the list //remove the dest operand from the list
operands.remove(0); instr.operands.rotate_left(1);
instr.operands[3] = Operand::Nothing;
} }
// cbz marked as special since there is only 1 operand // cbz marked as special since there is only 1 operand
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
let special_case = matches!(cs_instr.mnemonic().unwrap(), "cbz" | "cbnz"); let special_case = matches!(instr.opcode, Opcode::CBZ | Opcode::CBNZ);
#[allow(clippy::cast_sign_loss, clippy::similar_names)] #[allow(clippy::cast_sign_loss, clippy::similar_names)]
let operand1 = if let Arm64Operand(arm64operand) = operands.first().unwrap() { let operand1 = match instr.operands[0] {
match arm64operand.op_type { //the only possibilities are registers for the first operand
Arm64OperandType::Reg(regid) => Some(CmplogOperandType::Regid(regid)), //precompute the aarch64 frida register because it is ambiguous if register=31 means xzr or sp in yaxpeax
Arm64OperandType::Imm(val) => Some(CmplogOperandType::Imm(val as u64)), Operand::Register(sizecode, reg) => Some(CmplogOperandType::Regid(writer_register(
Arm64OperandType::Mem(opmem) => Some(CmplogOperandType::Mem( reg, sizecode, true,
opmem.base(), ))),
opmem.index(), Operand::RegisterOrSP(sizecode, reg) => Some(CmplogOperandType::Regid(
opmem.disp(), writer_register(reg, sizecode, false),
instruction_width(cs_instr, &operands),
)), )),
Arm64OperandType::Cimm(val) => Some(CmplogOperandType::Cimm(val as u64)), _ => panic!("First argument is not a register"), //this should never be possible in arm64
_ => return None,
}
} else {
None
}; };
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
let operand2 = if special_case { let operand2 = if special_case {
Some(CmplogOperandType::Imm(0)) Some((CmplogOperandType::Imm(0), None))
} else if let Arm64Operand(arm64operand2) = &operands[1] {
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(),
instruction_width(cs_instr, &operands),
)),
Arm64OperandType::Cimm(val) => Some(CmplogOperandType::Cimm(val as u64)),
_ => return None,
}
} else { } else {
None match instr.operands[1] {
Operand::Register(sizecode, reg) => Some((
CmplogOperandType::Regid(writer_register(reg, sizecode, true)),
None,
)),
Operand::ImmShift(imm, shift) => {
Some((CmplogOperandType::Imm((imm as u64) << shift), None))
} //precalculate the shift
Operand::RegShift(shiftstyle, amount, regsize, reg) => {
let reg = CmplogOperandType::Regid(writer_register(reg, regsize, true));
let shift = (shiftstyle, amount);
Some((reg, Some(shift)))
}
Operand::Immediate(imm) => Some((CmplogOperandType::Imm(imm as u64), None)),
_ => panic!("Second argument could not be decoded"),
}
}; };
// tbz will need to have special handling at emit time(masking operand1 value with operand2) // tbz will need to have special handling at emit time(masking operand1 value with operand2)
let special_case = match cs_instr.mnemonic().unwrap() { let special_case = match instr.opcode {
"tbz" => Some(SpecialCmpLogCase::Tbz), Opcode::TBZ => Some(SpecialCmpLogCase::Tbz),
"tbnz" => Some(SpecialCmpLogCase::Tbnz), Opcode::TBNZ => Some(SpecialCmpLogCase::Tbnz),
_ => None, _ => None,
}; };
if let Some(op1) = operand1 { if let Some(op1) = operand1 {
operand2.map(|op2| (op1, op2, special_case)) operand2.map(|op2| (op1, op2.0, op2.1, special_case))
} else { } else {
None None
} }

View File

@ -6,11 +6,6 @@ use std::{
rc::Rc, rc::Rc,
}; };
#[cfg(target_arch = "aarch64")]
use capstone::{
arch::{self, BuildsCapstone},
Capstone,
};
#[cfg(unix)] #[cfg(unix)]
use frida_gum::instruction_writer::InstructionWriter; use frida_gum::instruction_writer::InstructionWriter;
use frida_gum::{ use frida_gum::{
@ -27,6 +22,10 @@ use libafl_targets::drcov::DrCovBasicBlock;
#[cfg(unix)] #[cfg(unix)]
use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::sys::mman::{mmap, MapFlags, ProtFlags};
use rangemap::RangeMap; use rangemap::RangeMap;
#[cfg(target_arch = "aarch64")]
use yaxpeax_arch::Arch;
#[cfg(all(target_arch = "aarch64", unix))]
use yaxpeax_arm::armv8::a64::{ARMv8, InstDecoder};
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use yaxpeax_x86::amd64::InstDecoder; use yaxpeax_x86::amd64::InstDecoder;
@ -445,28 +444,14 @@ where
let ranges = Rc::clone(ranges); let ranges = Rc::clone(ranges);
let runtimes = Rc::clone(runtimes); let runtimes = Rc::clone(runtimes);
#[cfg(target_arch = "aarch64")]
let capstone = Capstone::new()
.arm64()
.mode(arch::arm64::ArchMode::Arm)
.detail(true)
.build()
.expect("Failed to create Capstone object");
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
let decoder = InstDecoder::minimal(); let decoder = InstDecoder::minimal();
Transformer::from_callback(gum, move |basic_block, output| {
Self::transform(
basic_block,
&output,
&ranges,
&runtimes,
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
&capstone, let decoder = <ARMv8 as Arch>::Decoder::default();
#[cfg(target_arch = "x86_64")]
decoder, Transformer::from_callback(gum, move |basic_block, output| {
); Self::transform(basic_block, &output, &ranges, &runtimes, decoder);
}) })
} }
@ -523,7 +508,7 @@ where
} }
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
if let Some((basereg, indexreg, displacement, width, shift, extender)) = res { if let Some((basereg, indexreg, displacement, width, shift)) = res {
if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() { if let Some(rt) = runtimes.match_first_type_mut::<AsanRuntime>() {
rt.emit_shadow_check( rt.emit_shadow_check(
address, address,
@ -533,18 +518,25 @@ where
displacement, displacement,
width, width,
shift, shift,
extender,
); );
} }
} }
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
if let Some(rt) = runtimes.match_first_type_mut::<CmpLogRuntime>() { if let Some(rt) = runtimes.match_first_type_mut::<CmpLogRuntime>() {
if let Some((op1, op2, special_case)) = if let Some((op1, op2, shift, special_case)) =
CmpLogRuntime::cmplog_is_interesting_instruction(&capstone, address, instr) CmpLogRuntime::cmplog_is_interesting_instruction(decoder, address, instr)
//change this as well
{ {
//emit code that saves the relevant data in runtime(passes it to x0, x1) //emit code that saves the relevant data in runtime(passes it to x0, x1)
rt.emit_comparison_handling(address, &output, &op1, &op2, special_case); rt.emit_comparison_handling(
address,
&output,
&op1,
&op2,
shift,
special_case,
);
} }
} }

View File

@ -1,100 +1,128 @@
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use capstone::Capstone;
#[cfg(target_arch = "aarch64")]
use capstone::{
arch::{self, arm64::Arm64OperandType, ArchOperand::Arm64Operand},
Insn,
};
#[cfg(target_arch = "aarch64")]
use frida_gum::instruction_writer::Aarch64Register; use frida_gum::instruction_writer::Aarch64Register;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use frida_gum::instruction_writer::X86Register; use frida_gum::instruction_writer::X86Register;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] #[cfg(any(target_arch = "x86_64"))]
use frida_gum_sys; use frida_gum_sys;
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use num_traits::cast::FromPrimitive; use num_traits::cast::FromPrimitive;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use yaxpeax_arch::LengthedInstruction; use yaxpeax_arch::LengthedInstruction;
#[cfg(target_arch = "aarch64")]
use yaxpeax_arch::{Decoder, ReaderBuilder};
#[cfg(target_arch = "aarch64")]
use yaxpeax_arm::armv8::a64::{InstDecoder, Instruction, Opcode, Operand, SIMDSizeCode, SizeCode};
#[cfg(target_arch = "x86_64")]
use yaxpeax_x86::amd64::Operand; use yaxpeax_x86::amd64::Operand;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use yaxpeax_x86::amd64::{InstDecoder, Instruction, RegSpec}; use yaxpeax_x86::amd64::{InstDecoder, Instruction, RegSpec};
/// Determine the size of an SIMD register
#[cfg(target_arch = "aarch64")]
#[inline]
#[must_use]
pub fn get_simd_size(sizecode: SIMDSizeCode) -> u32 {
match sizecode {
SIMDSizeCode::B => 1,
SIMDSizeCode::H => 2,
SIMDSizeCode::S => 4,
SIMDSizeCode::D => 8,
SIMDSizeCode::Q => 16,
}
}
/// Determine the size of a normal register
#[cfg(target_arch = "aarch64")]
#[inline]
#[must_use]
pub fn get_reg_size(sizecode: SizeCode) -> u32 {
match sizecode {
SizeCode::W => {
//this is guaranteed to be 4 because we deal with the strb/ldrb and strh/ldrh should be dealt with in instruction_width
4
}
SizeCode::X => 8,
}
}
/// Determine the width of the specified instruction /// Determine the width of the specified instruction
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn instruction_width(instr: &Insn, operands: &[arch::ArchOperand]) -> u32 { pub fn instruction_width(instr: &Instruction) -> u32 {
use capstone::arch::arm64::{Arm64Insn as I, Arm64Reg as R, Arm64Vas as V}; let num_registers = match instr.opcode {
Opcode::STP
let num_registers = match instr.id().0.into() { | Opcode::STXP
I::ARM64_INS_STP | Opcode::STNP
| I::ARM64_INS_STXP | Opcode::STLXP
| I::ARM64_INS_STNP | Opcode::LDP
| I::ARM64_INS_STLXP | Opcode::LDXP
| I::ARM64_INS_LDP | Opcode::LDNP => 2,
| I::ARM64_INS_LDXP
| I::ARM64_INS_LDNP => 2,
_ => 1, _ => 1,
}; };
let mnemonic = instr.mnemonic().unwrap(); // let mnemonic = instr.opcode.to_string().as_bytes();
match mnemonic.as_bytes().last().unwrap() { match instr.opcode.to_string().as_bytes().last().unwrap() {
b'b' => return 1, b'b' => return 1,
b'h' => return 2, b'h' => return 2,
b'w' => return 4 * num_registers, b'w' => return 4 * num_registers,
_ => (), _ => (),
} }
if let Arm64Operand(operand) = operands.first().unwrap() { let size = match instr.operands.first().unwrap() {
if operand.vas != V::ARM64_VAS_INVALID { Operand::Register(sizecode, _) => {
let count_byte: u32 = if mnemonic.starts_with("st") || mnemonic.starts_with("ld") { //this is used for standard loads/stores including ldr, ldp, etc.
mnemonic.chars().nth(2).unwrap().to_digit(10).unwrap() get_reg_size(*sizecode)
} else {
1
};
return match operand.vas {
V::ARM64_VAS_1B => count_byte,
V::ARM64_VAS_1H => 2 * count_byte,
V::ARM64_VAS_4B | V::ARM64_VAS_1S | V::ARM64_VAS_1D | V::ARM64_VAS_2H => {
4 * count_byte
} }
V::ARM64_VAS_8B Operand::RegisterPair(sizecode, _) => {
| V::ARM64_VAS_4H //not sure where this is used, but it is possible in yaxpeax
| V::ARM64_VAS_2S get_reg_size(*sizecode)
| V::ARM64_VAS_2D }
| V::ARM64_VAS_1Q => 8 * count_byte, Operand::SIMDRegister(sizecode, _) => {
V::ARM64_VAS_8H | V::ARM64_VAS_4S | V::ARM64_VAS_16B => 16 * count_byte, //this is used in cases like ldr q0, [sp]
V::ARM64_VAS_INVALID => { get_simd_size(*sizecode)
panic!("should not be reached"); }
Operand::SIMDRegisterGroup(sizecode, _, _, num) => {
////This is used for cases such as ld4 {v1.2s, v2.2s, v3.2s, v4.2s}, [x0].
//the sizecode is the size of each simd structure (This can only be D or Q), num is the number of them (i.e. ld4 would be 4)
get_simd_size(*sizecode) * *num as u32
}
Operand::SIMDRegisterGroupLane(_, sizecode, num, _) => {
//This is used for cases such as ld4 {v0.s, v1.s, v2.s, v3.s}[0], [x0]. In this case sizecode is the size of each lane, num is the number of them
get_simd_size(*sizecode) * *num as u32
}
_ => {
return 0;
} }
}; };
} else if let Arm64OperandType::Reg(operand) = operand.op_type { num_registers * size
match u32::from(operand.0) {
R::ARM64_REG_W0..=R::ARM64_REG_W30
| R::ARM64_REG_WZR
| R::ARM64_REG_WSP
| R::ARM64_REG_S0..=R::ARM64_REG_S31 => return 4 * num_registers,
R::ARM64_REG_D0..=R::ARM64_REG_D31 => return 8 * num_registers,
R::ARM64_REG_Q0..=R::ARM64_REG_Q31 => return 16,
_ => (),
}
};
};
8 * num_registers
} }
/// Convert from a capstone register id to a frida `InstructionWriter` register index /// Convert from a yaxpeax register to frida gum's register state
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
#[must_use] #[must_use]
#[inline] #[inline]
pub fn writer_register(reg: capstone::RegId) -> Aarch64Register { pub fn writer_register(reg: u16, sizecode: SizeCode, zr: bool) -> Aarch64Register {
let regint: u16 = reg.0; //yaxpeax and arm both make it so that depending on the opcode reg=31 can be EITHER SP or XZR.
Aarch64Register::from_u32(u32::from(regint)).unwrap() match (reg, sizecode, zr) {
(0..=28, SizeCode::X, _) => {
Aarch64Register::from_u32(Aarch64Register::X0 as u32 + reg as u32).unwrap()
}
(0..=30, SizeCode::W, _) => {
Aarch64Register::from_u32(Aarch64Register::W0 as u32 + reg as u32).unwrap()
}
(29, SizeCode::X, _) => Aarch64Register::Fp,
(30, SizeCode::X, _) => Aarch64Register::Lr,
(31, SizeCode::X, false) => Aarch64Register::Sp,
(31, SizeCode::W, false) => Aarch64Register::Wsp,
(31, SizeCode::X, true) => Aarch64Register::Xzr,
(31, SizeCode::W, true) => Aarch64Register::Wzr,
_ => panic!("Failed to get writer register"),
}
} }
/// Translate from `RegSpec` to `X86Register` /// Translate from `RegSpec` to `X86Register`
#[cfg(all(target_arch = "x86_64", unix))]
const X86_64_REGS: [(RegSpec, X86Register); 34] = [ const X86_64_REGS: [(RegSpec, X86Register); 34] = [
(RegSpec::eax(), X86Register::Eax), (RegSpec::eax(), X86Register::Eax),
(RegSpec::ecx(), X86Register::Ecx), (RegSpec::ecx(), X86Register::Ecx),
@ -151,7 +179,7 @@ pub fn writer_register(reg: RegSpec) -> X86Register {
/// Translates a frida instruction to a capstone instruction. /// Translates a frida instruction to a capstone instruction.
/// Returns a [`capstone::Instructions`] with a single [`capstone::Insn`] inside. /// Returns a [`capstone::Instructions`] with a single [`capstone::Insn`] inside.
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] #[cfg(any(target_arch = "x86_64"))]
pub(crate) fn frida_to_cs(decoder: InstDecoder, frida_insn: &frida_gum_sys::Insn) -> Instruction { pub(crate) fn frida_to_cs(decoder: InstDecoder, frida_insn: &frida_gum_sys::Insn) -> Instruction {
decoder.decode_slice(frida_insn.bytes()).unwrap() decoder.decode_slice(frida_insn.bytes()).unwrap()
} }
@ -229,3 +257,19 @@ pub fn disas_count(decoder: &InstDecoder, data: &[u8], count: usize) -> Vec<Inst
counter -= 1; counter -= 1;
} }
} }
#[cfg(target_arch = "aarch64")]
/// Disassemble "count" number of instructions
pub fn disas_count(decoder: &InstDecoder, data: &[u8], count: usize) -> Vec<Instruction> {
let _counter = count;
let mut ret = vec![];
let _start = 0;
let mut reader = ReaderBuilder::<u64, u8>::read_from(data);
while let Ok(insn) = decoder.decode(&mut reader) {
ret.push(insn);
}
ret
}