diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 384826b880..cc25331022 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -116,7 +116,7 @@ jobs: run: clang-format -n -Werror --style=file $(find . -type f \( -name '*.cpp' -o -iname '*.hpp' -o -name '*.cc' -o -name '*.cxx' -o -name '*.cc' -o -name '*.h' \) | grep -v '/target/' | grep -v 'libpng-1\.6\.37' | grep -v 'stb_image\.h' | grep -v 'dlmalloc\.c' | grep -v 'QEMU-Nyx') - name: run shellcheck run: shellcheck ./scripts/*.sh - + # ---- doc check ---- - name: Build Docs run: RUSTFLAGS="--cfg docsrs" cargo +nightly doc --all-features @@ -127,11 +127,11 @@ jobs: run: cargo build --verbose - name: Build examples run: cargo build --examples --verbose - + # --- miri undefined behavior test -- - name: Run miri tests run: RUST_BACKTRACE=1 MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test - + ubuntu-clippy: runs-on: ubuntu-22.04 steps: @@ -159,7 +159,7 @@ jobs: # Clean up files to save up disk space - name: Cleanup run: cargo clean - + # --- test embedding the libafl_libfuzzer_runtime library # Fix me plz # - name: Test Build libafl_libfuzzer with embed @@ -212,9 +212,9 @@ jobs: - uses: actions/checkout@v3 - uses: Swatinem/rust-cache@v2 - name: Install smoke test deps - run: sudo ./libafl_concolic/test/smoke_test_ubuntu_deps.sh + run: sudo ./libafl_concolic/test/smoke_test_ubuntu_deps.sh - name: Run smoke test - run: ./libafl_concolic/test/smoke_test.sh + run: ./libafl_concolic/test/smoke_test.sh bindings: runs-on: ubuntu-latest @@ -257,7 +257,7 @@ jobs: # this might remove tools that are actually needed, # if set to "true" but frees about 6 GB tool-cache: false - + # all of these default to true, but feel free to set to # "false" if necessary for your workflow android: true @@ -330,7 +330,7 @@ jobs: # this might remove tools that are actually needed, # if set to "true" but frees about 6 GB tool-cache: false - + # all of these default to true, but feel free to set to # "false" if necessary for your workflow android: true @@ -553,7 +553,7 @@ jobs: - name: run x86_64 until panic! run: cd ./fuzzers/baby_no_std && cargo +nightly run || test $? -ne 0 || exit 1 - name: no_std tests - run: cd ./libafl && cargo test --no-default-features + run: cd ./libafl && cargo test --no-default-features nostd-clippy: runs-on: ubuntu-latest @@ -600,12 +600,12 @@ jobs: - uses: ilammy/msvc-dev-cmd@v1 - name: install cxx bridge run: cargo install cxxbridge-cmd - - name: Build fuzzers/libfuzzer_stb_image + - name: Build fuzzers/libfuzzer_stb_image run: cd fuzzers/libfuzzer_stb_image && cargo build --release - name: Build fuzzers/frida_libpng run: cd fuzzers/frida_libpng/ && cargo make test - 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 run: cd fuzzers/tinyinst_simple/ && cargo make test @@ -622,7 +622,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - + macos: runs-on: macOS-latest steps: @@ -668,7 +668,7 @@ jobs: - name: Build iOS run: cargo build --target aarch64-apple-ios && cd libafl_frida && cargo build --target aarch64-apple-ios && cd .. - name: Build Android - run: cargo ndk -t arm64-v8a build --release + run: cargo ndk -t arm64-v8a build --release #run: cargo build --target aarch64-linux-android # TODO: Figure out how to properly build stuff with clang #- name: Add clang path to $PATH env diff --git a/fuzzers/frida_gdiplus/.gitignore b/fuzzers/frida_gdiplus/.gitignore index fa4015c1ef..99540d33ad 100644 --- a/fuzzers/frida_gdiplus/.gitignore +++ b/fuzzers/frida_gdiplus/.gitignore @@ -1,5 +1,7 @@ corpus_discovered +output_t* *.exp *.lib *.obj +*.lnk diff --git a/fuzzers/frida_gdiplus/Cargo.toml b/fuzzers/frida_gdiplus/Cargo.toml index bc9d435c96..bc374e315f 100644 --- a/fuzzers/frida_gdiplus/Cargo.toml +++ b/fuzzers/frida_gdiplus/Cargo.toml @@ -32,3 +32,4 @@ libafl_targets = { path = "../../libafl_targets", features = ["sancov_cmplog"] } libloading = "0.7" mimalloc = { version = "*", default-features = false } color-backtrace = "0.5" +iced-x86 = { version = "1.20.0", features = ["code_asm"] } diff --git a/fuzzers/frida_gdiplus/Makefile.toml b/fuzzers/frida_gdiplus/Makefile.toml index e204f2a5c4..5d7f24b555 100644 --- a/fuzzers/frida_gdiplus/Makefile.toml +++ b/fuzzers/frida_gdiplus/Makefile.toml @@ -24,6 +24,12 @@ script=''' 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 [tasks.fuzzer] linux_alias = "unsupported" @@ -56,6 +62,28 @@ linux_alias = "unsupported" mac_alias = "unsupported" 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] script_runner = "@shell" script=''' @@ -64,4 +92,4 @@ start "" "frida_gdiplus.exe" -H harness.dll -i corpus -o output --libs-to-instru ping -n 10 127.0.0.1>NUL && taskkill /im frida_gdiplus.exe /F >nul 2>nul dir /a-d "corpus_discovered\*" && (echo Files exist) || (exit /b 1337) ''' -dependencies = [ "fuzzer", "harness" ] \ No newline at end of file +dependencies = [ "fuzzer", "harness" ] diff --git a/fuzzers/frida_gdiplus/cmplog_test.asm b/fuzzers/frida_gdiplus/cmplog_test.asm new file mode 100644 index 0000000000..bd4de12df9 --- /dev/null +++ b/fuzzers/frida_gdiplus/cmplog_test.asm @@ -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 diff --git a/fuzzers/frida_gdiplus/cmplog_test.def b/fuzzers/frida_gdiplus/cmplog_test.def new file mode 100644 index 0000000000..1db236d34c --- /dev/null +++ b/fuzzers/frida_gdiplus/cmplog_test.def @@ -0,0 +1,8 @@ +EXPORTS + t1 + t2 + t3 + t4 + t5 + t6 + t7 diff --git a/fuzzers/frida_gdiplus/src/fuzzer.rs b/fuzzers/frida_gdiplus/src/fuzzer.rs index 0af353a2fd..64b4514e9f 100644 --- a/fuzzers/frida_gdiplus/src/fuzzer.rs +++ b/fuzzers/frida_gdiplus/src/fuzzer.rs @@ -323,6 +323,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { println!("We imported {} inputs from disk.", state.corpus().count()); } + println!("Cmplog observer is enabled"); // Create an observation channel using cmplog map let cmplog_observer = CmpLogObserver::new("cmplog", true); diff --git a/fuzzers/frida_libpng/src/fuzzer.rs b/fuzzers/frida_libpng/src/fuzzer.rs index 6972a55f4d..ac910c0b68 100644 --- a/fuzzers/frida_libpng/src/fuzzer.rs +++ b/fuzzers/frida_libpng/src/fuzzer.rs @@ -216,6 +216,7 @@ unsafe fn fuzz(options: &FuzzerOptions) -> Result<(), Error> { let coverage = CoverageRuntime::new(); let cmplog = CmpLogRuntime::new(); + println!("cmplog runtime created"); let mut frida_helper = FridaInstrumentationHelper::new(&gum, options, tuple_list!(coverage, cmplog)); diff --git a/libafl/src/executors/inprocess.rs b/libafl/src/executors/inprocess.rs index e2da742965..599ac72bc1 100644 --- a/libafl/src/executors/inprocess.rs +++ b/libafl/src/executors/inprocess.rs @@ -1065,6 +1065,7 @@ pub mod windows_asan_handler { } } +/// Handles exceptions on Windows #[cfg(all(windows, feature = "std"))] pub mod windows_exception_handler { #[cfg(feature = "std")] diff --git a/libafl/src/mutators/token_mutations.rs b/libafl/src/mutators/token_mutations.rs index ca76c927c4..e0067b5527 100644 --- a/libafl/src/mutators/token_mutations.rs +++ b/libafl/src/mutators/token_mutations.rs @@ -452,6 +452,7 @@ where let cmps_len = { let meta = state.metadata_map().get::(); + log::trace!("meta: {:x?}", meta); if meta.is_none() { return Ok(MutationResult::Skipped); } diff --git a/libafl_frida/Cargo.toml b/libafl_frida/Cargo.toml index 62724c897b..c86cc2d68a 100644 --- a/libafl_frida/Cargo.toml +++ b/libafl_frida/Cargo.toml @@ -22,7 +22,7 @@ all-features = true [features] default = ["serdeany_autoreg"] -cmplog = [] +cmplog = ["iced-x86"] serdeany_autoreg = ["libafl_bolts/serdeany_autoreg"] [build-dependencies] @@ -33,6 +33,7 @@ yaxpeax-arm = "0.2.4" [target.'cfg(target_arch = "x86_64")'.dependencies] yaxpeax-x86 = "1.2.2" +iced-x86 = { version = "1.20.0", features = ["code_asm"], optional = true } [dependencies] libafl = { path = "../libafl", default-features = false, version = "0.11.2", features = [ diff --git a/libafl_frida/src/cmplog_rt.rs b/libafl_frida/src/cmplog_rt.rs index 614a0f7436..1be78974b0 100644 --- a/libafl_frida/src/cmplog_rt.rs +++ b/libafl_frida/src/cmplog_rt.rs @@ -3,11 +3,11 @@ //! This allows the fuzzer to potentially solve the compares, if a compare value is directly //! 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. -use std::ffi::c_void; -use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; -#[cfg(target_arch = "aarch64")] -use frida_gum_sys::Insn; +#[cfg(all(feature = "cmplog", target_arch = "x86_64"))] +use std::collections::HashMap; + +use dynasmrt::dynasm; use libafl::{ inputs::{HasTargetBytes, Input}, Error, @@ -24,11 +24,19 @@ extern "C" { use std::rc::Rc; use frida_gum::ModuleMap; +#[cfg(target_arch = "x86_64")] +use frida_gum::{instruction_writer::InstructionWriter, stalker::StalkerOutput}; #[cfg(target_arch = "aarch64")] use frida_gum::{ instruction_writer::{Aarch64Register, IndexMode, InstructionWriter}, 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"))] use crate::utils::{disas_count, writer_register}; @@ -43,8 +51,15 @@ pub enum SpecialCmpLogCase { Tbnz, } +#[cfg(all(feature = "cmplog", target_arch = "x86_64"))] +/// Speciial `CmpLog` Cases for `aarch64` +#[derive(Debug)] +pub enum SpecialCmpLogCase {} + #[cfg(target_arch = "aarch64")] 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`] /// @@ -73,13 +88,35 @@ pub enum CmplogOperandType { // 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 /// `LibAFL` can use this knowledge for powerful mutations. #[derive(Debug)] +#[cfg(target_arch = "aarch64")] pub struct CmpLogRuntime { - ops_save_register_and_blr_to_populate: Option>, - ops_handle_tbz_masking: Option>, - ops_handle_tbnz_masking: Option>, + save_register_and_blr_to_populate: Option>, + handle_tbz_masking: Option>, + handle_tbnz_masking: Option>, +} + +/// `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>, + restore_registers: Option>, } impl FridaRuntime for CmpLogRuntime { @@ -106,6 +143,7 @@ impl FridaRuntime for CmpLogRuntime { impl CmpLogRuntime { /// Create a new [`CmpLogRuntime`] #[must_use] + #[cfg(target_arch = "aarch64")] pub fn new() -> CmpLogRuntime { Self { 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 #[allow(clippy::unused_self)] + #[cfg(target_arch = "aarch64")] extern "C" fn populate_lists(&mut self, op1: u64, op2: u64, retaddr: u64) { // log::trace!( // "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. #[allow(clippy::similar_names)] + #[cfg(target_arch = "aarch64")] fn generate_instrumentation_blobs(&mut self) { macro_rules! blr_to_populate { ($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::::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::::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::::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::::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 #[inline] #[must_use] + #[cfg(target_arch = "aaarch64")] pub fn ops_save_register_and_blr_to_populate(&self) -> &[u8] { 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 #[inline] #[must_use] + #[cfg(target_arch = "aaarch64")] pub fn ops_handle_tbz_masking(&self) -> &[u8] { self.ops_handle_tbz_masking.as_ref().unwrap() } @@ -260,11 +400,163 @@ impl CmpLogRuntime { /// Get the blob which handles the tbnz opcode masking #[inline] #[must_use] + #[cfg(target_arch = "aaarch64")] pub fn ops_handle_tbnz_masking(&self) -> &[u8] { self.ops_handle_tbnz_masking.as_ref().unwrap() } /// 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, + _special_case: &Option, + ) { + 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"))] #[allow(clippy::too_many_lines)] #[inline] @@ -274,8 +566,8 @@ impl CmpLogRuntime { output: &StalkerOutput, op1: &CmplogOperandType, //first operand of the comparsion op2: &CmplogOperandType, //second operand of the comparsion - _shift: Option<(ShiftStyle, u8)>, - special_case: Option, + _shift: &Option<(ShiftStyle, u8)>, + special_case: &Option, ) { 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, + Option, + )> { + 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"))] #[allow(clippy::similar_names)] #[inline] diff --git a/libafl_frida/src/helper.rs b/libafl_frida/src/helper.rs index a2fb39bbb9..70ca46f467 100644 --- a/libafl_frida/src/helper.rs +++ b/libafl_frida/src/helper.rs @@ -30,7 +30,7 @@ use yaxpeax_x86::amd64::InstDecoder; #[cfg(unix)] use crate::asan::asan_rt::AsanRuntime; -#[cfg(all(feature = "cmplog", target_arch = "aarch64"))] +#[cfg(feature = "cmplog")] use crate::cmplog_rt::CmpLogRuntime; 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::() { if let Some((op1, op2, shift, special_case)) = 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) rt.emit_comparison_handling( address, - &output, + output, &op1, &op2, - shift, - special_case, + &shift, + &special_case, ); } } diff --git a/libafl_frida/src/lib.rs b/libafl_frida/src/lib.rs index 17efe2c9b1..cc58092ef1 100644 --- a/libafl_frida/src/lib.rs +++ b/libafl_frida/src/lib.rs @@ -94,7 +94,6 @@ pub mod drcov_rt; pub mod executor; /// Utilities -#[cfg(unix)] pub mod utils; // for parsing asan and cmplog cores diff --git a/libafl_frida/src/utils.rs b/libafl_frida/src/utils.rs index 9126492768..b33d9c07ed 100644 --- a/libafl_frida/src/utils.rs +++ b/libafl_frida/src/utils.rs @@ -2,8 +2,6 @@ use frida_gum::instruction_writer::Aarch64Register; #[cfg(target_arch = "x86_64")] use frida_gum::instruction_writer::X86Register; -#[cfg(target_arch = "x86_64")] -use frida_gum_sys; #[cfg(target_arch = "aarch64")] use num_traits::cast::FromPrimitive; #[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` -#[cfg(all(target_arch = "x86_64", unix))] +#[cfg(target_arch = "x86_64")] const X86_64_REGS: [(RegSpec, X86Register); 34] = [ (RegSpec::eax(), X86Register::Eax), (RegSpec::ecx(), X86Register::Ecx), @@ -162,7 +160,8 @@ const X86_64_REGS: [(RegSpec, X86Register); 34] = [ /// The writer registers /// frida registers: -#[cfg(all(target_arch = "x86_64", unix))] +/// capstone registers: +#[cfg(target_arch = "x86_64")] #[must_use] #[inline] #[allow(clippy::unused_self)] @@ -177,7 +176,7 @@ pub fn writer_register(reg: RegSpec) -> X86Register { } /// 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 { decoder.decode_slice(frida_insn.bytes()).unwrap() }