First commit.

This commit is contained in:
Ege Balcı
2023-11-14 16:42:33 +01:00
parent 701873004e
commit d17469ab4e
11 changed files with 603 additions and 200 deletions
+15 -1
View File
@@ -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
Generated
+64 -21
View File
@@ -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"
+7 -1
View File
@@ -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/"
+22
View File
@@ -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.
+24
View File
@@ -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
+9 -8
View File
@@ -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);
}
+14 -4
View File
@@ -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')]
-165
View File
@@ -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<String> {
regs.iter().map(|&x| cs.reg_name(x).unwrap()).collect()
}
/// Return instruction group names
pub fn get_group_names(cs: &Capstone, regs: &[InsnGroupId]) -> Vec<String> {
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<u64> {
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<String, capstone::Error> {
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<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)?;
engine.option(keystone::OptionType::SYNTAX, keystone::OPT_SYNTAX_INTEL)?;
engine.asm(code, addr)
}
+279
View File
@@ -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<String>,
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 {
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<u64> {
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<String, DeoptimizerError> {
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<Vec<Instruction>, 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<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)?;
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()
}
+4
View File
@@ -0,0 +1,4 @@
mod deoptimizer;
mod transforms;
pub use deoptimizer::*;
pub use transforms::*;
+165
View File
@@ -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<Code, TransformError> {
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<Code, TransformError> {
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<Vec<Instruction>, TransformError> {
// First check the operand types.
let index_reg = inst.memory_index();
if !inst
.op_kinds()
.collect::<Vec<OpKind>>()
.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<Vec<Instruction>, TransformError> {
let index_reg = inst.memory_index();
if !inst
.op_kinds()
.collect::<Vec<OpKind>>()
.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<Vec<Instruction>, TransformError> {
todo!("...")
}
// 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> {
todo!("...")
}
/// Applies arithmetic partitioning transform to given instruction.
pub fn apply_ap_transform(
inst: &mut Instruction,
mode: u32,
) -> Result<Vec<Instruction>, TransformError> {
todo!("...")
}
/// Applies logical inverse transform to given instruction.
pub fn apply_li_transform(
inst: &mut Instruction,
mode: u32,
) -> Result<Vec<Instruction>, TransformError> {
todo!("...")
}
// Others...
/// Applies call proxy instruction transform.
pub fn apply_cp_transform(inst_addr: u64, mode: u32) -> Result<(), TransformError> {
todo!("...")
}