diff --git a/.gitignore b/.gitignore index ea8c4bf..251be22 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,15 @@ -/target +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ +test/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/Cargo.lock b/Cargo.lock index 7090dec..6fa29e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,26 +101,6 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" -[[package]] -name = "capstone" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1097e608594dad3bad608295567f757742b883606fe150faf7a9740b849730d8" -dependencies = [ - "capstone-sys", - "libc", -] - -[[package]] -name = "capstone-sys" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7f651d5ec4c2a2e6c508f2c8032655003cd728ec85663e9796616990e25b5a" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "cc" version = "1.0.83" @@ -228,14 +208,15 @@ name = "deoptimizer" version = "0.1.0" dependencies = [ "anyhow", - "capstone", "chrono", "clap", "colored", "ctrlc", "hexdump", + "iced-x86", "keystone", "log", + "rand", "thiserror", ] @@ -249,6 +230,17 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.4.1" @@ -294,6 +286,15 @@ dependencies = [ "cc", ] +[[package]] +name = "iced-x86" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd366a53278429c028367e0ba22a46cab6d565a57afb959f06e92c7a69e7828" +dependencies = [ + "lazy_static", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -380,6 +381,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.69" @@ -398,6 +405,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rustix" version = "0.38.21" @@ -460,6 +497,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.88" diff --git a/Cargo.toml b/Cargo.toml index 9b32e5a..e55b2fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,12 +7,18 @@ edition = "2021" [dependencies] anyhow = "1.0.75" -capstone = "0.11.0" 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" + +# [profile.release] +# rustc-link-lib = ["libkeystone.a"] +# rustc-link-search = ["/usr/lib/"] +# rustc-flags = "-L /lib/" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5bb1736 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2023 Ege Balcı + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..834c11c --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +FLAGS=RUSTFLAGS="-C target-feature=+crt-static" +NIGHTLY_FLAGS=RUSTFLAGS="-C target-feature=+crt-static -Zlocation-detail=none" +BUILD=cargo build --release +CROSS=cross build --release +default: + ${BUILD} +static: + ${FLAGS} ${BUILD} +nightly: + ${NIGHTLY_FLAGS} ${BUILD} +windows: + ${BUILD} --target x86_64-pc-windows-gnu + ${BUILD} --target i686-pc-windows-gnu + # ${BUILD} --target aarch64-pc-windows-msvc +linux: + ${FLAGS} ${BUILD} --target x86_64-unknown-linux-musl + ${FLAGS} ${CROSS} --target i686-unknown-linux-musl + ${FLAGS} ${CROSS} --target aarch64-unknown-linux-musl +darwin: + ${FLAGS} ${BUILD} --target x86_64-apple-darwin + # ${FLAGS} ${BUILD} --target aarch64-apple-darwin + + +all: linux windows darwin diff --git a/src/main.rs b/src/main.rs index a061faa..352b47a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ -use std::io::Write; - use log::{error, info, warn, LevelFilter}; +use std::io::Write; mod options; mod utils; @@ -20,22 +19,24 @@ fn main() { } }; let file = utils::read_file(opts.file.clone()).expect("failed reading target file"); - warn!("File size: {}", file.len()); - let out = x86::disassemble(&file, opts.mode, 0x401000).expect("disassembly failed!"); + info!("File size: {}", file.len()); + let mut deopt = x86::Deoptimizer::new(); + deopt.set_syntax(opts.syntax).expect("invalid syntax"); + let out = deopt + .disassemble(&file, opts.mode, 0x401000) + .expect("disassembly failed!"); println!("{}", out); - match x86::assemble(out.clone(), opts.mode, 0x401000) { + 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!"); - return; } 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); } diff --git a/src/options.rs b/src/options.rs index ebc7d68..d595218 100644 --- a/src/options.rs +++ b/src/options.rs @@ -6,8 +6,6 @@ use thiserror::Error; pub enum ArgParseError { #[error("Target file not found")] FileNotFound, - #[error("Invalid bit value provided")] - InvalidBitness, } /// QSocket toolkit options. @@ -16,13 +14,25 @@ pub enum ArgParseError { #[command(version = "1.0.0")] #[command(about = "x86 Deoptimizer.", long_about = None)] pub struct Options { - /// target x86 binary file. + /// target x86 binary file name. #[arg(long, short = 'f', default_value_t = String::new())] pub file: String, + /// assembler formatter syntax (nasm/masm/intel/gas). + #[arg(long, short = 's', 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: u8, + pub mode: u32, + + /// deoptimization frequency. + #[arg(long, short = 'F', default_value_t = 0.5)] + pub freq: f64, + + /// Immidiate instruction partitioning length. + #[arg(long, default_value_t = 1)] + pub ipl: u32, /// verbose output mode. #[arg(long, short = 'v')] diff --git a/src/x86.rs b/src/x86.rs deleted file mode 100644 index d7ee1bc..0000000 --- a/src/x86.rs +++ /dev/null @@ -1,165 +0,0 @@ -extern crate keystone; -use capstone::arch::x86::X86OperandType; -use capstone::arch::ArchOperand; -use capstone::prelude::*; -use capstone::{self, Arch, Capstone, Insn, InsnDetail, InsnGroupType, Mode}; -use keystone::{AsmResult, Keystone}; - -/// Return register names -pub fn get_reg_names(cs: &Capstone, regs: &[RegId]) -> Vec { - regs.iter().map(|&x| cs.reg_name(x).unwrap()).collect() -} - -/// Return instruction group names -pub fn get_group_names(cs: &Capstone, regs: &[InsnGroupId]) -> Vec { - regs.iter().map(|&x| cs.group_name(x).unwrap()).collect() -} - -fn is_call(detail: &InsnDetail) -> bool { - detail - .groups() - .iter() - .any(|group| group.0 as u32 == InsnGroupType::CS_GRP_CALL) -} - -fn is_jump(detail: &InsnDetail) -> bool { - detail - .groups() - .iter() - .any(|group| group.0 as u32 == InsnGroupType::CS_GRP_JUMP) -} - -fn is_relative_branch(detail: &InsnDetail) -> bool { - detail - .groups() - .iter() - .any(|group| group.0 as u32 == InsnGroupType::CS_GRP_BRANCH_RELATIVE) -} - -fn is_imm(op: &ArchOperand) -> Option { - if let ArchOperand::X86Operand(op) = op { - if let X86OperandType::Imm(imm) = op.op_type { - return Some(imm as u64); - } - } - None -} - -fn is_known_address(ins: &[Insn], addr: u64) -> bool { - for i in ins.as_ref() { - if i.address() == addr { - return true; - } - } - false -} - -// Option<(offset, address, size)> -fn is_ip_offset(insn: &Insn, op: &ArchOperand) -> Option<(i64, u64, u64)> { - if let ArchOperand::X86Operand(op) = op { - if let X86OperandType::Mem(op) = op.op_type { - use capstone::arch::x86::X86Reg; - let reg = op.base().0 as u32; - let size = if reg == X86Reg::X86_REG_RIP { - 8 - } else if reg == X86Reg::X86_REG_EIP { - 4 - } else { - return None; - }; - let offset = op.disp(); - let address = (insn.address() + insn.bytes().len() as u64).wrapping_add(offset as u64); - return Some((offset, address, size)); - } - } - None -} - -pub fn disassemble(code: &[u8], mode: u8, start_addr: u64) -> Result { - let m = match mode { - 16 => arch::x86::ArchMode::Mode16, - 32 => arch::x86::ArchMode::Mode32, - 64 => arch::x86::ArchMode::Mode64, - _ => return Err(capstone::Error::InvalidMode), - }; - - let cs = Capstone::new() - .x86() - .mode(m) - .syntax(arch::x86::ArchSyntax::Intel) - .detail(true) - .build()?; - - let ins = cs.disasm_all(code, start_addr)?; - println!("Found {} instructions", ins.len()); - println!( - "Last instruction address: {}", - ins.last().unwrap().to_string().split(":").next().unwrap() - ); - let mut disassembly = String::new(); - // let end_addr = u64::from_str_radix( - // ins.last() - // .unwrap() - // .to_string() - // .split(":") - // .next() - // .unwrap() - // .strip_prefix("0x") - // .unwrap(), - // 16, - // ) - // .unwrap(); - - for i in ins.as_ref() { - // println!("loc_{}", i.to_string().strip_prefix("0x").unwrap()); - let detail: InsnDetail = cs.insn_detail(&i)?; - let arch_detail: ArchDetail = detail.arch_detail(); - let ops = arch_detail.operands(); - // - // let output: &[(&str, String)] = &[ - // ("insn id:", format!("{:?}", i.id().0)), - // ("bytes:", format!("{:?}", i.bytes())), - // ("read regs:", get_reg_names(&cs, detail.regs_read())), - // ("write regs:", get_reg_names(&cs, detail.regs_write())), - // ("insn groups:", get_group_names(&cs, detail.groups())), - // ]; - // - // for &(ref name, ref message) in output.iter() { - // println!("{:4}{:12} {}", "", name, message); - // } - // - // println!("{:4}operands: {}", "", ops.len()); - // for op in ops { - // println!("{:8}{:?}", "", op); - // } - // if get_group_names(&cs, defail.groups()).contains("branch_relative") - - if is_call(&detail) || is_jump(&detail) || is_relative_branch(&detail) { - if let Some(imm) = is_imm(ops.first().unwrap()) { - if is_known_address(&ins, imm) { - disassembly += - &format!("{}\n", i.to_string().replace("0x", "loc_").to_string()); - continue; - } else { - println!("[!] out of bound branch ==> {}", i.to_string()); - } - } - } - - disassembly += - &(format!("loc_{}\n", i.to_string().strip_prefix("0x").unwrap()).to_string()); - } - Ok(disassembly) -} - -pub fn assemble(code: String, mode: u8, 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)?; - engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_INTEL)?; - engine.asm(code, addr) -} diff --git a/src/x86/deoptimizer.rs b/src/x86/deoptimizer.rs new file mode 100644 index 0000000..bd49a7f --- /dev/null +++ b/src/x86/deoptimizer.rs @@ -0,0 +1,279 @@ +extern crate keystone; +// use iced_x86::*; +use iced_x86::{ + Code, ConditionCode, Decoder, DecoderOptions, Formatter, GasFormatter, Instruction, + InstructionInfoFactory, IntelFormatter, MasmFormatter, NasmFormatter, OpKind, Register, + RflagsBits, +}; +use keystone::{AsmResult, Keystone}; +use log::{error, info, warn, LevelFilter}; +use rand::seq::SliceRandom; +use thiserror::Error; + +use crate::x86::{ + apply_ap_transform, apply_itr_transform, apply_li_transform, apply_om_transform, + apply_otr_transform, apply_rs_transform, +}; + +#[derive(Error, Debug)] +pub enum DeoptimizerError { + #[error("Invalid formatter syntax.")] + InvalidSyntax, + #[error("Instruction with unexpected operand count.")] + UnexpectedOperandCount, + #[error("All available instruction transform gadgets failed.")] + AllTransformsFailed, +} + +enum AssemblySyntax { + Keystone, + Nasm, + Masm, + Intel, + Gas, +} + +pub struct Deoptimizer { + /// Immidiate partitioning length. + pub ipl: u32, + /// Deoptimization frequency. + pub freq: f64, + /// Is the given code analyzed. + pub is_analyzed: bool, + disassembly: Option, + syntax: AssemblySyntax, + known_addr_table: Option>, + known_branch_targets: Option>, + cfe_addr_table: Option>, +} + +impl Deoptimizer { + pub fn new() -> Self { + Self { + ipl: 1, + freq: 0.5, + is_analyzed: false, + disassembly: None, + syntax: AssemblySyntax::Nasm, + known_addr_table: None, + known_branch_targets: None, + cfe_addr_table: None, + } + } + + pub fn set_syntax(&mut self, syntax: String) -> Result<(), DeoptimizerError> { + match syntax.to_lowercase().as_str() { + "keystone" => self.syntax = AssemblySyntax::Keystone, + "nasm" => self.syntax = AssemblySyntax::Nasm, + "masm" => self.syntax = AssemblySyntax::Masm, + "intel" => self.syntax = AssemblySyntax::Intel, + "gas" => self.syntax = AssemblySyntax::Gas, + _ => return Err(DeoptimizerError::InvalidSyntax), + } + Ok(()) + } + + fn analyze(&mut self, code: &[u8], mode: u32, start_addr: u64) { + let mut decoder = Decoder::with_ip(mode, code, start_addr, DecoderOptions::NONE); + let mut instruction = Instruction::default(); + let mut known_addr_table = Vec::new(); + let mut known_branch_targets = Vec::new(); + let mut cfe_addr_table = Vec::new(); + while decoder.can_decode() { + decoder.decode_out(&mut instruction); + // Push to known address table + known_addr_table.push(instruction.ip()); + let nbt = instruction.near_branch_target(); + if nbt != 0 { + known_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 + { + cfe_addr_table.push(instruction.ip()) + } + } + 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 + } + + pub fn format_instruction(&self, inst: &Instruction) -> String { + let mut result = String::new(); + match self.syntax { + AssemblySyntax::Keystone => { + let mut formatter = IntelFormatter::new(); + formatter.options_mut().set_uppercase_keywords(false); + formatter + .options_mut() + .set_memory_size_options(iced_x86::MemorySizeOptions::Always); + formatter.options_mut().set_hex_prefix("0x"); + formatter.options_mut().set_hex_suffix(""); + formatter.format(inst, &mut result); + } + AssemblySyntax::Nasm => { + let mut formatter = NasmFormatter::new(); + formatter.format(inst, &mut result); + } + AssemblySyntax::Masm => { + let mut formatter = MasmFormatter::new(); + formatter.format(inst, &mut result); + } + AssemblySyntax::Intel => { + let mut formatter = IntelFormatter::new(); + formatter.format(inst, &mut result); + } + AssemblySyntax::Gas => { + let mut formatter = GasFormatter::new(); + formatter.format(inst, &mut result); + } + }; + 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); + 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); + while decoder.can_decode() { + decoder.decode_out(&mut instruction); + // let offsets = decoder.get_constant_offsets(&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) + ); + continue; + } + + let temp = self.format_instruction(&instruction); + let nbt = instruction.near_branch_target(); + if nbt != 0 { + if self.is_known_address(nbt) { + result += &format!( + "loc_{:016X}: {} {}\n", + instruction.ip(), + temp.split(' ').next().unwrap(), + &format!("loc_{:016X}", nbt) + ); + continue; + } else { + warn!("Misaligned instruction detected at {}", instruction.ip()) + } + } + result += &format!("loc_{:016X}: {}\n", instruction.ip(), temp); + } + self.disassembly = Some(result.clone()); + Ok(result) + } + + pub fn apply_transform( + inst: &Instruction, + mode: u32, + ) -> Result, DeoptimizerError> { + match inst.op_count() { + 0 => todo!("Perform call proxy..."), + 1 => todo!("Handle blacklisted operands. (only 3 exists)"), + 2..=5 => { + // Priority is important, start with immidate obfuscation + if let Ok(t) = apply_ap_transform(&mut inst.clone(), mode) { + return Ok(t); + } + if let Ok(t) = apply_li_transform(&mut inst.clone(), mode) { + return Ok(t); + } + if let Ok(t) = apply_itr_transform(&mut inst.clone(), mode) { + return Ok(t); + } + // second target memory obfuscation + if let Ok(t) = apply_om_transform(&mut inst.clone(), mode) { + return Ok(t); + } + if let Ok(t) = apply_otr_transform(&mut inst.clone(), mode) { + return Ok(t); + } + // Now swap registers + if let Ok(t) = apply_rs_transform(&mut inst.clone(), mode) { + return Ok(t); + } + } + _ => return Err(DeoptimizerError::UnexpectedOperandCount), // WTF? this shouldn't happen. + } + 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)?; + + match self.syntax { + AssemblySyntax::Nasm | AssemblySyntax::Keystone => { + engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_INTEL)? + } + AssemblySyntax::Masm => { + engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_MASM)? + } + 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) + } +} + +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/mod.rs b/src/x86/mod.rs new file mode 100644 index 0000000..c376698 --- /dev/null +++ b/src/x86/mod.rs @@ -0,0 +1,4 @@ +mod deoptimizer; +mod transforms; +pub use deoptimizer::*; +pub use transforms::*; diff --git a/src/x86/transforms.rs b/src/x86/transforms.rs new file mode 100644 index 0000000..8ba60bc --- /dev/null +++ b/src/x86/transforms.rs @@ -0,0 +1,165 @@ +use iced_x86::{ + Code, ConditionCode, Decoder, DecoderOptions, Formatter, GasFormatter, Instruction, + InstructionInfoFactory, IntelFormatter, MasmFormatter, NasmFormatter, OpKind, Register, + RflagsBits, +}; +use rand::Rng; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TransformError { + #[error("This transform not possible for given instruction.")] + TransformNotPossible, + #[error("Unexpected register size given.")] + UnexpectedRegisterSize, + #[error("Invalid processor mode given. (16/32/64 accepted)")] + InvalidProcessorMode, +} + +pub fn get_random_register_value(reg: Register) -> usize { + let mut rng = rand::thread_rng(); + rng.gen_range(0..(reg.size() * 8)) +} + +fn get_add_code_with(size: usize) -> Result { + let c = 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::UnexpectedRegisterSize), + }; + Ok(c) +} + +fn get_sub_code_with(size: usize) -> Result { + let c = 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::UnexpectedRegisterSize), + }; + Ok(c) +} + +// 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 index_reg = inst.memory_index(); + if !inst + .op_kinds() + .collect::>() + .contains(&OpKind::Memory) + || index_reg == Register::None + || index_reg.is_segment_register() + { + return Err(TransformError::TransformNotPossible); + } + let rnd_reg_val = get_random_register_value(index_reg); + let coin_flip: bool = rand::thread_rng().gen(); + let code = match coin_flip { + true => get_add_code_with(index_reg.size())?, + false => get_sub_code_with(index_reg.size())?, + }; + let mut pre_inst = Instruction::with(code); + + match index_reg.size() { + 1 => pre_inst.set_immediate8(rnd_reg_val as u8), + 2 => pre_inst.set_immediate16(rnd_reg_val as u16), + 4 => pre_inst.set_immediate32(rnd_reg_val as u32), + 8 => pre_inst.set_immediate64(rnd_reg_val as u64), + _ => return Err(TransformError::UnexpectedRegisterSize), + }; + + let new_disply = match coin_flip { + true => inst.memory_displacement64() - rnd_reg_val as u64, + false => inst.memory_displacement64() - rnd_reg_val as u64, + }; + + let mut post_inst = match coin_flip { + true => Instruction::with(get_sub_code_with(index_reg.size())?), + false => Instruction::with(get_add_code_with(index_reg.size())?), + }; + + match mode { + 16 => { + post_inst.set_immediate32(rnd_reg_val as u32); + inst.set_memory_displacement64(new_disply); + } + 32 => { + post_inst.set_immediate32(rnd_reg_val as u32); + inst.set_memory_displacement32(new_disply as u32); + } + 64 => { + post_inst.set_immediate64(rnd_reg_val as u64); + inst.set_memory_displacement64(new_disply); + } + _ => return Err(TransformError::InvalidProcessorMode), + } + + Ok(Vec::from([pre_inst, inst.clone(), post_inst])) +} +/// Applies offset-to-register transform to given instruction. +pub fn apply_otr_transform( + inst: &mut Instruction, + mode: u32, +) -> Result, TransformError> { + let index_reg = inst.memory_index(); + if !inst + .op_kinds() + .collect::>() + .contains(&OpKind::Memory) + || index_reg != Register::None + { + return Err(TransformError::TransformNotPossible); + } + + todo!("...") +} + +// Register Obfuscation Transforms + +/// Applies register swapping transform to given instruction. +pub fn apply_rs_transform( + inst: &mut Instruction, + mode: u32, +) -> Result, TransformError> { + todo!("...") +} + +// Immidiate Obfuscation Transforms + +/// Applies immidiate-to-register transform to given instruction. +pub fn apply_itr_transform( + inst: &mut Instruction, + mode: u32, +) -> Result, TransformError> { + todo!("...") +} +/// Applies arithmetic partitioning transform to given instruction. +pub fn apply_ap_transform( + inst: &mut Instruction, + mode: u32, +) -> Result, TransformError> { + todo!("...") +} +/// Applies logical inverse transform to given instruction. +pub fn apply_li_transform( + inst: &mut Instruction, + mode: u32, +) -> Result, TransformError> { + todo!("...") +} + +// Others... + +/// Applies call proxy instruction transform. +pub fn apply_cp_transform(inst_addr: u64, mode: u32) -> Result<(), TransformError> { + todo!("...") +}