POC attempt to make cmplog work on x64 (#1713)

* POC attempt to make cmplog work on x64

windows POC seems working

unix POC seems working :)

* no register collisions
* rsp-related ref support

iced optional dep

iced depends on cmplog

warnings

one more warning

comments cleanup

ci unbreak

rebase windows unbreak

rebase unix unbreak

unix only

fmt check

clang formatting

clang formatting again

make clippy happy

formatting

double import

windows unbreak

hashmap is conditional

leftover definition

tutorial related formatter

review fixes

comments

.asm fuzz targets for cmplog on Windows

more tests

rip-relative reference support without index register form

proper ignore rip-related references and ignore 8 bit comparisons

another try_into packing

* harness modification reverted

* dummy commit to restart CI

* review comments

---------

Co-authored-by: sbarsky <sbarsky@denuvo.com>
Co-authored-by: Dongjia "toka" Zhang <tokazerkje@outlook.com>
This commit is contained in:
expend20 2024-01-19 05:52:15 -05:00 committed by GitHub
parent e615cb4aed
commit 72c862171e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 582 additions and 35 deletions

View File

@ -605,7 +605,7 @@ jobs:
- name: Build fuzzers/frida_libpng - name: Build fuzzers/frida_libpng
run: cd fuzzers/frida_libpng/ && cargo make test run: cd fuzzers/frida_libpng/ && cargo make test
- name: Build fuzzers/frida_gdiplus - name: Build fuzzers/frida_gdiplus
run: cd fuzzers/frida_gdiplus/ && cargo make test run: cd fuzzers/frida_gdiplus/ && cargo make test && cargo make test_cmplog
- name: Build fuzzers/tinyinst_simple - name: Build fuzzers/tinyinst_simple
run: cd fuzzers/tinyinst_simple/ && cargo make test run: cd fuzzers/tinyinst_simple/ && cargo make test

View File

@ -1,5 +1,7 @@
corpus_discovered corpus_discovered
output_t*
*.exp *.exp
*.lib *.lib
*.obj *.obj
*.lnk

View File

@ -32,3 +32,4 @@ libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] }
libloading = "0.7" libloading = "0.7"
mimalloc = { version = "*", default-features = false } mimalloc = { version = "*", default-features = false }
color-backtrace = "0.5" color-backtrace = "0.5"
iced-x86 = { version = "1.20.0", features = ["code_asm"] }

View File

@ -24,6 +24,12 @@ script='''
cl.exe /LD harness.cc /link /dll gdiplus.lib ole32.lib cl.exe /LD harness.cc /link /dll gdiplus.lib ole32.lib
''' '''
[tasks.harness_windows_cmplog_test]
script_runner="@shell"
script='''
ml64 cmplog_test.asm /subsystem:windows /link /dll /def:cmplog_test.def /entry:dll_main /out:cmplog.dll
'''
# Fuzzer # Fuzzer
[tasks.fuzzer] [tasks.fuzzer]
linux_alias = "unsupported" linux_alias = "unsupported"
@ -56,6 +62,28 @@ linux_alias = "unsupported"
mac_alias = "unsupported" mac_alias = "unsupported"
windows_alias = "test_windows" windows_alias = "test_windows"
[tasks.test_cmplog]
linux_alias = "unsupported"
mac_alias = "unsupported"
windows_alias = "test_windows_cmplog"
[tasks.test_windows_cmplog]
script_runner = "@shell"
script='''
@echo off
for %%i in (t1 t2 t3 t4 t5 t6 t7) do (
echo Testing %%i...
rmdir /s /q output_%%i
start "" "frida_gdiplus.exe" -H cmplog.dll -i corpus -o output_%%i --libs-to-instrument cmplog.dll -F %%i -C
ping -n 3 127.0.0.1>NUL && taskkill /im frida_gdiplus.exe /F
>nul 2>nul dir /a-d "output_%%i" && (echo Files exist) || (exit /b 1337)
)
echo All tests done
'''
dependencies = [ "fuzzer", "harness_windows_cmplog_test" ]
[tasks.test_windows] [tasks.test_windows]
script_runner = "@shell" script_runner = "@shell"
script=''' script='''

View File

@ -0,0 +1,102 @@
public dll_main
public t1
public t2
public t3
public t4
public t5
public t6
public t7
.code
crash:
mov rax, 0
mov rax, [rax]
ret
; dummy test which does not produce crashes or coverage
test_no_cov:
ret
; test 64 bits mem/reg
t1:
cmp rdx, 8
jb @f
mov rax, 01234567812345678h
cmp qword ptr [rcx], rax ; demonstrate rax stack usage (see emit_comparison_handling function)
je crash
@@:
ret
; test 32 bits mem/reg
t2:
cmp rdx, 4
jb @f
mov r8d, 012345678h
mov rax, 100h ; test indes/scale usage
cmp dword ptr [rcx + rax*2 - 200h], r8d
je crash
@@:
ret
; test 16 bits mem/reg
t3:
cmp rdx, 2
jb @f
mov r8w, 01234h
cmp word ptr [rcx], r8w
je crash
@@:
ret
; test 64 bit reg/reg
t4:
cmp rdx, 8
jb @f
mov rax, 01234567812345678h
mov rcx, qword ptr [rcx]
cmp rax, rcx
je crash
@@:
ret
; test 32 bit reg/imm
t5:
cmp rdx, 4
jb @f
mov rcx, qword ptr [rcx]
cmp rcx, 012345678h
je crash
@@:
ret
; test 32 bit rsp-related reference
t6:
cmp rdx, 4
jb @f
sub rsp, 8
mov dword ptr [rsp], 012345678h
mov ecx, dword ptr [rcx]
cmp dword ptr [rsp], ecx
je crash
add rsp, 8
@@:
ret
; test 32 bit rip-related reference
t7_rip_rel_ref:
dq 012345678h
t7:
cmp rdx, 4
jb @f
mov ecx, dword ptr [rcx]
cmp dword ptr [t7_rip_rel_ref], ecx
je crash
@@:
ret
dll_main:
mov eax, 1
ret
END

View File

@ -0,0 +1,8 @@
EXPORTS
t1
t2
t3
t4
t5
t6
t7

View File

@ -323,6 +323,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
println!("We imported {} inputs from disk.", state.corpus().count()); println!("We imported {} inputs from disk.", state.corpus().count());
} }
println!("Cmplog observer is enabled");
// Create an observation channel using cmplog map // Create an observation channel using cmplog map
let cmplog_observer = CmpLogObserver::new("cmplog", true); let cmplog_observer = CmpLogObserver::new("cmplog", true);

View File

@ -216,6 +216,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> {
let coverage = CoverageRuntime::new(); let coverage = CoverageRuntime::new();
let cmplog = CmpLogRuntime::new(); let cmplog = CmpLogRuntime::new();
println!("cmplog runtime created");
let mut frida_helper = let mut frida_helper =
FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog)); FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog));

View File

@ -1065,6 +1065,7 @@ pub mod windows_asan_handler {
} }
} }
/// Handles exceptions on Windows
#[cfg(all(windows, feature = "std"))] #[cfg(all(windows, feature = "std"))]
pub mod windows_exception_handler { pub mod windows_exception_handler {
#[cfg(feature = "std")] #[cfg(feature = "std")]

View File

@ -452,6 +452,7 @@ where
let cmps_len = { let cmps_len = {
let meta = state.metadata_map().get::<CmpValuesMetadata>(); let meta = state.metadata_map().get::<CmpValuesMetadata>();
log::trace!("meta: {:x?}", meta);
if meta.is_none() { if meta.is_none() {
return Ok(MutationResult::Skipped); return Ok(MutationResult::Skipped);
} }

View File

@ -22,7 +22,7 @@ all-features = true
[features] [features]
default = ["serdeany_autoreg"] default = ["serdeany_autoreg"]
cmplog = [] cmplog = ["iced-x86"]
serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"] serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"]
[build-dependencies] [build-dependencies]
@ -33,6 +33,7 @@ yaxpeax-arm = "0.2.4"
[target.'cfg(target_arch = "x86_64")'.dependencies] [target.'cfg(target_arch = "x86_64")'.dependencies]
yaxpeax-x86 = "1.2.2" yaxpeax-x86 = "1.2.2"
iced-x86 = { version = "1.20.0", features = ["code_asm"], optional = true }
[dependencies] [dependencies]
libafl = { path = "../libafl", default-features = false, version = "0.11.2", features = [ libafl = { path = "../libafl", default-features = false, version = "0.11.2", features = [

View File

@ -3,11 +3,11 @@
//! This allows the fuzzer to potentially solve the compares, if a compare value is directly //! This allows the fuzzer to potentially solve the compares, if a compare value is directly
//! related to the input. //! related to the input.
//! Read the [`RedQueen`](https://www.ndss-symposium.org/ndss-paper/redqueen-fuzzing-with-input-to-state-correspondence/) paper for the general concepts. //! Read the [`RedQueen`](https://www.ndss-symposium.org/ndss-paper/redqueen-fuzzing-with-input-to-state-correspondence/) paper for the general concepts.
use std::ffi::c_void;
use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; #[cfg(all(feature = "cmplog", target_arch = "x86_64"))]
#[cfg(target_arch = "aarch64")] use std::collections::HashMap;
use frida_gum_sys::Insn;
use dynasmrt::dynasm;
use libafl::{ use libafl::{
inputs::{HasTargetBytes, Input}, inputs::{HasTargetBytes, Input},
Error, Error,
@ -24,11 +24,19 @@ extern "C" {
use std::rc::Rc; use std::rc::Rc;
use frida_gum::ModuleMap; use frida_gum::ModuleMap;
#[cfg(target_arch = "x86_64")]
use frida_gum::{instruction_writer::InstructionWriter, stalker::StalkerOutput};
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use frida_gum::{ use frida_gum::{
instruction_writer::{Aarch64Register, IndexMode, InstructionWriter}, instruction_writer::{Aarch64Register, IndexMode, InstructionWriter},
stalker::StalkerOutput, stalker::StalkerOutput,
}; };
use frida_gum_sys::Insn;
#[cfg(all(feature = "cmplog", target_arch = "x86_64"))]
use iced_x86::{
BlockEncoder, Code, DecoderOptions, Instruction, InstructionBlock, MemoryOperand, MemorySize,
OpKind, Register,
};
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
use crate::utils::{disas_count, writer_register}; use crate::utils::{disas_count, writer_register};
@ -43,8 +51,15 @@ pub enum SpecialCmpLogCase {
Tbnz, Tbnz,
} }
#[cfg(all(feature = "cmplog", target_arch = "x86_64"))]
/// Speciial `CmpLog` Cases for `aarch64`
#[derive(Debug)]
pub enum SpecialCmpLogCase {}
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use yaxpeax_arm::armv8::a64::{InstDecoder, Opcode, Operand, ShiftStyle}; use yaxpeax_arm::armv8::a64::{InstDecoder, Opcode, Operand, ShiftStyle};
#[cfg(target_arch = "x86_64")]
use yaxpeax_x86::long_mode::InstDecoder;
/// The [`frida_gum_sys::GUM_RED_ZONE_SIZE`] casted to [`i32`] /// The [`frida_gum_sys::GUM_RED_ZONE_SIZE`] casted to [`i32`]
/// ///
@ -73,13 +88,35 @@ pub enum CmplogOperandType {
// We don't need a memory type because you cannot directly compare with memory // We don't need a memory type because you cannot directly compare with memory
} }
/// The type of an operand loggged during `CmpLog`
#[derive(Debug, Clone, Copy)]
#[cfg(all(feature = "cmplog", target_arch = "x86_64"))]
pub enum CmplogOperandType {
/// A Register
Reg(Register),
/// An immediate value
Imm(u64),
/// A memory operand
Mem(Register, Register, i64, u32, MemorySize), // base, index, disp, scale, mem_size
}
/// `Frida`-based binary-only innstrumentation that logs compares to the fuzzer /// `Frida`-based binary-only innstrumentation that logs compares to the fuzzer
/// `LibAFL` can use this knowledge for powerful mutations. /// `LibAFL` can use this knowledge for powerful mutations.
#[derive(Debug)] #[derive(Debug)]
#[cfg(target_arch = "aarch64")]
pub struct CmpLogRuntime { pub struct CmpLogRuntime {
ops_save_register_and_blr_to_populate: Option<Box<[u8]>>, save_register_and_blr_to_populate: Option<Box<[u8]>>,
ops_handle_tbz_masking: Option<Box<[u8]>>, handle_tbz_masking: Option<Box<[u8]>>,
ops_handle_tbnz_masking: Option<Box<[u8]>>, handle_tbnz_masking: Option<Box<[u8]>>,
}
/// `Frida`-based binary-only innstrumentation that logs compares to the fuzzer
/// `LibAFL` can use this knowledge for powerful mutations.
#[derive(Debug)]
#[cfg(target_arch = "x86_64")]
pub struct CmpLogRuntime {
save_registers: Option<Box<[u8]>>,
restore_registers: Option<Box<[u8]>>,
} }
impl FridaRuntime for CmpLogRuntime { impl FridaRuntime for CmpLogRuntime {
@ -106,6 +143,7 @@ impl FridaRuntime for CmpLogRuntime {
impl CmpLogRuntime { impl CmpLogRuntime {
/// Create a new [`CmpLogRuntime`] /// Create a new [`CmpLogRuntime`]
#[must_use] #[must_use]
#[cfg(target_arch = "aarch64")]
pub fn new() -> CmpLogRuntime { pub fn new() -> CmpLogRuntime {
Self { Self {
ops_save_register_and_blr_to_populate: None, ops_save_register_and_blr_to_populate: None,
@ -114,8 +152,19 @@ impl CmpLogRuntime {
} }
} }
/// Create a new [`CmpLogRuntime`]
#[must_use]
#[cfg(target_arch = "x86_64")]
pub fn new() -> CmpLogRuntime {
Self {
save_registers: None,
restore_registers: None,
}
}
/// Call the external function that populates the `cmplog_map` with the relevant values /// Call the external function that populates the `cmplog_map` with the relevant values
#[allow(clippy::unused_self)] #[allow(clippy::unused_self)]
#[cfg(target_arch = "aarch64")]
extern "C" fn populate_lists(&mut self, op1: u64, op2: u64, retaddr: u64) { extern "C" fn populate_lists(&mut self, op1: u64, op2: u64, retaddr: u64) {
// log::trace!( // log::trace!(
// "entered populate_lists with: {:#02x}, {:#02x}, {:#02x}", // "entered populate_lists with: {:#02x}, {:#02x}, {:#02x}",
@ -130,8 +179,25 @@ impl CmpLogRuntime {
} }
} }
#[allow(clippy::unused_self)]
#[cfg(target_arch = "x86_64")]
extern "C" fn populate_lists(size: u8, op1: u64, op2: u64, retaddr: u64) {
// log::trace!(
// "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_instructions(k, size, op1, op2);
}
}
/// Generate the instrumentation blobs for the current arch. /// Generate the instrumentation blobs for the current arch.
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
#[cfg(target_arch = "aarch64")]
fn generate_instrumentation_blobs(&mut self) { fn generate_instrumentation_blobs(&mut self) {
macro_rules! blr_to_populate { macro_rules! blr_to_populate {
($ops:ident) => {dynasm!($ops ($ops:ident) => {dynasm!($ops
@ -243,9 +309,82 @@ impl CmpLogRuntime {
); );
} }
#[allow(clippy::similar_names)]
#[cfg(all(windows, target_arch = "x86_64"))]
fn generate_instrumentation_blobs(&mut self) {
macro_rules! save_registers {
($ops:ident) => {dynasm!($ops
; .arch x64
; push rcx
; push rdx
; push r8
; push r9
; push r10
; push r11
; push rax
);};
}
let mut save_registers = dynasmrt::VecAssembler::<dynasmrt::x64::X64Relocation>::new(0);
save_registers!(save_registers);
self.save_registers = Some(save_registers.finalize().unwrap().into_boxed_slice());
macro_rules! restore_registers {
($ops:ident) => {dynasm!($ops
; .arch x64
; pop rax
; pop r11
; pop r10
; pop r9
; pop r8
; pop rdx
; pop rcx
);};
}
let mut restore_registers = dynasmrt::VecAssembler::<dynasmrt::x64::X64Relocation>::new(0);
restore_registers!(restore_registers);
self.restore_registers = Some(restore_registers.finalize().unwrap().into_boxed_slice());
}
#[allow(clippy::similar_names)]
#[cfg(all(unix, target_arch = "x86_64"))]
fn generate_instrumentation_blobs(&mut self) {
macro_rules! save_registers {
($ops:ident) => {dynasm!($ops
; .arch x64
; push rax
; push rdi
; push rsi
; push rdx
; push rcx
; push r8
; push r9
);};
}
let mut save_registers = dynasmrt::VecAssembler::<dynasmrt::x64::X64Relocation>::new(0);
save_registers!(save_registers);
self.save_registers = Some(save_registers.finalize().unwrap().into_boxed_slice());
macro_rules! restore_registers {
($ops:ident) => {dynasm!($ops
; .arch x64
; pop r9
; pop r8
; pop rcx
; pop rdx
; pop rsi
; pop rdi
; pop rax
);};
}
let mut restore_registers = dynasmrt::VecAssembler::<dynasmrt::x64::X64Relocation>::new(0);
restore_registers!(restore_registers);
self.restore_registers = Some(restore_registers.finalize().unwrap().into_boxed_slice());
}
/// Get the blob which saves the context, jumps to the populate function and restores the context /// Get the blob which saves the context, jumps to the populate function and restores the context
#[inline] #[inline]
#[must_use] #[must_use]
#[cfg(target_arch = "aaarch64")]
pub fn ops_save_register_and_blr_to_populate(&self) -> &[u8] { pub fn ops_save_register_and_blr_to_populate(&self) -> &[u8] {
self.ops_save_register_and_blr_to_populate.as_ref().unwrap() self.ops_save_register_and_blr_to_populate.as_ref().unwrap()
} }
@ -253,6 +392,7 @@ impl CmpLogRuntime {
/// Get the blob which handles the tbz opcode masking /// Get the blob which handles the tbz opcode masking
#[inline] #[inline]
#[must_use] #[must_use]
#[cfg(target_arch = "aaarch64")]
pub fn ops_handle_tbz_masking(&self) -> &[u8] { pub fn ops_handle_tbz_masking(&self) -> &[u8] {
self.ops_handle_tbz_masking.as_ref().unwrap() self.ops_handle_tbz_masking.as_ref().unwrap()
} }
@ -260,11 +400,163 @@ impl CmpLogRuntime {
/// Get the blob which handles the tbnz opcode masking /// Get the blob which handles the tbnz opcode masking
#[inline] #[inline]
#[must_use] #[must_use]
#[cfg(target_arch = "aaarch64")]
pub fn ops_handle_tbnz_masking(&self) -> &[u8] { pub fn ops_handle_tbnz_masking(&self) -> &[u8] {
self.ops_handle_tbnz_masking.as_ref().unwrap() self.ops_handle_tbnz_masking.as_ref().unwrap()
} }
/// Emit the instrumentation code which is responsible for operands value extraction and cmplog map population /// Emit the instrumentation code which is responsible for operands value extraction and cmplog map population
#[cfg(all(feature = "cmplog", target_arch = "x86_64"))]
#[allow(clippy::too_many_lines)]
#[inline]
pub fn emit_comparison_handling(
&self,
address: u64,
output: &StalkerOutput,
op1: &CmplogOperandType, //first operand of the comparsion
op2: &CmplogOperandType, //second operand of the comparsion
_shift: &Option<SpecialCmpLogCase>,
_special_case: &Option<SpecialCmpLogCase>,
) {
let writer = output.writer();
writer.put_bytes(&self.save_registers.clone().unwrap());
// let int3 = [0xcc];
// writer.put_bytes(&int3);
let mut insts = vec![];
// self ptr is not used so far
let mut size_op = 0;
let arg_reg_1;
let arg_reg_2;
let arg_reg_3;
let arg_reg_4;
let mut tmp_reg = HashMap::new();
tmp_reg.insert(8, Register::RAX);
tmp_reg.insert(4, Register::EAX);
tmp_reg.insert(2, Register::AX);
#[cfg(windows)]
{
arg_reg_1 = Register::CL;
arg_reg_2 = Register::RDX;
arg_reg_3 = Register::R8;
arg_reg_4 = Register::R9;
}
#[cfg(unix)]
{
arg_reg_1 = Register::DL;
arg_reg_2 = Register::RSI;
arg_reg_3 = Register::RDX;
arg_reg_4 = Register::RCX;
}
let mut set_size = |s: usize| {
if size_op == 0 {
size_op = s;
} else {
assert_eq!(size_op, s);
}
};
// we put the operand value into rax and than push it on stack, so the
// only clobbered register is rax, and if actual operand value uses it,
// we simply restore it from stack
for (op_num, op) in [op1, op2].iter().enumerate() {
let op_num: i64 = op_num.try_into().unwrap();
match op {
CmplogOperandType::Reg(reg) => {
let info = reg.info();
set_size(info.size());
let reg_largest = reg.full_register();
if reg_largest == Register::RAX {
insts.push(
// we rely on the fact that latest saved register on stack is rax
Instruction::with1(
Code::Push_rm64,
MemoryOperand::with_base_displ(Register::RSP, op_num * 8),
)
.unwrap(),
);
} else {
insts.push(Instruction::with1(Code::Push_rm64, reg_largest).unwrap());
}
}
CmplogOperandType::Mem(reg_base, reg_index, disp, scale, mem_size) => {
let size;
let inst;
match *mem_size {
MemorySize::UInt64 | MemorySize::Int64 => {
size = 8;
inst = Code::Mov_r64_rm64;
}
MemorySize::UInt32 | MemorySize::Int32 => {
size = 4;
inst = Code::Mov_r32_rm32;
}
MemorySize::UInt16 | MemorySize::Int16 => {
size = 2;
inst = Code::Mov_r16_rm16;
}
_ => {
println!("Invalid memory size");
size = 4;
inst = Code::Push_rm32;
}
}
set_size(size);
let mut disp_adjusted = *disp;
let mut reg_base = *reg_base;
if reg_base == Register::RSP {
// 0x38 is an amount of bytes used by save_registers()
disp_adjusted = disp_adjusted + 0x38 + 8_i64 * op_num;
}
let tmp_reg_adjusted = *tmp_reg.get(&size).unwrap();
// in case of RIP, disp is an absolute address already calculated
// by iced, we can simply load it to rax (but in this case index register must
// be not rax)
if reg_base == Register::RIP {
insts.push(
Instruction::with2(Code::Mov_r64_imm64, Register::RAX, disp_adjusted)
.unwrap(),
);
reg_base = Register::RAX;
disp_adjusted = 0;
}
insts.push(
Instruction::with2(
inst,
tmp_reg_adjusted,
MemoryOperand::with_base_index_scale_displ_size(
reg_base,
*reg_index,
*scale,
disp_adjusted,
1,
),
)
.unwrap(),
);
insts.push(Instruction::with1(Code::Push_rm64, Register::RAX).unwrap());
}
CmplogOperandType::Imm(imm) => {
insts.push(Instruction::with1(Code::Pushq_imm32, *imm as i32).unwrap());
}
}
}
insts.push(Instruction::with2(Code::Mov_r8_imm8, arg_reg_1, size_op as u64).unwrap());
insts.push(Instruction::with1(Code::Pop_r64, arg_reg_2).unwrap());
insts.push(Instruction::with1(Code::Pop_r64, arg_reg_3).unwrap());
insts.push(Instruction::with2(Code::Mov_r64_imm64, arg_reg_4, address).unwrap());
let block = InstructionBlock::new(&insts, 0);
let block = BlockEncoder::encode(64, block, DecoderOptions::NONE).unwrap();
writer.put_bytes(block.code_buffer.as_slice());
writer.put_call_address((CmpLogRuntime::populate_lists as usize).try_into().unwrap());
writer.put_bytes(&self.restore_registers.clone().unwrap());
}
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
#[inline] #[inline]
@ -274,8 +566,8 @@ impl CmpLogRuntime {
output: &StalkerOutput, output: &StalkerOutput,
op1: &CmplogOperandType, //first operand of the comparsion op1: &CmplogOperandType, //first operand of the comparsion
op2: &CmplogOperandType, //second operand of the comparsion op2: &CmplogOperandType, //second operand of the comparsion
_shift: Option<(ShiftStyle, u8)>, _shift: &Option<(ShiftStyle, u8)>,
special_case: Option<SpecialCmpLogCase>, special_case: &Option<SpecialCmpLogCase>,
) { ) {
let writer = output.writer(); let writer = output.writer();
@ -347,6 +639,114 @@ impl CmpLogRuntime {
)); ));
} }
#[cfg(all(feature = "cmplog", target_arch = "x86_64"))]
#[allow(clippy::similar_names)]
#[inline]
/// Check if the current instruction is cmplog relevant one(any opcode which sets the flags)
#[must_use]
pub fn cmplog_is_interesting_instruction(
_decoder: InstDecoder,
_address: u64,
instr: &Insn,
) -> Option<(
CmplogOperandType,
CmplogOperandType,
Option<SpecialCmpLogCase>,
Option<SpecialCmpLogCase>,
)> {
let bytes = instr.bytes();
let mut decoder =
iced_x86::Decoder::with_ip(64, bytes, instr.address(), iced_x86::DecoderOptions::NONE);
if !decoder.can_decode() {
return None;
}
let mut instruction = iced_x86::Instruction::default();
decoder.decode_out(&mut instruction);
match instruction.mnemonic() {
iced_x86::Mnemonic::Cmp | iced_x86::Mnemonic::Sub => {} // continue
_ => return None,
}
if instruction.op_count() != 2 {
return None;
}
// we don't support rip related reference with index register yet
if instruction.memory_base() == Register::RIP
&& instruction.memory_index() != Register::None
{
return None;
}
if instruction.memory_size() == MemorySize::UInt8
|| instruction.memory_size() == MemorySize::Int8
{
return None;
}
let op1 = match instruction.op0_kind() {
OpKind::Register => CmplogOperandType::Reg(instruction.op0_register()),
OpKind::Immediate16
| OpKind::Immediate32
| OpKind::Immediate64
| OpKind::Immediate32to64 => CmplogOperandType::Imm(instruction.immediate(0)),
OpKind::Memory => {
// can't use try_into here, since we need to cast u64 to i64
// which is fine in this case
#[allow(clippy::cast_possible_wrap)]
CmplogOperandType::Mem(
instruction.memory_base(),
instruction.memory_index(),
instruction.memory_displacement64() as i64,
instruction.memory_index_scale(),
instruction.memory_size(),
)
}
_ => {
return None;
}
};
let op2 = match instruction.op1_kind() {
OpKind::Register => CmplogOperandType::Reg(instruction.op1_register()),
OpKind::Immediate16
| OpKind::Immediate32
| OpKind::Immediate64
| OpKind::Immediate32to64 => CmplogOperandType::Imm(instruction.immediate(1)),
OpKind::Memory =>
{
#[allow(clippy::cast_possible_wrap)]
CmplogOperandType::Mem(
instruction.memory_base(),
instruction.memory_index(),
instruction.memory_displacement64() as i64,
instruction.memory_index_scale(),
instruction.memory_size(),
)
}
_ => {
return None;
}
};
// debug print, shows all the cmp instrumented instructions
if log::log_enabled!(log::Level::Debug) {
use iced_x86::{Formatter, NasmFormatter};
let mut formatter = NasmFormatter::new();
let mut output = String::new();
formatter.format(&instruction, &mut output);
log::debug!(
"inst: {:x} {:?}, {:?} {:?}",
instruction.ip(),
output,
op1,
op2
);
}
Some((op1, op2, None, None))
}
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(feature = "cmplog", target_arch = "aarch64"))]
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
#[inline] #[inline]

View File

@ -30,7 +30,7 @@ use yaxpeax_x86::amd64::InstDecoder;
#[cfg(unix)] #[cfg(unix)]
use crate::asan::asan_rt::AsanRuntime; use crate::asan::asan_rt::AsanRuntime;
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(feature = "cmplog")]
use crate::cmplog_rt::CmpLogRuntime; use crate::cmplog_rt::CmpLogRuntime;
use crate::{coverage_rt::CoverageRuntime, drcov_rt::DrCovRuntime}; use crate::{coverage_rt::CoverageRuntime, drcov_rt::DrCovRuntime};
@ -520,7 +520,10 @@ where
} }
} }
#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] #[cfg(all(
feature = "cmplog",
any(target_arch = "aarch64", target_arch = "x86_64")
))]
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, shift, special_case)) = if let Some((op1, op2, shift, special_case)) =
CmpLogRuntime::cmplog_is_interesting_instruction(decoder, address, instr) CmpLogRuntime::cmplog_is_interesting_instruction(decoder, address, instr)
@ -529,11 +532,11 @@ where
//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( rt.emit_comparison_handling(
address, address,
&output, output,
&op1, &op1,
&op2, &op2,
shift, &shift,
special_case, &special_case,
); );
} }
} }

View File

@ -94,7 +94,6 @@ pub mod drcov_rt;
pub mod executor; pub mod executor;
/// Utilities /// Utilities
#[cfg(unix)]
pub mod utils; pub mod utils;
// for parsing asan and cmplog cores // for parsing asan and cmplog cores

View File

@ -2,8 +2,6 @@
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(target_arch = "x86_64")]
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")]
@ -122,7 +120,7 @@ pub fn writer_register(reg: u16, sizecode: SizeCode, zr: bool) -> Aarch64Registe
} }
/// Translate from `RegSpec` to `X86Register` /// Translate from `RegSpec` to `X86Register`
#[cfg(all(target_arch = "x86_64", unix))] #[cfg(target_arch = "x86_64")]
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),
@ -162,7 +160,8 @@ const X86_64_REGS: [(RegSpec, X86Register); 34] = [
/// The writer registers /// The writer registers
/// frida registers: <https://docs.rs/frida-gum/0.4.0/frida_gum/instruction_writer/enum.X86Register.html> /// frida registers: <https://docs.rs/frida-gum/0.4.0/frida_gum/instruction_writer/enum.X86Register.html>
#[cfg(all(target_arch = "x86_64", unix))] /// capstone registers: <https://docs.rs/capstone-sys/0.14.0/capstone_sys/x86_reg/index.html>
#[cfg(target_arch = "x86_64")]
#[must_use] #[must_use]
#[inline] #[inline]
#[allow(clippy::unused_self)] #[allow(clippy::unused_self)]
@ -177,7 +176,7 @@ pub fn writer_register(reg: RegSpec) -> X86Register {
} }
/// Translates a frida instruction to a disassembled instruction. /// Translates a frida instruction to a disassembled instruction.
#[cfg(target_arch = "x86_64")] #[cfg(all(target_arch = "x86_64", unix))]
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()
} }