mirror of
https://github.com/EgeBalci/deoptimizer
synced 2026-06-08 10:55:50 +00:00
First commit.
This commit is contained in:
+15
-1
@@ -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
@@ -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
@@ -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/"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod deoptimizer;
|
||||
mod transforms;
|
||||
pub use deoptimizer::*;
|
||||
pub use transforms::*;
|
||||
@@ -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!("...")
|
||||
}
|
||||
Reference in New Issue
Block a user