First attempt of deoptimize with source.

This commit is contained in:
Ege Balcı
2023-11-30 18:05:32 +01:00
parent 4d6c5da4a2
commit 008a502cb0
8 changed files with 872 additions and 673 deletions
Generated
+2 -26
View File
@@ -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",
-2
View File
@@ -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"
+15 -14
View File
@@ -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!");
}
+12 -4
View File
@@ -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,
+258 -167
View File
@@ -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<u8>,
start_addr: u64,
code: Vec<Instruction>,
addr_map: HashMap<u64, Instruction>,
known_addr_table: Vec<u64>,
near_branch_targets: Vec<u64>,
far_branch_targets: Vec<u64>,
cfe_addr_table: Vec<u64>,
}
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<u64> {
self.cfe_addr_table.choose(&mut rand::thread_rng()).copied()
}
pub fn is_affecting_cf(&self, inst: &Instruction) -> Result<bool, DeoptimizerError> {
// 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<HashMap<u64, Instruction>>,
disassembly: Option<String>,
/// Allow processing of invalid instructions.
pub allow_invalid: bool,
/// Disassembler syntax.
syntax: AssemblySyntax,
known_addr_table: Option<Vec<u64>>,
known_branch_targets: Option<Vec<u64>>,
cfe_addr_table: Option<Vec<u64>>,
}
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<AnalyzedCode, DeoptimizerError> {
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<u64, Instruction> = HashMap::new();
let mut code = Vec::new();
let mut addr_map: HashMap<u64, Instruction> = 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<u64> {
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<String, DeoptimizerError> {
let mut decoder = Decoder::with_ip(mode, code, start_addr, DecoderOptions::NONE);
pub fn disassemble(&mut self, acode: &AnalyzedCode) -> Result<String, DeoptimizerError> {
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<bool, DeoptimizerError> {
// 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<Vec<Instruction>, 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<AsmResult, keystone::Error> {
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<String, DeoptimizerError> {
let mut dcode = Vec::new(); // deoptimized code
// let mut nb_fix_table: HashMap<u64, u64> = HashMap::new();
// let mut fb_fix_table: HashMap<u64, u64> = 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()
}
+211 -91
View File
@@ -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<Mnemonic> {
// 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<Instruction>,
rip: u64,
) -> Result<Vec<Instruction>, 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<Code, TransformError> {
// 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<Code, TransformError> {
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<Code, TransformError> {
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<Code, TransformError> {
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<Code, TransformError> {
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<Code, TransformError> {
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<Code, TransformError> {
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<Code, TransformError> {
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()
}
+122 -131
View File
@@ -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) {
+252 -238
View File
@@ -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<Vec<Instruction>, TransformError> {
// First check the operand types.
let base_reg = inst.memory_base();
if !inst
.op_kinds()
.collect::<Vec<OpKind>>()
.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<Vec<Instruction>, TransformError> {
if !inst
.op_kinds()
.collect::<Vec<OpKind>>()
.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<Vec<Instruction>, 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<Vec<Instruction>, 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<Vec<Instruction>, 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<Vec<Instruction>, 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<Vec<Instruction>, 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<Vec<Instruction>, 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<Vec<Instruction>, 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<Vec<Instruction>, TransformError> {
// First check the operand types.
let base_reg = inst.memory_base();
if !inst
.op_kinds()
.collect::<Vec<OpKind>>()
.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<Vec<Instruction>, TransformError> {
if !inst
.op_kinds()
.collect::<Vec<OpKind>>()
.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...