diff --git a/Cargo.lock b/Cargo.lock index 6fa29e4..b07f80e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,12 +65,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "anyhow" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" - [[package]] name = "arrayvec" version = "0.5.2" @@ -83,12 +77,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "bitflags" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" - [[package]] name = "bitflags" version = "2.4.1" @@ -207,14 +195,12 @@ dependencies = [ name = "deoptimizer" version = "0.1.0" dependencies = [ - "anyhow", "chrono", "clap", "colored", "ctrlc", "hexdump", "iced-x86", - "keystone", "log", "rand", "thiserror", @@ -321,16 +307,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "keystone" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0409d156f77bf2a5fc3a9f78233b0594ca5cf7a6021fa617403e6c7a2dd421fe" -dependencies = [ - "bitflags 0.7.0", - "libc", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -361,7 +337,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.1", + "bitflags", "cfg-if", "libc", ] @@ -441,7 +417,7 @@ version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", diff --git a/Cargo.toml b/Cargo.toml index e55b2fb..edaec7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.75" chrono = "0.4.31" clap = { version = "4.4.7", features = ["derive"] } colored = "2.0.4" ctrlc = "3.4.1" hexdump = "0.1.1" iced-x86 = "1.20.0" -keystone = "0.9.0" log = "0.4.20" rand = "0.8.5" thiserror = "1.0.50" diff --git a/src/main.rs b/src/main.rs index ca09b3e..6d608f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use log::{error, info, warn, LevelFilter}; +use std::fs::File; use std::io::Write; mod options; @@ -24,25 +25,25 @@ fn main() { warn!("The output size will drasstically increase.") } - let file = utils::read_file(opts.file.clone()).expect("failed reading target file"); + let file = utils::read_file(opts.file.clone()).expect("failed reading target file!"); + let mut out_file = File::create(opts.source).expect("source file creation failed!"); info!("File size: {}", file.len()); let mut deopt = x86_64::Deoptimizer::new(); deopt.set_syntax(opts.syntax).expect("invalid syntax"); - let out = deopt - .disassemble(&file, opts.mode, 0x401000) - .expect("disassembly failed!"); - println!("{}", out); - match deopt.assemble(out.clone(), opts.mode, 0x401000) { - Ok(b) => { - let mut f2 = std::fs::File::create("success.bin").expect("failed opening file!"); - f2.write_all(&b.bytes).expect("failed writing!"); - } + let acode = deopt + .analyze(&file, opts.bitness, 0x401000) + .expect("code analysis failed!"); + // let out = deopt.disassemble(&acode).expect("disassembly failed!"); + // println!("{}", out); + info!("Deoptimizing..."); + let bin = deopt.deoptimize(&acode); + let bytes = match bin { + Ok(b) => b, Err(e) => { error!("{}", e); - let mut f2 = std::fs::File::create("error.asm").expect("failed opening file!"); - let _ = f2.write_all(b"[BITS 32]\n"); - f2.write_all(out.as_bytes()).expect("failed writing!"); + return; } }; - // hexdump::hexdump(&bin.bytes); + let _ = out_file.write_all(bytes.as_bytes()); + info!("All done!"); } diff --git a/src/options.rs b/src/options.rs index e38f362..23d7aa0 100644 --- a/src/options.rs +++ b/src/options.rs @@ -18,13 +18,17 @@ pub struct Options { #[arg(long, short = 'f', default_value_t = String::new())] pub file: String, + /// source assembly file. + #[arg(long, short = 's', default_value_t = String::new())] + pub source: String, + /// assembler formatter syntax (nasm/masm/intel/gas). - #[arg(long, short = 's', default_value_t = String::from("keystone"))] + #[arg(long, default_value_t = String::from("keystone"))] pub syntax: String, - /// bitness of the binary file (32/64). - #[arg(long, short = 'm', default_value_t = 32)] - pub mode: u32, + /// bitness of the binary file (16/32/64). + #[arg(long, short = 'b', default_value_t = 64)] + pub bitness: u32, /// total number of deoptimization cycles. #[arg(long, short = 'c', default_value_t = 1)] @@ -34,6 +38,10 @@ pub struct Options { #[arg(long, short = 'F', default_value_t = 0.5)] pub freq: f64, + /// allow processing of invalid instructions. + #[arg(long)] + pub allow_invalid: bool, + /// verbose output mode. #[arg(long, short = 'v')] pub verbose: bool, diff --git a/src/x86_64/deoptimizer.rs b/src/x86_64/deoptimizer.rs index 185b989..28dbf16 100644 --- a/src/x86_64/deoptimizer.rs +++ b/src/x86_64/deoptimizer.rs @@ -1,14 +1,12 @@ -extern crate keystone; use iced_x86::*; -use keystone::{AsmResult, Keystone}; -use log::{error, info, warn}; -use rand::seq::SliceRandom; -use std::collections::HashMap; +use log::{debug, error, info, warn}; +use rand::{distributions::uniform::UniformSampler, seq::SliceRandom, Rng}; +use std::{collections::HashMap, error}; use thiserror::Error; use crate::x86_64::{ apply_ap_transform, apply_itr_transform, apply_li_transform, apply_om_transform, - apply_rs_transform, + apply_rs_transform, set_branch_target, to_db_mnemonic, }; #[derive(Error, Debug)] @@ -23,6 +21,14 @@ pub enum DeoptimizerError { InstructionNotFound, #[error("Code analysis results are not found.")] MissingCodeAnalysis, + #[error("Near branch value too large.")] + NearBranchTooBig, + #[error("Far branch value too large.")] + FarBranchTooBig, + #[error("Found invalid instruction.")] + InvalidInstruction, + #[error("Instruction encoding failed: {0}")] + EncodingFail(#[from] IcedError), } enum AssemblySyntax { @@ -33,30 +39,77 @@ enum AssemblySyntax { Gas, } +pub struct AnalyzedCode { + bitness: u32, + bytes: Vec, + start_addr: u64, + code: Vec, + addr_map: HashMap, + known_addr_table: Vec, + near_branch_targets: Vec, + far_branch_targets: Vec, + cfe_addr_table: Vec, +} + +impl AnalyzedCode { + fn is_known_address(&self, addr: u64) -> bool { + self.known_addr_table.contains(&addr) + } + fn is_near_branch_target(&self, addr: u64) -> bool { + self.near_branch_targets.contains(&addr) + } + fn is_far_branch_target(&self, addr: u64) -> bool { + self.far_branch_targets.contains(&addr) + } + fn get_random_cfe_addr(&self) -> Option { + self.cfe_addr_table.choose(&mut rand::thread_rng()).copied() + } + pub fn is_affecting_cf(&self, inst: &Instruction) -> Result { + // Check if the instruction exists in self.code + if self.addr_map.get(&inst.ip()).is_none() { + return Err(DeoptimizerError::InstructionNotFound); + } + + let mut rflags = RflagsBits::NONE; + let m_rflags = inst.rflags_modified(); + if m_rflags == RflagsBits::NONE { + return Ok(false); + } + + let mut cursor = inst.clone(); + while self.addr_map.get(&cursor.next_ip()).is_some() { + cursor = *self.addr_map.get(&cursor.next_ip()).unwrap(); + rflags = rflags | cursor.rflags_modified(); + if (cursor.rflags_read() & m_rflags) > 0 { + break; + } + if (m_rflags & rflags) == m_rflags { + return Ok(false); + } + } + + Ok(true) + } +} + pub struct Deoptimizer { + /// Total number of deoptimization cycles. + pub cycle: u32, /// Deoptimization frequency. pub freq: f64, - /// Is the given code analyzed. - pub is_analyzed: bool, - code_map: Option>, - disassembly: Option, + /// Allow processing of invalid instructions. + pub allow_invalid: bool, + /// Disassembler syntax. syntax: AssemblySyntax, - known_addr_table: Option>, - known_branch_targets: Option>, - cfe_addr_table: Option>, } impl Deoptimizer { pub fn new() -> Self { Self { + cycle: 1, freq: 0.5, - is_analyzed: false, - code_map: None, - disassembly: None, + allow_invalid: false, syntax: AssemblySyntax::Nasm, - known_addr_table: None, - known_branch_targets: None, - cfe_addr_table: None, } } @@ -72,57 +125,72 @@ impl Deoptimizer { Ok(()) } - fn analyze(&mut self, code: &[u8], mode: u32, start_addr: u64) { - info!("Analyzing {} bytes with {} bit mode...", code.len(), mode); - let mut decoder = Decoder::with_ip(mode, code, start_addr, DecoderOptions::NONE); - let mut instruction = Instruction::default(); + pub fn analyze( + &mut self, + bytes: &[u8], + bitness: u32, + start_addr: u64, + ) -> Result { + info!( + "Analyzing {} bytes with {} bit mode...", + bytes.len(), + bitness + ); + let mut decoder = Decoder::with_ip(bitness, bytes, start_addr, DecoderOptions::NONE); + let mut inst = Instruction::default(); let mut known_addr_table = Vec::new(); - let mut known_branch_targets = Vec::new(); + let mut near_branch_targets = Vec::new(); + let mut far_branch_targets = Vec::new(); let mut cfe_addr_table = Vec::new(); - let mut code: HashMap = HashMap::new(); + let mut code = Vec::new(); + let mut addr_map: HashMap = HashMap::new(); while decoder.can_decode() { - decoder.decode_out(&mut instruction); - code.insert(instruction.ip(), instruction); + decoder.decode_out(&mut inst); + if inst.is_invalid() { + warn!("Found invalid instruction at: 0x{:016X}", inst.ip()); + if !self.allow_invalid { + return Err(DeoptimizerError::InvalidSyntax); + } + } + code.push(inst); + addr_map.insert(inst.ip(), inst); + + if inst.op_count() == 1 + && matches!(inst.op0_kind(), OpKind::FarBranch16 | OpKind::FarBranch32) + { + match inst.op0_kind() { + OpKind::FarBranch32 => far_branch_targets.push(inst.far_branch32() as u64), + OpKind::FarBranch16 => far_branch_targets.push(inst.far_branch16() as u64), + _ => (), + } + } + // Push to known address table - known_addr_table.push(instruction.ip()); - let nbt = instruction.near_branch_target(); + known_addr_table.push(inst.ip()); + let nbt = inst.near_branch_target(); if nbt != 0 { - known_branch_targets.push(nbt); + near_branch_targets.push(nbt); } // Push to control flow exit address table if it is a JMP of RET - if instruction.mnemonic() == iced_x86::Mnemonic::Ret - || instruction.mnemonic() == iced_x86::Mnemonic::Retf - || instruction.mnemonic() == iced_x86::Mnemonic::Jmp + if inst.mnemonic() == Mnemonic::Ret + || inst.mnemonic() == Mnemonic::Retf + || inst.mnemonic() == Mnemonic::Jmp { - cfe_addr_table.push(instruction.ip()) + cfe_addr_table.push(inst.ip()) } } - self.code_map = Some(code); - self.known_addr_table = Some(known_addr_table); - self.known_branch_targets = Some(known_branch_targets); - self.cfe_addr_table = Some(cfe_addr_table); - self.is_analyzed = true; - } - fn is_known_address(&mut self, addr: u64) -> bool { - if let Some(table) = &self.known_addr_table { - return table.contains(&addr); - } - false - } - - fn is_branch_target(&mut self, addr: u64) -> bool { - if let Some(table) = &self.known_branch_targets { - return table.contains(&addr); - } - false - } - - fn get_random_cfe_addr(&self) -> Option { - if let Some(table) = &self.cfe_addr_table { - return table.choose(&mut rand::thread_rng()).copied(); - } - None + Ok(AnalyzedCode { + bitness, + bytes: bytes.to_vec(), + start_addr, + code, + addr_map, + known_addr_table, + near_branch_targets, + far_branch_targets, + cfe_addr_table, + }) } pub fn format_instruction(&self, inst: &Instruction) -> String { @@ -158,154 +226,177 @@ impl Deoptimizer { result } - pub fn disassemble( - &mut self, - code: &[u8], - mode: u32, - start_addr: u64, - ) -> Result { - let mut decoder = Decoder::with_ip(mode, code, start_addr, DecoderOptions::NONE); + pub fn disassemble(&mut self, acode: &AnalyzedCode) -> Result { + info!( + "Disassembling at -> 0x{:X} (mode={})", + acode.start_addr, acode.bitness + ); let mut result = String::new(); - // let mut info_factory = InstructionInfoFactory::new(); - let mut instruction = Instruction::default(); - // Analyze the given binary and genereate nessesary tables... - self.analyze(code, mode, start_addr); - info!("Disassembling at -> 0x{:X} (mode={mode})", start_addr); - while decoder.can_decode() { - decoder.decode_out(&mut instruction); - // let is_cfe = self.is_affecting_cf(&instruction)?; - if instruction.is_invalid() { - warn!("Found invalid instruction at {}", instruction.ip()); - let start_index = (instruction.ip() - start_addr) as usize; - let instr_bytes = &code[start_index..start_index + instruction.len()]; - result += &format!( - "loc_{:016X}: {}\n", - instruction.ip(), - to_db_mnemonic(instr_bytes) + for inst in acode.code.clone() { + if inst.is_invalid() { + warn!( + "Inlining invalid instruction bytes at: 0x{:016X}", + inst.ip() ); + let start_index = (inst.ip() - acode.start_addr) as usize; + let instr_bytes = &acode.bytes[start_index..start_index + inst.len()]; + result += &format!("loc_{:016X}: {}\n", inst.ip(), to_db_mnemonic(instr_bytes)); continue; } - - let temp = self.format_instruction(&instruction); - let nbt = instruction.near_branch_target(); + let temp = self.format_instruction(&inst); + let nbt = inst.near_branch_target(); if nbt != 0 { - if self.is_known_address(nbt) { + if acode.is_known_address(nbt) { result += &format!( "loc_{:016X}: {} {}\n", - instruction.ip(), + inst.ip(), temp.split(' ').next().unwrap(), &format!("loc_{:016X}", nbt) ); continue; } else { - warn!("Misaligned instruction detected at {}", instruction.ip()) + warn!("Misaligned instruction detected at {}", inst.ip()) } } - result += &format!("loc_{:016X}: {}\n", instruction.ip(), temp); + result += &format!("loc_{:016X}: {}\n", inst.ip(), temp); } - self.disassembly = Some(result.clone()); Ok(result) } - pub fn is_affecting_cf(&self, inst: &Instruction) -> Result { - // First check if the code is analyzed - if !self.is_analyzed || self.code_map.is_none() { - return Err(DeoptimizerError::MissingCodeAnalysis); - } - let code_map = self.code_map.clone().unwrap(); - - // Check if the instruction exists in self.code - if code_map.get(&inst.ip()).is_none() { - return Err(DeoptimizerError::InstructionNotFound); - } - - let mut rflags = RflagsBits::NONE; - let m_rflags = inst.rflags_modified(); - if m_rflags == RflagsBits::NONE { - return Ok(false); - } - - let mut cursor = inst.clone(); - while code_map.get(&cursor.next_ip()).is_some() { - cursor = *code_map.get(&cursor.next_ip()).unwrap(); - rflags = rflags | cursor.rflags_modified(); - if (cursor.rflags_read() & m_rflags) > 0 { - break; - } - if (m_rflags & rflags) == m_rflags { - return Ok(false); - } - } - - Ok(true) - } - pub fn apply_transform( + bitness: u32, inst: &Instruction, - mode: u32, ) -> Result, DeoptimizerError> { - if inst.op_count() == 0 { - todo!("Perform call proxy..."); - } else { + if inst.op_count() > 1 { // Priority is important, start with immidate obfuscation - if let Ok(t) = apply_ap_transform(&mut inst.clone()) { + if let Ok(t) = apply_ap_transform(bitness, &mut inst.clone()) { return Ok(t); } - if let Ok(t) = apply_li_transform(&mut inst.clone()) { + if let Ok(t) = apply_li_transform(bitness, &mut inst.clone()) { return Ok(t); } - if let Ok(t) = apply_itr_transform(&mut inst.clone(), mode) { + if let Ok(t) = apply_itr_transform(bitness, &mut inst.clone()) { + // Clobbers CFLAGS return Ok(t); } // second target memory obfuscation - if let Ok(t) = apply_om_transform(&mut inst.clone(), mode) { + if let Ok(t) = apply_om_transform(bitness, &mut inst.clone()) { return Ok(t); } // Now swap registers - if let Ok(t) = apply_rs_transform(&mut inst.clone(), mode) { + if let Ok(t) = apply_rs_transform(bitness, &mut inst.clone()) { return Ok(t); } } - Err(DeoptimizerError::AllTransformsFailed) } - pub fn assemble( - &self, - code: String, - mode: u32, - addr: u64, - ) -> Result { - let m = match mode { - 16 => keystone::MODE_16, - 32 => keystone::MODE_32, - 64 => keystone::MODE_64, - _ => return Err(keystone::ERR_MODE), - }; - let engine = Keystone::new(keystone::Arch::X86, m)?; + pub fn deoptimize(&self, acode: &AnalyzedCode) -> Result { + let mut dcode = Vec::new(); // deoptimized code + // let mut nb_fix_table: HashMap = HashMap::new(); + // let mut fb_fix_table: HashMap = HashMap::new(); + let mut dcode_addr_table = Vec::new(); + // let mut new_ip: u64 = acode.start_addr; + for inst in acode.code.clone() { + // if acode.is_near_branch_target(inst.ip()) { + // nb_fix_table.insert(inst.ip(), new_ip); + // } + // if acode.is_far_branch_target(inst.ip()) { + // fb_fix_table.insert(inst.ip(), new_ip); + // } + // inst.set_ip(new_ip); + if rand::thread_rng().gen_range(0.0..1.0) < self.freq { + match Deoptimizer::apply_transform(acode.bitness, &inst) { + Ok(dinst) => { + // new_ip = dcode.last().unwrap().next_ip(); + dcode_addr_table.push(dinst.first().unwrap().ip()); + dcode = [dcode, dinst.clone()].concat(); + continue; + } + Err(e) => debug!("TransformError: {e}"), + } + } + // new_ip = inst.next_ip(); + dcode_addr_table.push(inst.ip()); + dcode.push(inst); + } - match self.syntax { - AssemblySyntax::Nasm | AssemblySyntax::Keystone => { - engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_INTEL)? + // let mut dfcode = Vec::new(); + // for mut inst in dcode.clone() { + // let nbt = inst.near_branch_target(); + // if nbt != 0 { + // if let Some(new_nbt) = nb_fix_table.get(&nbt) { + // // info!("{} -> {:X}h ({:?})", inst, new_nbt, inst.op0_kind()); + // set_branch_target(&mut inst, *new_nbt)?; + // dfcode.push(inst); + // } else { + // error!("Could not find near branch fix entry for: {}", inst); + // } + // continue; + // } + // match inst.op0_kind() { + // OpKind::FarBranch16 => { + // let fbt = inst.far_branch16(); + // if let Some(new_fbt) = fb_fix_table.get(&(fbt as u64)) { + // info!("{} -> {:X}h ({:?})", inst, new_fbt, inst.op0_kind()); + // set_branch_target(&mut inst, *new_fbt)?; + // dfcode.push(inst); + // } else { + // error!("Could not find far branch fix entry for: {}", inst); + // } + // continue; + // } + // OpKind::FarBranch32 => { + // let fbt = inst.far_branch32(); + // if let Some(new_fbt) = fb_fix_table.get(&(fbt as u64)) { + // info!("{} -> {:X}h ({:?})", inst, new_fbt, inst.op0_kind()); + // set_branch_target(&mut inst, *new_fbt)?; + // dfcode.push(inst); + // } else { + // error!("Could not find far branch fix entry for: {}", inst); + // } + // continue; + // } + // _ => (), + // } + // dfcode.push(inst); + // } + // + let mut result = String::from(format!("[BITS {}]\n", acode.bitness)); + for inst in dcode { + let temp = self.format_instruction(&inst); + let mut i_addr = rand::thread_rng().gen_range(u64::MIN..u64::MAX); + let mut label_prefix = "doc"; + if dcode_addr_table.contains(&inst.ip()) { + label_prefix = "loc"; + i_addr = inst.ip(); } - AssemblySyntax::Masm => { - engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_MASM)? + let nbt = inst.near_branch_target(); + if nbt != 0 { + result += &format!( + "{}_{:016X}: {} {}\n", + label_prefix, + i_addr, + temp.split(' ').next().unwrap(), + &format!("loc_{:016X}", nbt) + ); + continue; } - AssemblySyntax::Intel => { - engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_INTEL)? - } - AssemblySyntax::Gas => { - engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_GAS)? - } - }; - engine.asm(code, addr) + result += &format!("{}_{:016X}: {}\n", label_prefix, i_addr, temp); + } + + // let mut encoder = Encoder::new(acode.bitness); + // let mut buffer = Vec::new(); + // for inst in dfcode { + // match encoder.encode(&inst, inst.ip()) { + // Ok(_) => buffer = [buffer, encoder.take_buffer()].concat(), + // Err(e) => { + // error!("Encoding failed for -> {}", inst); + // return Err(DeoptimizerError::EncodingFail(e)); + // } + // } + // } + // println!("{}", result); + Ok(result) } } - -pub fn to_db_mnemonic(bytes: &[u8]) -> String { - let mut db_inst = String::from("db "); - for b in bytes.iter() { - db_inst += &format!("0x{:02X}, ", b); - } - db_inst.trim_end_matches(", ").to_string() -} diff --git a/src/x86_64/helpers.rs b/src/x86_64/helpers.rs index b8ddd47..689db1c 100644 --- a/src/x86_64/helpers.rs +++ b/src/x86_64/helpers.rs @@ -3,6 +3,8 @@ use iced_x86::*; use rand::thread_rng; use rand::{seq::SliceRandom, Rng}; +use super::DeoptimizerError; + pub fn randomize_immediate_value(imm: u64) -> u64 { let mut rng = rand::thread_rng(); if imm < u8::MAX as u64 { @@ -16,35 +18,82 @@ pub fn randomize_immediate_value(imm: u64) -> u64 { } } -// pub fn get_inverse_mnemonic(inst: &Instruction) -> Option { -// let ret = match inst.mnemonic() { -// Mnemonic::Add => Mnemonic::Sub, -// Mnemonic::Sub => Mnemonic::Add, -// Mnemonic::Sbb => Mnemonic::Adc, -// Mnemonic::Adc => Mnemonic::Sbb, -// Mnemonic::Rol => Mnemonic::Ror, -// Mnemonic::Ror => Mnemonic::Rol, -// Mnemonic::Xor => Mnemonic::Xor, -// Mnemonic::Inc => Mnemonic::Dec, -// Mnemonic::Dec => Mnemonic::Inc, -// Mnemonic::Or => Mnemonic::And, -// Mnemonic::And => Mnemonic::Or, -// Mnemonic::Shr => Mnemonic::Shl, -// Mnemonic::Shl => Mnemonic::Shr, -// Mnemonic::Not => Mnemonic::Not, -// _ => return None, -// }; -// Some(ret) -// } +pub fn encode( + bitness: u32, + insts: Vec, + rip: u64, +) -> Result, IcedError> { + let mut buffer = Vec::new(); + let mut result = Vec::new(); + for inst in insts.clone() { + let mut encoder = Encoder::new(bitness); + match encoder.encode(&inst, inst.ip()) { + Ok(_) => buffer = [buffer, encoder.take_buffer()].concat(), + Err(e) => return Err(e), + }; + } + let mut decoder = Decoder::new(bitness, &buffer, DecoderOptions::NONE); + decoder.set_ip(rip); + let mut inst = Instruction::default(); + while decoder.can_decode() { + decoder.decode_out(&mut inst); + result.push(inst); + } -pub fn is_ap_safe_instruction(inst: &Instruction) -> bool { + Ok(result) +} + +pub fn is_using_static_register(inst: &Instruction) -> bool { + if !matches!( + inst.mnemonic(), + Mnemonic::In + | Mnemonic::Out + | Mnemonic::Outsb + | Mnemonic::Outsd + | Mnemonic::Outsw + | Mnemonic::Or + | Mnemonic::Adc + | Mnemonic::Add + | Mnemonic::And + | Mnemonic::Cmp + | Mnemonic::Sbb + | Mnemonic::Sub + | Mnemonic::Xor + | Mnemonic::Mov + | Mnemonic::Test + | Mnemonic::Lodsb + | Mnemonic::Scasb + ) { + return false; + } + + if matches!( + inst.op0_register(), + Register::AL | Register::AX | Register::EAX + ) { + return true; + } + + if inst.op_count() > 0 && inst.op0_kind() == OpKind::Register { + if matches!( + inst.mnemonic(), + Mnemonic::In | Mnemonic::Out | Mnemonic::Outsb | Mnemonic::Outsd | Mnemonic::Outsw + ) && inst.op0_register() == Register::DX + { + return true; + } + } + false +} + +pub fn is_ap_compatible(inst: &Instruction) -> bool { matches!( inst.mnemonic(), Mnemonic::Mov | Mnemonic::Add | Mnemonic::Adc | Mnemonic::Sub | Mnemonic::Sbb ) } -pub fn is_li_safe_instruction(inst: &Instruction) -> bool { +pub fn is_li_compatible(inst: &Instruction) -> bool { matches!( inst.mnemonic(), Mnemonic::And @@ -61,20 +110,6 @@ pub fn is_li_safe_instruction(inst: &Instruction) -> bool { ) } -// pub fn get_random_arithmetic_mnemonic() -> Mnemonic { -// let arithmetics = Vec::from([ -// Mnemonic::Mov, -// Mnemonic::Add, -// Mnemonic::Sub, -// Mnemonic::Cmp, -// Mnemonic::Adc, -// Mnemonic::Sbb, -// Mnemonic::Inc, -// Mnemonic::Dec, -// ]); -// *arithmetics.choose(&mut rand::thread_rng()).unwrap() -// } - pub fn get_aprx_immediate_size(imm: u64) -> OpKind { if imm <= u8::MAX as u64 { return OpKind::Immediate8; @@ -132,9 +167,53 @@ pub fn get_register_save_seq(reg: Register) -> Result<(Instruction, Instruction) pub fn get_random_register_value(reg: Register) -> u64 { let mut rng = rand::thread_rng(); if reg.size() > 4 { - return rng.gen_range(1..u32::MAX) as u64; + return rng.gen_range(1..i32::MAX) as u64; } - rng.gen_range(1..u64::pow(2, (reg.size() * 8) as u32)) as u64 + rng.gen_range(1..i64::pow(2, (reg.size() * 8) as u32)) as u64 +} + +pub fn set_op1_immediate(inst: &mut Instruction, imm: u64) -> Result<(), TransformError> { + match inst.op1_kind() { + OpKind::Immediate8 => inst.set_immediate8(imm as u8), + OpKind::Immediate8to16 => inst.set_immediate8to16(imm as i16), + OpKind::Immediate8to32 => inst.set_immediate8to32(imm as i32), + OpKind::Immediate8to64 => inst.set_immediate8to64(imm as i64), + OpKind::Immediate8_2nd => inst.set_immediate8_2nd(imm as u8), + OpKind::Immediate16 => inst.set_immediate16(imm as u16), + OpKind::Immediate32 => inst.set_immediate32(imm as u32), + OpKind::Immediate32to64 => inst.set_immediate32to64(imm as i64), + OpKind::Immediate64 => inst.set_immediate64(imm), + _ => return Err(TransformError::UnexpectedImmediateSize), + }; + Ok(()) +} + +pub fn set_branch_target(inst: &mut Instruction, bt: u64) -> Result<(), DeoptimizerError> { + if matches!(inst.op0_kind(), OpKind::FarBranch16 | OpKind::FarBranch32) { + if bt < u16::MAX as u64 { + inst.set_op0_kind(OpKind::FarBranch16); + inst.set_far_branch16(bt as u16); + } else if bt >= u16::MAX as u64 && bt < u32::MAX as u64 { + inst.set_op0_kind(OpKind::FarBranch32); + inst.set_near_branch32(bt as u32); + } else { + return Err(DeoptimizerError::FarBranchTooBig); + } + return Ok(()); + } + if bt < u16::MAX as u64 { + inst.set_op0_kind(OpKind::NearBranch16); + inst.set_near_branch16(bt as u16); + } else if bt >= u16::MAX as u64 && bt < u32::MAX as u64 { + inst.set_op0_kind(OpKind::NearBranch32); + inst.set_near_branch32(bt as u32); + } else if bt >= u32::MAX as u64 && bt < u64::MAX { + inst.set_op0_kind(OpKind::NearBranch64); + inst.set_near_branch64(bt); + } else { + return Err(DeoptimizerError::NearBranchTooBig); + } + Ok(()) } pub fn get_random_gp_register( @@ -210,63 +289,104 @@ pub fn get_random_gp_register( } Err(TransformError::RegisterNotFound) } -// pub fn get_xor_code_with(reg: Register) -> Result { -// let c = match reg.size() { -// 1 => Code::Xor_rm8_imm8, -// 2 => Code::Xor_rm16_imm16, -// 4 | 8 => Code::Xor_rm32_imm32, -// _ => return Err(TransformError::UnexpectedRegisterSize), -// }; -// Ok(c) -// } -pub fn get_and_code_with(reg: Register) -> Result { - let c = match reg.size() { - 1 => Code::And_rm8_imm8, - 2 => Code::And_rm16_imm16, - 4 | 8 => Code::And_rm32_imm32, - _ => return Err(TransformError::UnexpectedRegisterSize), + +pub fn get_code_with_template( + mnemonic: Mnemonic, + inst: &Instruction, +) -> Result { + if (inst.op_count() != 2 && inst.op_count() != 1) || !is_immediate_operand(inst.op1_kind()) { + return Err(TransformError::InvalidTemplate); + } + let dst_op_size = match inst.op0_kind() { + OpKind::Memory => inst.memory_size().element_size(), + OpKind::Register => inst.op0_register().size(), + _ => return Err(TransformError::InvalidTemplate), }; - Ok(c) + + Ok(get_code_with_size(mnemonic, dst_op_size)?) } -pub fn get_or_code_with(reg: Register) -> Result { - let c = match reg.size() { - 1 => Code::Or_rm8_imm8, - 2 => Code::Or_rm16_imm16, - 4 | 8 => Code::Or_rm32_imm32, - _ => return Err(TransformError::UnexpectedRegisterSize), + +pub fn get_code_with_size(mnemonic: Mnemonic, size: usize) -> Result { + let c = match mnemonic { + Mnemonic::Add => match size { + 1 => Code::Add_rm8_imm8, + 2 => Code::Add_rm16_imm16, + 4 => Code::Add_rm32_imm32, + 8 => Code::Add_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Adc => match size { + 1 => Code::Adc_rm8_imm8, + 2 => Code::Adc_rm16_imm16, + 4 => Code::Adc_rm32_imm32, + 8 => Code::Adc_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Sub => match size { + 1 => Code::Sub_rm8_imm8, + 2 => Code::Sub_rm16_imm16, + 4 => Code::Sub_rm32_imm32, + 8 => Code::Sub_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Xor => match size { + 1 => Code::Xor_rm8_imm8, + 2 => Code::Xor_rm16_imm16, + 4 => Code::Xor_rm32_imm32, + 8 => Code::Xor_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::And => match size { + 1 => Code::And_rm8_imm8, + 2 => Code::And_rm16_imm16, + 4 => Code::And_rm32_imm32, + 8 => Code::And_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Or => match size { + 1 => Code::Or_rm8_imm8, + 2 => Code::Or_rm16_imm16, + 4 => Code::Or_rm32_imm32, + 8 => Code::Or_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Not => match size { + 1 => Code::Not_rm8, + 2 => Code::Not_rm16, + 4 => Code::Not_rm32, + 8 => Code::Not_rm64, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Mov => match size { + 1 => Code::Mov_rm8_imm8, + 2 => Code::Mov_rm16_imm16, + 4 => Code::Mov_rm32_imm32, + 8 => Code::Mov_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Cmp => match size { + 1 => Code::Cmp_rm8_imm8, + 2 => Code::Cmp_rm16_imm16, + 4 => Code::Cmp_rm32_imm32, + 8 => Code::Cmp_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + Mnemonic::Test => match size { + 1 => Code::Test_rm8_imm8, + 2 => Code::Test_rm16_imm16, + 4 => Code::Test_rm32_imm32, + 8 => Code::Test_rm64_imm32, + _ => return Err(TransformError::InvalidTemplate), + }, + _ => return Err(TransformError::InvalidTemplate), }; Ok(c) } -pub fn get_not_code_with(reg: Register) -> Result { - let c = match reg.size() { - 1 => Code::Not_rm8, - 2 => Code::Not_rm16, - 4 => Code::Not_rm32, - 8 => Code::Not_rm64, - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - Ok(c) -} - -pub fn get_add_code_with(reg: Register) -> Result { - let c = match reg.size() { - 1 => Code::Add_rm8_imm8, - 2 => Code::Add_rm16_imm16, - 4 => Code::Add_rm32_imm32, - 8 => Code::Add_rm64_imm32, - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - Ok(c) -} - -pub fn get_sub_code_with(reg: Register) -> Result { - let c = match reg.size() { - 1 => Code::Sub_rm8_imm8, - 2 => Code::Sub_rm16_imm16, - 4 => Code::Sub_rm32_imm32, - 8 => Code::Sub_rm64_imm32, - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - Ok(c) +pub fn to_db_mnemonic(bytes: &[u8]) -> String { + let mut db_inst = String::from("db "); + for b in bytes.iter() { + db_inst += &format!("0x{:02X}, ", b); + } + db_inst.trim_end_matches(", ").to_string() } diff --git a/src/x86_64/tests.rs b/src/x86_64/tests.rs index 72aa14a..9a37a10 100644 --- a/src/x86_64/tests.rs +++ b/src/x86_64/tests.rs @@ -4,41 +4,116 @@ mod tests { use iced_x86::*; #[test] - fn test_om_transform() { - println!("[*] testing offset mutation..."); - // let inst = Instruction::with2( - // Code::Mov_r64_rm64, - // Register::RAX, - // MemoryOperand::with_base_displ(Register::RBX, 0x10), - // ) - let inst = Instruction::with2( - Code::Mov_r64_rm64, - Register::RAX, - MemoryOperand::new( - Register::RBX, - Register::AL, - 1, - 0x10, - 1, - false, - Register::None, - ), - ) - .expect("Instruction creation failed"); - let mut formatter = NasmFormatter::new(); - let mut formatted_inst = String::new(); - formatter.format(&inst, &mut formatted_inst); - println!("[*] instruction: {}", formatted_inst); - println!("[*] Memory Base: {:?}", inst.memory_base()); - // assert_eq!(formatted_inst, "mov eax,[ebx+10h]"); - for _i in 0..10 { - println!("---------------------"); - match apply_om_transform(&mut inst.clone(), 64) { + fn test_ap_transform() { + println!("[*] Testing arithmetic partitioning transform..."); + let code: &[u8] = &[ + 0xbf, 0x8e, 0x00, 0x00, 0xc0, 0x41, 0xb9, 0x02, 0x00, 0x00, 0x00, 0xc6, 0x40, 0x38, + 0x01, 0x48, 0xb8, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x04, 0x10, 0x81, + 0xc2, 0x02, 0x10, 0x00, 0x00, 0x48, 0x81, 0xc6, 0x10, 0xff, 0xff, 0xff, 0x81, 0xd6, + 0x0c, 0x00, 0x88, 0xd6, 0x48, 0x81, 0xd6, 0x0c, 0x00, 0x88, 0x00, 0x14, 0xdc, 0x2c, + 0xa0, 0x48, 0x81, 0xee, 0xa0, 0x00, 0x00, 0x00, 0x2d, 0x20, 0x05, 0x93, 0x19, 0x1c, + 0x0c, 0x48, 0x1d, 0x33, 0x11, 0x22, 0x00, + ]; + let mut decoder = Decoder::new(64, code, DecoderOptions::NONE); + let mut inst = Instruction::default(); + while decoder.can_decode() { + decoder.decode_out(&mut inst); + println!("[>] {}", inst); + match apply_ap_transform(64, &mut inst.clone()) { Ok(result) => { for i in result { - formatted_inst.clear(); - formatter.format(&mut i.clone(), &mut formatted_inst); - println!("[+] {}", formatted_inst); + println!("{:016X}:\t{}", i.ip(), i); + } + } + Err(e) => { + println!("[-] {e}"); + break; + } + }; + } + } + + #[test] + fn test_li_transform() { + println!("[*] Testing logical inverse transform..."); + let code: &[u8] = &[ + 0x41, 0x81, 0xf2, 0x69, 0x6e, 0x65, 0x49, 0x34, 0x01, 0x80, 0x74, 0x24, 0x27, 0x01, + 0x24, 0x01, 0x25, 0x00, 0x00, 0x08, 0x00, 0x48, 0x81, 0x23, 0xff, 0x9f, 0xff, 0xff, + 0x81, 0x20, 0x1f, 0x00, 0xfe, 0xff, 0x80, 0x64, 0xf8, 0x38, 0xfb, 0x81, 0xca, 0x00, + 0x03, 0x00, 0x00, 0x81, 0xca, 0x00, 0x00, 0x00, 0x03, 0x48, 0x81, 0x0b, 0x00, 0x60, + 0x00, 0x00, 0x80, 0x4c, 0xc8, 0x38, 0x20, 0xd1, 0xeb, 0xc1, 0xeb, 0x00, 0x48, 0xc1, + 0xeb, 0x10, 0xc0, 0xe0, 0x05, 0xc1, 0x21, 0x20, 0xc1, 0xc8, 0x0a, 0xc0, 0x0a, 0x14, + 0x48, 0xc1, 0xc1, 0x31, 0x40, 0xc0, 0xc6, 0x69, + ]; + let mut decoder = Decoder::new(64, code, DecoderOptions::NONE); + let mut inst = Instruction::default(); + while decoder.can_decode() { + decoder.decode_out(&mut inst); + println!("[>] {}", inst); + match apply_li_transform(64, &mut inst.clone()) { + Ok(result) => { + for i in result { + println!("{:016X}:\t{}", i.ip(), i); + } + } + Err(e) => { + println!("[-] {e}"); + break; + } + }; + } + } + + #[test] + fn test_itr_transform() { + println!("[*] Testing immediate to register transform..."); + let code: &[u8] = &[ + 0x41, 0x81, 0xf2, 0x69, 0x6e, 0x65, 0x49, 0x34, 0x01, 0x80, 0x74, 0x24, 0x27, 0x01, + 0x24, 0x01, 0x25, 0x00, 0x00, 0x08, 0x00, 0x48, 0x81, 0x23, 0xff, 0x9f, 0xff, 0xff, + 0x81, 0x20, 0x1f, 0x00, 0xfe, 0xff, 0x80, 0x64, 0xf8, 0x38, 0xfb, 0x81, 0xca, 0x00, + 0x03, 0x00, 0x00, 0x81, 0xca, 0x00, 0x00, 0x00, 0x03, 0x48, 0x81, 0x0b, 0x00, 0x60, + 0x00, 0x00, 0x80, 0x4c, 0xc8, 0x38, 0x20, 0xd1, 0xeb, 0xc1, 0xeb, 0x00, 0x48, 0xc1, + 0xeb, 0x10, 0xc0, 0xe0, 0x05, 0xc1, 0x21, 0x20, 0xc1, 0xc8, 0x0a, 0xc0, 0x0a, 0x14, + 0x48, 0xc1, 0xc1, 0x31, 0x40, 0xc0, 0xc6, 0x69, + ]; + let mut decoder = Decoder::new(64, code, DecoderOptions::NONE); + let mut inst = Instruction::default(); + while decoder.can_decode() { + decoder.decode_out(&mut inst); + println!("[>] {}", inst); + match apply_itr_transform(64, &mut inst.clone()) { + Ok(result) => { + for i in result { + println!("{:016X}:\t{}", i.ip(), i); + } + } + Err(e) => { + println!("[-] {e}"); + break; + } + }; + } + } + + #[test] + fn test_om_transform() { + println!("[*] testing offset mutation..."); + let code: &[u8] = &[ + 0x48, 0x8d, 0x09, 0x89, 0x38, 0x48, 0x8b, 0x4b, 0x28, 0x48, 0x89, 0x74, 0x24, 0x10, + 0xc7, 0x45, 0x40, 0x01, 0x00, 0x00, 0x00, 0x48, 0x89, 0x54, 0xc8, 0x28, 0x48, 0x8d, + 0x0c, 0xc9, 0x41, 0x0f, 0xb6, 0xb4, 0x80, 0x53, 0xb3, 0x0f, 0x00, 0x48, 0x81, 0x23, + 0xff, 0x9f, 0xff, 0xff, 0x48, 0x0f, 0xba, 0x2b, 0x0e, 0x48, 0x8b, 0x04, 0x25, 0x88, + 0x77, 0x66, 0x55, + ]; + let mut decoder = Decoder::new(64, code, DecoderOptions::NONE); + let mut inst = Instruction::default(); + while decoder.can_decode() { + decoder.decode_out(&mut inst); + println!("[>] {}", inst); + match apply_om_transform(64, &mut inst.clone()) { + Ok(result) => { + for i in result { + println!("{:016X}:\t{}", inst.ip(), i); } } Err(e) => { @@ -52,113 +127,29 @@ mod tests { #[test] fn test_rs_transform() { println!("[*] Testing register swap..."); - let inst = Instruction::with2(Code::Mov_rm64_r64, Register::RAX, Register::RBX) - .expect("Instruction creation failed"); - let mut formatter = NasmFormatter::new(); - let mut formatted_inst = String::new(); - formatter.format(&inst, &mut formatted_inst); - println!("[*] instruction: {}", formatted_inst); - // assert_eq!(formatted_inst, "mov eax,bl"); - for _i in 0..10 { - println!("---------------------"); - match apply_rs_transform(&mut inst.clone(), 64) { - Ok(res) => { - for i in res { - formatted_inst.clear(); - formatter.format(&mut i.clone(), &mut formatted_inst); - println!("[+] {}", formatted_inst); + let code: &[u8] = &[ + 0x34, 0x01, 0x66, 0x83, 0xe0, 0x31, 0x83, 0xc0, 0x69, 0x49, 0x81, 0xf2, 0x69, 0x6e, + 0x65, 0x49, 0x30, 0x44, 0x24, 0x27, 0x29, 0x5c, 0x78, 0x2a, 0x09, 0xca, 0x87, 0xd6, + ]; + let mut decoder = Decoder::new(64, code, DecoderOptions::NONE); + let mut inst = Instruction::default(); + while decoder.can_decode() { + decoder.decode_out(&mut inst); + println!("[>] {}", inst); + match apply_rs_transform(64, &mut inst.clone()) { + Ok(result) => { + for i in result { + println!("{:016X}:\t{}", inst.ip(), i); } } Err(e) => { println!("[-] {e}"); break; } - } + }; } } - #[test] - fn test_itr_transform() { - println!("[*] Testing immediate to register transform..."); - let inst = Instruction::with2(Code::Mov_r32_imm32, Register::EAX, 0x6931) - .expect("Instruction creation failed"); - let mut formatter = NasmFormatter::new(); - let mut formatted_inst = String::new(); - formatter.format(&inst, &mut formatted_inst); - println!("[*] instruction: {}", formatted_inst); - // assert_eq!(formatted_inst, "mov eax,ebx"); - for _i in 0..10 { - println!("---------------------"); - match apply_itr_transform(&mut inst.clone(), 32) { - Ok(res) => { - for i in res { - formatted_inst.clear(); - formatter.format(&mut i.clone(), &mut formatted_inst); - println!("[+] {}", formatted_inst); - } - } - Err(e) => { - println!("[-] {e}"); - break; - } - } - } - } - - #[test] - fn test_ap_transform() { - println!("[*] Testing arithmetic partitioning transform..."); - let inst = Instruction::with2(Code::Mov_rm32_imm32, Register::EAX, 0xDEADBEEF as u32) - .expect("Instruction creation failed"); - let mut formatter = NasmFormatter::new(); - let mut formatted_inst = String::new(); - formatter.format(&inst, &mut formatted_inst); - println!("[*] instruction: {}", formatted_inst); - // assert_eq!(formatted_inst, "mov eax,ebx"); - for _i in 0..10 { - println!("---------------------"); - match apply_ap_transform(&mut inst.clone()) { - Ok(res) => { - for i in res { - formatted_inst.clear(); - formatter.format(&mut i.clone(), &mut formatted_inst); - println!("[+] {}", formatted_inst); - } - } - Err(e) => { - println!("[-] {e}"); - break; - } - } - } - } - - #[test] - fn test_li_transform() { - println!("[*] Testing logical inverse transform..."); - let inst = Instruction::with2(Code::Ror_rm32_imm8, Register::EAX, 0x31) - .expect("Instruction creation failed"); - let mut formatter = NasmFormatter::new(); - let mut formatted_inst = String::new(); - formatter.format(&inst, &mut formatted_inst); - println!("[*] instruction: {}", formatted_inst); - // assert_eq!(formatted_inst, "mov eax,ebx"); - println!("---------------------"); - match apply_li_transform(&mut inst.clone()) { - Ok(res) => { - for i in res { - formatted_inst.clear(); - formatter.format(&mut i.clone(), &mut formatted_inst); - println!("[+] {}", formatted_inst); - } - } - Err(e) => { - println!("[-] {e}"); - } - } - println!("---------------------"); - } - #[test] fn test_get_random_gp_register() { match get_random_gp_register(false, 4, None) { diff --git a/src/x86_64/transforms.rs b/src/x86_64/transforms.rs index 1aee703..bb36779 100644 --- a/src/x86_64/transforms.rs +++ b/src/x86_64/transforms.rs @@ -7,279 +7,104 @@ use thiserror::Error; pub enum TransformError { #[error("This transform not possible for given instruction.")] TransformNotPossible, + #[error("Unexpected memory size given.")] + UnexpectedMemorySize, #[error("Unexpected register size given.")] UnexpectedRegisterSize, #[error("Unexpected immediate operand size encountered.")] UnexpectedImmediateSize, #[error("Invalid processor mode given. (16/32/64 accepted)")] InvalidProcessorMode, - // #[error("Register collection failed.")] - // RegisterCollectFail, #[error("No GP register found with given parameters.")] RegisterNotFound, + #[error("Invalid instruction template.")] + InvalidTemplate, #[error("IcedError: {0}")] IcedError(#[from] IcedError), } -// Memory Obfuscation Transforms - -/// Applies offset mutation to given instruction. -pub fn apply_om_transform( - inst: &mut Instruction, - mode: u32, -) -> Result, TransformError> { - // First check the operand types. - let base_reg = inst.memory_base(); - if !inst - .op_kinds() - .collect::>() - .contains(&OpKind::Memory) - || base_reg == Register::None - || base_reg.is_segment_register() - || base_reg.is_vector_register() - { - return Err(TransformError::TransformNotPossible); - } - - if base_reg == Register::None { - let mut info_factory = InstructionInfoFactory::new(); - let info = info_factory.info(&inst); - let rand_reg = - get_random_gp_register(mode == 64, (mode / 8) as usize, Some(info.used_registers()))?; - let rand_val = get_random_register_value(rand_reg); - let (reg_save_pre, reg_save_suf) = get_register_save_seq(rand_reg)?; - inst.set_memory_base(rand_reg); - let mut new_disply: i64 = inst.memory_displacement64().abs_diff(rand_val) as i64; - if rand_val > inst.memory_displacement64() { - new_disply = -new_disply; - } - let mov = match mode { - 32 => { - inst.set_memory_displacement32(new_disply as u32); - Instruction::with2(Code::Mov_rm32_imm32, rand_reg, rand_val)? - } - 64 => { - inst.set_memory_displacement64(new_disply as u64); - Instruction::with2(Code::Mov_r64_imm64, rand_reg, rand_val)? - } - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - Ok(Vec::from([reg_save_pre, mov, *inst, reg_save_suf])) - } else { - let rnd_reg_val = get_random_register_value(base_reg); - let coin_flip: bool = rand::thread_rng().gen(); - - let (c1, c2) = match coin_flip { - true => (get_add_code_with(base_reg)?, get_sub_code_with(base_reg)?), - false => (get_sub_code_with(base_reg)?, get_add_code_with(base_reg)?), - }; - let pre_inst = Instruction::with2(c1, base_reg, rnd_reg_val)?; - let post_inst = Instruction::with2(c2, base_reg, rnd_reg_val)?; - let new_disply = inst.memory_displacement64().abs_diff(rnd_reg_val); - let mut new_disply_signed = new_disply as i64; // This is not right!!! - if coin_flip { - new_disply_signed = -new_disply_signed; - } - match mode { - 16 | 32 => inst.set_memory_displacement32(new_disply_signed as u32), - 64 => inst.set_memory_displacement64(new_disply_signed as u64), - _ => return Err(TransformError::InvalidProcessorMode), - } - Ok(Vec::from([pre_inst, inst.clone(), post_inst])) - } -} - -// Register Obfuscation Transforms -/// Applies register swapping transform to given instruction. -pub fn apply_rs_transform( - inst: &mut Instruction, - mode: u32, -) -> Result, TransformError> { - if !inst - .op_kinds() - .collect::>() - .contains(&OpKind::Register) - { - return Err(TransformError::TransformNotPossible); - } - let mut info_factory = InstructionInfoFactory::new(); - let info = info_factory.info(&inst); - - let mut reg_idxs = Vec::new(); - let mut used_regs = Vec::new(); - for o in 0..inst.op_count() { - if inst.op_kind(o) == OpKind::Register { - reg_idxs.push(o) - } - } - for i in reg_idxs { - if inst.op_register(i).is_gpr() { - used_regs.push((i, inst.op_register(i))); - } - } - if used_regs.len() == 0 { - return Err(TransformError::TransformNotPossible); - } - - let (reg_index, swap_reg) = *used_regs.choose(&mut rand::thread_rng()).unwrap(); - let rand_reg = - get_random_gp_register(mode == 64, swap_reg.size(), Some(info.used_registers()))?; - let xchg = match swap_reg.size() { - 1 => Instruction::with2(Code::Xchg_rm8_r8, swap_reg, rand_reg)?, - 2 => Instruction::with2(Code::Xchg_rm16_r16, swap_reg, rand_reg)?, - 4 => Instruction::with2(Code::Xchg_rm32_r32, swap_reg, rand_reg)?, - 8 => Instruction::with2(Code::Xchg_rm64_r64, swap_reg, rand_reg)?, - _ => return Err(TransformError::TransformNotPossible), - }; - inst.set_op_register(reg_index, rand_reg); - Ok(Vec::from([xchg, inst.clone(), xchg])) -} - // Immidiate Obfuscation Transforms -/// Applies immidiate-to-register transform to given instruction. -pub fn apply_itr_transform( - inst: &mut Instruction, - mode: u32, -) -> Result, TransformError> { - let idxs = match get_immediate_indexes(inst) { - Some(i) => i, - None => return Err(TransformError::TransformNotPossible), - }; - let mut info_factory = InstructionInfoFactory::new(); - let info = info_factory.info(&inst); - - let rand_reg = - get_random_gp_register(mode == 64, (mode / 8) as usize, Some(info.used_registers()))?; - let (reg_save_pre, reg_save_post) = get_register_save_seq(rand_reg)?; - - let mut mov = match rand_reg.size() { - 4 => Instruction::with2( - Code::Mov_rm64_imm32, - rand_reg, - inst.immediate(*idxs.first().unwrap()), - )?, - 8 => Instruction::with2( - Code::Mov_r64_imm64, - rand_reg, - inst.immediate(*idxs.first().unwrap()), - )?, - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - // Obfuscate mov... - let obs_mov = apply_ap_transform(&mut mov)?; - inst.set_op_kind(*idxs.first().unwrap(), OpKind::Register); - inst.set_op_register(*idxs.first().unwrap(), rand_reg); - let mut result = [Vec::from([reg_save_pre]), obs_mov].concat(); - result.push(*inst); - result.push(reg_save_post); - Ok(result) -} - /// Applies arithmetic partitioning transform to given instruction. -pub fn apply_ap_transform(inst: &mut Instruction) -> Result, TransformError> { - if !is_ap_safe_instruction(inst) - || inst.op0_kind() != OpKind::Register - || !is_immediate_operand(inst.op1_kind()) - || inst.op_count() != 2 - || (inst.mnemonic() == Mnemonic::Mov && inst.op1_kind() == OpKind::Immediate64) - { +pub fn apply_ap_transform( + bitness: u32, + inst: &mut Instruction, +) -> Result, TransformError> { + if !is_ap_compatible(inst) || !is_immediate_operand(inst.op1_kind()) || inst.op_count() != 2 { return Err(TransformError::TransformNotPossible); } // We are looking for MOV (=) ADD/ADC (+) SUB/SBB (-) - // let mnemonic = inst.mnemonic(); - let reg = inst.op0_register(); + let rip = inst.ip(); let imm = inst.immediate(1); let rand_imm_val = randomize_immediate_value(imm); let imm_delta: u64 = rand_imm_val.abs_diff(imm); - - match inst.op1_kind() { - OpKind::Immediate8 => inst.set_immediate8(rand_imm_val as u8), - OpKind::Immediate8to16 => inst.set_immediate8to16(rand_imm_val as i16), - OpKind::Immediate8to32 => inst.set_immediate8to32(rand_imm_val as i32), - OpKind::Immediate8to64 => inst.set_immediate8to64(rand_imm_val as i64), - OpKind::Immediate8_2nd => inst.set_immediate8_2nd(rand_imm_val as u8), - OpKind::Immediate16 => inst.set_immediate16(rand_imm_val as u16), - OpKind::Immediate32 => inst.set_immediate32(rand_imm_val as u32), - OpKind::Immediate32to64 => inst.set_immediate32(rand_imm_val as u32), - OpKind::Immediate64 => inst.set_immediate64(rand_imm_val), - _ => return Err(TransformError::UnexpectedImmediateSize), - }; - - let mut result = Vec::new(); - if inst.mnemonic() == Mnemonic::Mov { - result.push(*inst); - } - // we always need to make the value adjustment before the original instruction for preserving - // the cflags... - if imm > rand_imm_val { - result.push(Instruction::with2(get_add_code_with(reg)?, reg, imm_delta)?); + let mut fix_inst = inst.clone(); + if inst.mnemonic() == Mnemonic::Mov && inst.op1_kind() == OpKind::Immediate64 { + set_op1_immediate(inst, !imm)?; + fix_inst = Instruction::with1(get_code_with_size(Mnemonic::Not, 8)?, inst.op0_register())?; } else { - result.push(Instruction::with2(get_sub_code_with(reg)?, reg, imm_delta)?); - } - if inst.mnemonic() != Mnemonic::Mov { - result.push(*inst); + set_op1_immediate(inst, rand_imm_val)?; + if imm > rand_imm_val { + fix_inst.set_code(get_code_with_template(Mnemonic::Add, inst)?); + set_op1_immediate(&mut fix_inst, imm_delta)?; + } else { + fix_inst.set_code(get_code_with_template(Mnemonic::Sub, inst)?); + set_op1_immediate(&mut fix_inst, imm_delta)?; + } } - Ok(result) + if inst.mnemonic() == Mnemonic::Mov { + Ok(encode(bitness, Vec::from([*inst, fix_inst]), rip)?) + } else { + Ok(encode(bitness, Vec::from([fix_inst, *inst]), rip)?) + } } /// Applies logical inverse transform to given instruction. -pub fn apply_li_transform(inst: &mut Instruction) -> Result, TransformError> { - if !is_li_safe_instruction(inst) - || inst.op0_kind() != OpKind::Register - || !is_immediate_operand(inst.op1_kind()) - || inst.op_count() != 2 - { +pub fn apply_li_transform( + bitness: u32, + inst: &mut Instruction, +) -> Result, TransformError> { + if !is_li_compatible(inst) || !is_immediate_operand(inst.op1_kind()) || inst.op_count() != 2 { return Err(TransformError::TransformNotPossible); } // We are looking for XOR (^) AND (&) OR (|) SHR (>) SHL (<) ROR (>>) ROL (<<) + let rip = inst.ip(); let mnemonic = inst.mnemonic(); - let reg = inst.op0_register(); let mut imm = inst.immediate(1); - // let rand_imm_val = randomize_immediate_value(imm); + if imm == 0 { + // Unlikely but possible... + return Ok(Vec::from([Instruction::with(Code::Nopd)])); + } + let result = match mnemonic { Mnemonic::Xor => { - match reg.size() { - 1 => inst.set_immediate8(!(imm as u8)), - 2 => inst.set_immediate16(!(imm as u16)), - 4 => inst.set_immediate32(!(imm as u32)), - 8 => inst.set_immediate32(!(imm as u32)), - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - Vec::from([Instruction::with1(get_not_code_with(reg)?, reg)?, *inst]) + set_op1_immediate(inst, !imm)?; + let mut not = inst.clone(); + not.set_code(get_code_with_template(Mnemonic::Not, inst)?); + Vec::from([not, *inst]) } Mnemonic::And => { - let mut or = Instruction::with2(get_or_code_with(reg)?, reg, 0)?; - match reg.size() { - 1 => or.set_immediate8(!(imm as u8)), - 2 => or.set_immediate16(!(imm as u16)), - 4 => or.set_immediate32(!(imm as u32)), - 8 => or.set_immediate32(!(imm as u32)), - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - Vec::from([ - Instruction::with1(get_not_code_with(reg)?, reg)?, - or, - Instruction::with1(get_not_code_with(reg)?, reg)?, - ]) + let mut or = inst.clone(); + or.set_code(get_code_with_template(Mnemonic::Or, inst)?); + set_op1_immediate(&mut or, !imm)?; + let mut not = inst.clone(); + not.set_code(get_code_with_template(Mnemonic::Not, inst)?); + Vec::from([not, or, not]) } Mnemonic::Or => { - let mut or = Instruction::with2(get_and_code_with(reg)?, reg, 0)?; - match reg.size() { - 1 => or.set_immediate8(!(imm as u8)), - 2 => or.set_immediate16(!(imm as u16)), - 4 => or.set_immediate32(!(imm as u32)), - 8 => or.set_immediate32(!(imm as u32)), - _ => return Err(TransformError::UnexpectedRegisterSize), - }; - Vec::from([ - Instruction::with1(get_not_code_with(reg)?, reg)?, - or, - Instruction::with1(get_not_code_with(reg)?, reg)?, - ]) + let mut and = inst.clone(); + and.set_code(get_code_with_template(Mnemonic::And, inst)?); + set_op1_immediate(&mut and, !imm)?; + let mut not = inst.clone(); + not.set_code(get_code_with_template(Mnemonic::Not, inst)?); + Vec::from([not, and, not]) } Mnemonic::Shr | Mnemonic::Sar | Mnemonic::Shl | Mnemonic::Sal => { + if imm == 1 { + // Need to fix this case... + return Err(TransformError::TransformNotPossible); + } if imm.is_power_of_two() { let mut shift1 = inst.clone(); let mut shift2 = inst.clone(); @@ -295,15 +120,204 @@ pub fn apply_li_transform(inst: &mut Instruction) -> Result, Tr } } Mnemonic::Ror | Mnemonic::Rcr | Mnemonic::Rol | Mnemonic::Rcl => { - let reg_size = (reg.size() * 8) as u64; - imm = imm % reg_size; - let pow = rand::thread_rng().gen_range(1..((u8::MAX as u64 / reg_size) - 2) as u8) - 1; - inst.set_immediate8((reg_size * pow as u64 + imm) as u8); + let dst_op_size = match inst.op0_kind() { + OpKind::Memory => (inst.memory_size().element_size() * 8) as u64, + OpKind::Register => (inst.op0_register().size() * 8) as u64, + _ => return Err(TransformError::InvalidTemplate), + }; + imm = imm % dst_op_size; + let pow = rand::thread_rng().gen_range(2..(u8::MAX as u64 / dst_op_size) as u8); + inst.set_immediate8((dst_op_size * pow as u64 + imm) as u8); Vec::from([*inst]) } _ => return Err(TransformError::TransformNotPossible), }; - Ok(result) + Ok(encode(bitness, result, rip)?) +} + +/// Applies immidiate-to-register transform to given instruction. +pub fn apply_itr_transform( + bitness: u32, + inst: &mut Instruction, +) -> Result, TransformError> { + if inst.is_stack_instruction() { + return Err(TransformError::TransformNotPossible); + } + let idxs = match get_immediate_indexes(inst) { + Some(i) => i, + None => return Err(TransformError::TransformNotPossible), + }; + + let rip = inst.ip(); + let mut info_factory = InstructionInfoFactory::new(); + let info = info_factory.info(&inst); + let rand_reg = get_random_gp_register( + bitness == 64, + (bitness / 8) as usize, + Some(info.used_registers()), + )?; + let (reg_save_pre, reg_save_post) = get_register_save_seq(rand_reg)?; + + let mut mov = match rand_reg.size() { + 4 => Instruction::with2( + Code::Mov_rm32_imm32, + rand_reg, + inst.immediate(*idxs.first().unwrap()), + )?, + 8 => Instruction::with2( + Code::Mov_r64_imm64, + rand_reg, + inst.immediate(*idxs.first().unwrap()), + )?, + _ => return Err(TransformError::UnexpectedRegisterSize), + }; + // Obfuscate mov... + let obs_mov = apply_ap_transform(bitness, &mut mov)?; + inst.set_op_kind(*idxs.first().unwrap(), OpKind::Register); + inst.set_op_register(*idxs.first().unwrap(), rand_reg); + let mut result = [Vec::from([reg_save_pre]), obs_mov].concat(); + result.push(*inst); + result.push(reg_save_post); + Ok(encode(bitness, result, rip)?) +} +// Memory Obfuscation Transforms + +/// Applies offset mutation to given instruction. +/// Note: This transform may clobber the CFLAGS! +/// avoid using with CF altering instructions. +pub fn apply_om_transform( + bitness: u32, + inst: &mut Instruction, +) -> Result, TransformError> { + // First check the operand types. + let base_reg = inst.memory_base(); + if !inst + .op_kinds() + .collect::>() + .contains(&OpKind::Memory) + || base_reg.is_segment_register() + || base_reg.is_vector_register() + { + return Err(TransformError::TransformNotPossible); + } + let rip = inst.ip(); + let mem_disp = inst.memory_displacement64(); + if base_reg == Register::None { + if inst.is_stack_instruction() { + return Err(TransformError::TransformNotPossible); + } + let mut ifac = InstructionInfoFactory::new(); + let info = ifac.info(inst); + let rand_reg = get_random_gp_register( + bitness == 64, + (bitness / 8) as usize, + Some(info.used_registers()), + )?; + let (reg_save_pre, reg_save_suf) = get_register_save_seq(rand_reg)?; + inst.set_memory_base(rand_reg); + inst.set_memory_displacement64(0); + inst.set_memory_displ_size(0); + + let mut mov = match bitness { + 16 => Instruction::with2(Code::Mov_rm16_imm16, rand_reg, mem_disp)?, + 32 => Instruction::with2(Code::Mov_rm32_imm32, rand_reg, mem_disp)?, + 64 => Instruction::with2(Code::Mov_r64_imm64, rand_reg, mem_disp)?, + _ => return Err(TransformError::UnexpectedRegisterSize), + }; + + let movs = apply_ap_transform(bitness, &mut mov)?; + let mut result = Vec::from([reg_save_pre]); + result = [result, movs].concat(); + result.push(*inst); + result.push(reg_save_suf); + Ok(encode(bitness, result, rip)?) + } else { + let rnd_reg_val = get_random_register_value(base_reg); + let coin_flip: bool = rand::thread_rng().gen(); + let (c1, c2) = match coin_flip { + true => ( + get_code_with_size(Mnemonic::Add, base_reg.size())?, + get_code_with_size(Mnemonic::Sub, base_reg.size())?, + ), + false => ( + get_code_with_size(Mnemonic::Sub, base_reg.size())?, + get_code_with_size(Mnemonic::Add, base_reg.size())?, + ), + }; + let pre_inst = Instruction::with2(c1, base_reg, rnd_reg_val)?; + let post_inst = Instruction::with2(c2, base_reg, rnd_reg_val)?; + let new_disply = mem_disp.abs_diff(rnd_reg_val); + let mut new_disply_signed = new_disply as i64; // This is not right!!! + if coin_flip { + new_disply_signed = -new_disply_signed; + } + inst.set_memory_displ_size(1); + inst.set_memory_displacement64(new_disply_signed as u64); + Ok(encode( + bitness, + Vec::from([pre_inst, inst.clone(), post_inst]), + rip, + )?) + } +} + +// Register Obfuscation Transforms +/// Applies register swapping transform to given instruction. +pub fn apply_rs_transform( + bitness: u32, + inst: &mut Instruction, +) -> Result, TransformError> { + if !inst + .op_kinds() + .collect::>() + .contains(&OpKind::Register) + { + return Err(TransformError::TransformNotPossible); + } + // We need to fix the code if it is spesific to any register + if is_using_static_register(inst) && is_immediate_operand(inst.op1_kind()) { + if let Ok(code) = get_code_with_template(inst.mnemonic(), inst) { + inst.set_code(code); + match inst.op0_register().size() { + 1 => inst.set_op1_kind(OpKind::Immediate8), + 2 => inst.set_op1_kind(OpKind::Immediate16), + 4 => inst.set_op1_kind(OpKind::Immediate32), + 8 => inst.set_op1_kind(OpKind::Immediate64), // This may actually fail + _ => return Err(TransformError::UnexpectedRegisterSize), + } + } else { + return Err(TransformError::TransformNotPossible); + } + } + let rip = inst.ip(); + let mut info_factory = InstructionInfoFactory::new(); + let info = info_factory.info(&inst); + + let mut reg_idxs = Vec::new(); + let mut used_regs = Vec::new(); + for o in 0..inst.op_count() { + if inst.op_kind(o) == OpKind::Register { + reg_idxs.push(o) + } + } + for i in reg_idxs { + if inst.op_register(i).is_gpr() { + used_regs.push((i, inst.op_register(i))); + } + } + + let (reg_index, swap_reg) = *used_regs.last().unwrap(); + let rand_reg = + get_random_gp_register(bitness == 64, swap_reg.size(), Some(info.used_registers()))?; + let xchg = match swap_reg.size() { + 1 => Instruction::with2(Code::Xchg_rm8_r8, swap_reg, rand_reg)?, + 2 => Instruction::with2(Code::Xchg_rm16_r16, swap_reg, rand_reg)?, + 4 => Instruction::with2(Code::Xchg_rm32_r32, swap_reg, rand_reg)?, + 8 => Instruction::with2(Code::Xchg_rm64_r64, swap_reg, rand_reg)?, + _ => return Err(TransformError::TransformNotPossible), + }; + inst.set_op_register(reg_index, rand_reg); + Ok(encode(bitness, Vec::from([xchg, inst.clone(), xchg]), rip)?) } // Others...