├── tests ├── data │ ├── cheat-ac.exe │ ├── cheat-ac.pdb │ ├── rust-test.exe │ ├── rust_test.pdb │ ├── seh-test.exe │ ├── seh-test.pdb │ ├── login-program.exe │ └── login-program.pdb ├── cheat.bat ├── seh.bat ├── rust.bat └── normal.bat ├── Cargo.toml ├── crates ├── common │ ├── src │ │ ├── lib.rs │ │ └── logger.rs │ ├── Cargo.toml │ └── README.md ├── core │ ├── README.md │ ├── src │ │ ├── pe │ │ │ ├── mod.rs │ │ │ ├── parser.rs │ │ │ └── sections.rs │ │ ├── pdb │ │ │ ├── mod.rs │ │ │ └── parser.rs │ │ ├── obfuscator.rs │ │ ├── instruction.rs │ │ ├── passes │ │ │ ├── mod.rs │ │ │ └── mutation.rs │ │ ├── compiler.rs │ │ ├── lib.rs │ │ ├── analyzer.rs │ │ ├── branches.rs │ │ └── function.rs │ └── Cargo.toml └── cli │ ├── README.md │ ├── Cargo.toml │ └── src │ └── main.rs ├── .gitignore ├── .github └── workflows │ └── ci.yml └── README.md /tests/data/cheat-ac.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/cheat-ac.exe -------------------------------------------------------------------------------- /tests/data/cheat-ac.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/cheat-ac.pdb -------------------------------------------------------------------------------- /tests/data/rust-test.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/rust-test.exe -------------------------------------------------------------------------------- /tests/data/rust_test.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/rust_test.pdb -------------------------------------------------------------------------------- /tests/data/seh-test.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/seh-test.exe -------------------------------------------------------------------------------- /tests/data/seh-test.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/seh-test.pdb -------------------------------------------------------------------------------- /tests/data/login-program.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/login-program.exe -------------------------------------------------------------------------------- /tests/data/login-program.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasie1337/bin-obfuscator/HEAD/tests/data/login-program.pdb -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/core", "crates/cli", "crates/common", 4 | ] 5 | resolver = "2" 6 | -------------------------------------------------------------------------------- /tests/cheat.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cargo run --release --bin cli -- data\cheat-ac.exe data\cheat-ac.pdb -o ..\test-output\cheat.exe -------------------------------------------------------------------------------- /tests/seh.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cargo run --release --bin cli -- data\seh-test.exe data\seh-test.pdb -o ..\test-output\seh-test.exe -------------------------------------------------------------------------------- /tests/rust.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cargo run --release --bin cli -- data\rust-test.exe data\rust_test.pdb -o ..\test-output\rust-test.exe -------------------------------------------------------------------------------- /crates/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod logger; 2 | 3 | pub use logger::Logger; 4 | 5 | pub use log::{debug, error, info, trace, warn}; 6 | -------------------------------------------------------------------------------- /tests/normal.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cargo run --release --bin cli -- data\login-program.exe data\login-program.pdb -o ..\test-output\login-program.exe -------------------------------------------------------------------------------- /crates/core/README.md: -------------------------------------------------------------------------------- 1 | ## Core 2 | 3 | Crate containing the main functionality of the obfuscator including but not limited to the binary lifter, assembler and rewriter. -------------------------------------------------------------------------------- /crates/cli/README.md: -------------------------------------------------------------------------------- 1 | ## CLI 2 | 3 | Command-line interface for the binary obfuscator. This crate provides the main executable that users interact with to obfuscate binary files. 4 | -------------------------------------------------------------------------------- /crates/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | log = "0.4" 8 | env_logger = "0.11" 9 | chrono = { version = "0.4", features = ["serde"] } 10 | -------------------------------------------------------------------------------- /crates/core/src/pe/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parser; 2 | pub mod sections; 3 | 4 | pub enum PEType { 5 | DLL, 6 | EXE, 7 | SYS, 8 | } 9 | 10 | #[derive(Clone)] 11 | pub struct PEContext { 12 | pub pe_data: Vec, 13 | } 14 | -------------------------------------------------------------------------------- /crates/common/README.md: -------------------------------------------------------------------------------- 1 | ## Common 2 | 3 | Shared utilities and common functionality used across the binary obfuscator crates. This crate contains reusable components, data structures, and helper functions that are used by both the core and CLI crates. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | # IDE 6 | .vscode/ 7 | .idea/ 8 | *.swp 9 | *.swo 10 | 11 | # OS 12 | .DS_Store 13 | Thumbs.db 14 | 15 | # Logs 16 | *.log 17 | 18 | # Test artifacts 19 | /test-output/ 20 | /coverage/ 21 | -------------------------------------------------------------------------------- /crates/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | common = { path = "../common", package = "common" } 8 | core = { path = "../core", package = "core" } 9 | clap = { version = "4.5", features = ["derive"] } 10 | log = "0.4" -------------------------------------------------------------------------------- /crates/core/src/pdb/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod parser; 2 | 3 | #[derive(Clone)] 4 | #[allow(dead_code)] 5 | pub struct PDBFunction { 6 | pub name: String, 7 | pub rva: u32, 8 | pub size: u32, 9 | } 10 | 11 | #[derive(Clone)] 12 | pub struct PDBContext { 13 | pdb_data: Vec, 14 | } 15 | -------------------------------------------------------------------------------- /crates/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "core" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | common = { path = "../common", package = "common" } 8 | iced-x86 = { version = "1.21.0", features = ["code_asm"] } 9 | rand = "0.9.2" 10 | anyhow = "1.0" 11 | instant = "0.1" 12 | goblin = "0.10.0" 13 | symbolic = { version = "12.16.1", features = ["demangle"] } 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup Rust toolchain 16 | uses: dtolnay/rust-toolchain@stable 17 | with: 18 | components: rustfmt, clippy 19 | 20 | - name: Cache Rust dependencies 21 | uses: Swatinem/rust-cache@v2 22 | 23 | - name: Run tests 24 | run: cargo test --verbose 25 | 26 | - name: Build release 27 | run: cargo build --release --verbose 28 | 29 | -------------------------------------------------------------------------------- /crates/core/src/obfuscator.rs: -------------------------------------------------------------------------------- 1 | use crate::function::ObfuscatorFunction; 2 | use crate::passes::PassManager; 3 | 4 | pub struct Obfuscator { 5 | pass_manager: PassManager, 6 | } 7 | 8 | impl Obfuscator { 9 | pub fn new() -> Self { 10 | Self { 11 | pass_manager: PassManager::default(), 12 | } 13 | } 14 | 15 | pub fn obfuscate(&self, functions: &mut [ObfuscatorFunction]) -> Result<(), String> { 16 | functions.iter_mut().for_each(|function| { 17 | self.pass_manager.run_passes(function, 2); 18 | }); 19 | Ok(()) 20 | } 21 | } 22 | 23 | impl Default for Obfuscator { 24 | fn default() -> Self { 25 | Self::new() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/core/src/pdb/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::pdb::{PDBContext, PDBFunction}; 2 | use symbolic::common::Name; 3 | use symbolic::debuginfo::pdb::PdbObject; 4 | use symbolic::demangle::{Demangle, DemangleOptions}; 5 | 6 | impl PDBContext { 7 | pub fn new(pdb_data: Vec) -> Self { 8 | Self { pdb_data } 9 | } 10 | 11 | pub fn is_supported(&self) -> bool { 12 | true 13 | } 14 | 15 | pub fn get_functions(&self) -> Result, String> { 16 | match self.parse() { 17 | Ok(functions) => Ok(functions), 18 | Err(e) => Err(e.to_string()), 19 | } 20 | } 21 | 22 | fn parse(&self) -> Result, String> { 23 | let pdb_object = PdbObject::parse(&self.pdb_data).map_err(|e| e.to_string())?; 24 | let mut functions = Vec::new(); 25 | 26 | if let Ok(session) = pdb_object.debug_session() { 27 | for func in session.functions().flatten() { 28 | functions.push(PDBFunction { 29 | name: self.demangle_name(func.name.as_ref()), 30 | rva: func.address as u32, 31 | size: func.size as u32, 32 | }); 33 | } 34 | } 35 | 36 | functions.sort_by_key(|f: &PDBFunction| f.rva); 37 | functions.dedup_by(|a, b| a.rva == b.rva); 38 | 39 | Ok(functions) 40 | } 41 | 42 | fn demangle_name(&self, name: &str) -> String { 43 | let name = Name::from(name); 44 | let demangled = name.try_demangle(DemangleOptions::complete()); 45 | demangled.to_string() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/core/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use iced_x86::*; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | pub struct InstructionContext { 5 | next_id: AtomicUsize, 6 | } 7 | 8 | impl Clone for InstructionContext { 9 | fn clone(&self) -> Self { 10 | Self { 11 | next_id: AtomicUsize::new(self.next_id.load(Ordering::Relaxed)), 12 | } 13 | } 14 | } 15 | 16 | impl InstructionContext { 17 | pub fn new() -> Self { 18 | Self { 19 | next_id: AtomicUsize::new(0), 20 | } 21 | } 22 | 23 | pub fn next_id(&self) -> usize { 24 | self.next_id.fetch_add(1, Ordering::Relaxed) 25 | } 26 | 27 | pub fn create_instruction(&self, instruction: Instruction) -> InstructionWithId { 28 | InstructionWithId::new(self.next_id(), instruction) 29 | } 30 | } 31 | 32 | impl Default for InstructionContext { 33 | fn default() -> Self { 34 | Self::new() 35 | } 36 | } 37 | 38 | #[derive(Clone)] 39 | pub struct InstructionWithId { 40 | pub id: usize, 41 | pub instruction: Instruction, 42 | } 43 | 44 | impl InstructionWithId { 45 | pub fn new(id: usize, instruction: Instruction) -> Self { 46 | Self { id, instruction } 47 | } 48 | 49 | pub fn get_id(&self) -> usize { 50 | self.id 51 | } 52 | 53 | pub fn set_id(&mut self, id: usize) { 54 | self.id = id; 55 | } 56 | 57 | pub fn get_memory_operand(&self) -> MemoryOperand { 58 | let instr = &self.instruction; 59 | MemoryOperand::new( 60 | instr.memory_base(), 61 | instr.memory_index(), 62 | instr.memory_index_scale(), 63 | instr.memory_displacement64() as i64, 64 | instr.memory_displ_size(), 65 | instr.is_broadcast(), 66 | instr.segment_prefix(), 67 | ) 68 | } 69 | 70 | pub fn get_bytes(&self) -> Result, String> { 71 | let mut encoder = Encoder::new(64); 72 | encoder 73 | .encode(&self.instruction, self.instruction.ip()) 74 | .map_err(|e| format!("Encoding failed: {}", e))?; 75 | Ok(encoder.take_buffer()) 76 | } 77 | 78 | pub fn re_encode(&self, rip: u64) -> Result { 79 | let bytes = self.get_bytes()?; 80 | let mut decoder = Decoder::new(64, &bytes, DecoderOptions::NONE); 81 | decoder.set_ip(rip); 82 | let mut inst = Instruction::default(); 83 | decoder.decode_out(&mut inst); 84 | Ok(inst) 85 | } 86 | } 87 | 88 | impl std::fmt::Debug for InstructionWithId { 89 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 90 | write!(f, "InstructionWithId {{ id: {}, instruction: {:?} }}", self.id, self.instruction) 91 | } 92 | } 93 | 94 | impl std::fmt::Display for InstructionWithId { 95 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 96 | write!(f, "InstructionWithId {{ id: {}, instruction: {:?} }}", self.id, self.instruction) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crates/core/src/passes/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::function::ObfuscatorFunction; 2 | use common::{debug, error}; 3 | pub mod mutation; 4 | 5 | pub trait Pass { 6 | fn name(&self) -> &'static str; 7 | fn apply(&self, function: &mut ObfuscatorFunction) -> Result<(), String>; 8 | fn enabled_by_default(&self) -> bool { 9 | true 10 | } 11 | } 12 | 13 | pub struct PassManager { 14 | passes: Vec>, 15 | } 16 | 17 | impl PassManager { 18 | pub fn new() -> Self { 19 | Self { passes: Vec::new() } 20 | } 21 | 22 | pub fn add_pass(&mut self, pass: Box) { 23 | self.passes.push(pass); 24 | } 25 | 26 | pub fn run_passes(&self, function: &mut ObfuscatorFunction, count: usize) { 27 | debug!( 28 | "Running {} passes {} times on function {}", 29 | self.passes.len(), 30 | count, 31 | function.name 32 | ); 33 | 34 | for iteration in 0..count { 35 | debug!( 36 | "Pass iteration {} for function {}", 37 | iteration + 1, 38 | function.name 39 | ); 40 | 41 | for pass in &self.passes { 42 | debug!( 43 | "Applying pass '{}' to function {}", 44 | pass.name(), 45 | function.name 46 | ); 47 | let pre_instruction_count = function.instructions.len(); 48 | 49 | match pass.apply(function) { 50 | Ok(_) => { 51 | let post_instruction_count = function.instructions.len(); 52 | if pre_instruction_count != post_instruction_count { 53 | debug!( 54 | "Pass '{}' modified function {}: {} -> {} instructions", 55 | pass.name(), 56 | function.name, 57 | pre_instruction_count, 58 | post_instruction_count 59 | ); 60 | } else { 61 | debug!( 62 | "Pass '{}' completed on function {} (no changes)", 63 | pass.name(), 64 | function.name 65 | ); 66 | } 67 | } 68 | Err(e) => { 69 | error!( 70 | "Failed to apply pass {} to function {}: {}", 71 | pass.name(), 72 | function.name, 73 | e 74 | ); 75 | } 76 | } 77 | } 78 | } 79 | 80 | debug!( 81 | "Completed all pass iterations for function {}", 82 | function.name 83 | ); 84 | } 85 | } 86 | 87 | impl Default for PassManager { 88 | fn default() -> Self { 89 | let mut manager = Self::new(); 90 | manager.add_pass(Box::new(mutation::MutationPass::new())); 91 | manager 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/core/src/compiler.rs: -------------------------------------------------------------------------------- 1 | use crate::function::{AddressUpdatable, Encodable, ObfuscatorFunction, StateManaged}; 2 | use crate::pe::PEContext; 3 | use std::cell::RefCell; 4 | use std::rc::Rc; 5 | 6 | pub struct CompilerContext { 7 | pe_context: Rc>, 8 | } 9 | 10 | impl CompilerContext { 11 | pub fn new(pe_context: Rc>) -> Self { 12 | Self { pe_context } 13 | } 14 | 15 | pub fn compile_functions( 16 | &mut self, 17 | functions: &mut [ObfuscatorFunction], 18 | ) -> Result, String> { 19 | let base_rva = self 20 | .pe_context 21 | .borrow() 22 | .get_next_section_rva() 23 | .map_err(|e| format!("Failed to get section RVA: {e}"))?; 24 | 25 | let (merged_bytes, _) = 26 | functions 27 | .iter_mut() 28 | .try_fold((Vec::new(), base_rva), |(mut bytes, rva), func| { 29 | let encoded = func 30 | .encode(rva) 31 | .map_err(|e| format!("Failed to encode {}: {e}", func.name))?; 32 | 33 | bytes.extend_from_slice(&encoded); 34 | func.update_rva(rva as u32); 35 | func.update_size(encoded.len() as u32); 36 | 37 | Ok::<_, String>((bytes, rva + encoded.len() as u32)) 38 | })?; 39 | 40 | self.trash_old_function_bytes(functions)?; 41 | self.patch_function_redirects(functions)?; 42 | 43 | self.pe_context 44 | .borrow_mut() 45 | .create_executable_section(".vasie", &merged_bytes) 46 | .map_err(|e| format!("Failed to create section: {e}"))?; 47 | 48 | Ok(merged_bytes) 49 | } 50 | 51 | fn trash_old_function_bytes(&self, functions: &[ObfuscatorFunction]) -> Result<(), String> { 52 | functions 53 | .iter() 54 | .filter(|f| f.get_original_size() > 5) 55 | .try_for_each(|func| { 56 | let rva = func.get_original_rva() + 5; 57 | let size = func.get_original_size() - 5; 58 | let bytes = vec![0xCC; size as usize]; 59 | 60 | self.pe_context 61 | .borrow_mut() 62 | .write_data_at_rva(rva, &bytes) 63 | .map_err(|e| format!("Failed to zero bytes at {rva:#x}: {e}")) 64 | }) 65 | } 66 | 67 | fn patch_function_redirects(&self, functions: &[ObfuscatorFunction]) -> Result<(), String> { 68 | functions.iter().try_for_each(|func| { 69 | let src_rva = func.get_original_rva(); 70 | let rel_offset = (func.rva as i64) - ((src_rva + 5) as i64); 71 | 72 | let mut jmp_bytes = [0xE9u8; 5]; 73 | jmp_bytes[1..].copy_from_slice(&(rel_offset as i32).to_le_bytes()); 74 | 75 | self.pe_context 76 | .borrow_mut() 77 | .write_data_at_rva(src_rva, &jmp_bytes) 78 | .map_err(|e| format!("Failed to patch JMP at {src_rva:#x}: {e}")) 79 | }) 80 | } 81 | 82 | pub fn get_binary_data(self) -> Vec { 83 | self.pe_context.borrow().pe_data.clone() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Binary Obfuscator 2 | 3 | A x86-64 PE binary obfuscation tool that transforms executable code to make reverse engineering more difficult while preserving functionality. 4 | 5 | ## Overview 6 | 7 | This tool analyzes PE binaries with their corresponding PDB debug files and applies various obfuscation techniques to the machine code. The obfuscation process focuses on instruction-level mutations that maintain program semantics while increasing complexity. 8 | 9 | ## Architecture 10 | 11 | The project consists of three main crates: 12 | 13 | - `core` - Contains the core obfuscation engine, analysis, and compilation logic 14 | - `cli` - Command-line interface for the obfuscator 15 | - `common` - Shared utilities and logging functionality 16 | 17 | ## Features 18 | 19 | ### Instruction Mutations 20 | 21 | The mutation pass transforms specific x86-64 instructions into functionally equivalent but more complex sequences: 22 | 23 | - **LEA mutations** - Adds random displacement with compensating SUB instruction 24 | - **ADD mutations** - Replaces with CLC + ADC + flag preservation 25 | - **OR mutations** - Replaces with complex bit manipulation using ANDN, BLSI, TZCNT 26 | - **INC/DEC mutations** - Replaces with CLC + ADC/SBB + flag preservation 27 | - **PUSH mutations** - Replaces with explicit memory operations (MOV + SUB) 28 | 29 | ### Analysis Engine 30 | 31 | - PE binary parsing and validation 32 | - PDB debug information processing 33 | - Function discovery from debug symbols 34 | - Multi-stage filtering pipeline: 35 | - Size filtering (removes functions ≤5 bytes) 36 | - Exception function filtering (skips functions with unwind handlers) 37 | - Instruction decoding validation 38 | 39 | ### Branch Management 40 | 41 | - Branch instruction detection and mapping 42 | - Internal branch target tracking and fixup 43 | - Support for conditional and unconditional branches 44 | - Cross-reference resolution after mutation 45 | 46 | ### Compilation System 47 | 48 | - Binary reconstruction with obfuscated code 49 | - Instruction re-encoding and optimization 50 | - Output generation preserving PE structure 51 | 52 | ## Mutation Stability 53 | 54 | **Warning: The mutation system is experimental and unstable.** 55 | 56 | Current limitations: 57 | 58 | - Flag register handling may not preserve all semantics 59 | - Complex control flow may break with certain mutations 60 | - Memory operand mutations need validation 61 | - Some instruction sequences may produce incorrect results 62 | - Functions with indirect jumps or jump tables are not supported 63 | - Exception handling functions are automatically skipped 64 | 65 | The mutation pass transforms instructions but proper testing is required for each target binary. 66 | 67 | ## Usage 68 | 69 | ```bash 70 | bin-obfuscator [OPTIONS] 71 | 72 | Arguments: 73 | Path to the PE binary file to obfuscate 74 | Path to the corresponding PDB debug file 75 | 76 | Options: 77 | -o, --output Output path for the obfuscated binary 78 | -v, --verbose Enable verbose output (use -vv for debug, -vvv for trace) 79 | -q, --quiet Suppress non-error output 80 | -h, --help Print help 81 | ``` 82 | 83 | ## Requirements 84 | 85 | - x86-64 PE executable files (.exe, .dll) 86 | - Corresponding PDB debug files 87 | - Windows target platform 88 | 89 | ## Dependencies 90 | 91 | - `iced-x86` - x86 instruction encoding/decoding 92 | - `goblin` - Binary parsing 93 | - `symbolic` - Debug symbol processing 94 | - `clap` - CLI argument parsing 95 | - `rand` - Random number generation for mutations 96 | 97 | ## Build 98 | 99 | ```bash 100 | cargo build --release 101 | ``` 102 | 103 | ## Example 104 | 105 | ```bash 106 | bin-obfuscator.exe target.exe target.pdb -o obfuscated.exe -v 107 | ``` 108 | 109 | This will obfuscate `target.exe` using debug information from `target.pdb` and output the result to `obfuscated.exe` with verbose logging. 110 | -------------------------------------------------------------------------------- /crates/common/src/logger.rs: -------------------------------------------------------------------------------- 1 | use env_logger::{Builder, Target}; 2 | use log::LevelFilter; 3 | use std::io::Write; 4 | use std::sync::Once; 5 | 6 | static INIT: Once = Once::new(); 7 | 8 | pub struct Logger; 9 | 10 | impl Logger { 11 | pub fn init() -> Result<(), log::SetLoggerError> { 12 | Self::init_with_level(LevelFilter::Info) 13 | } 14 | 15 | pub fn ensure_init() { 16 | INIT.call_once(|| { 17 | let _ = Self::init(); 18 | }); 19 | } 20 | 21 | pub fn ensure_init_with_level(level: LevelFilter) { 22 | INIT.call_once(|| { 23 | let _ = Self::init_with_level(level); 24 | }); 25 | } 26 | 27 | pub fn init_with_level(level: LevelFilter) -> Result<(), log::SetLoggerError> { 28 | let mut builder = Builder::from_default_env(); 29 | 30 | builder 31 | .target(Target::Stdout) 32 | .filter_level(level) 33 | .format(|buf, record| { 34 | let file_path = record.file().unwrap_or("unknown"); 35 | let crate_name = Self::extract_crate_name(file_path); 36 | 37 | let (level_color, reset) = match record.level() { 38 | log::Level::Error => ("\x1b[31m", "\x1b[0m"), // Red 39 | log::Level::Warn => ("\x1b[33m", "\x1b[0m"), // Yellow 40 | log::Level::Info => ("\x1b[32m", "\x1b[0m"), // Green 41 | log::Level::Debug => ("\x1b[36m", "\x1b[0m"), // Cyan 42 | log::Level::Trace => ("\x1b[35m", "\x1b[0m"), // Magenta 43 | }; 44 | 45 | writeln!( 46 | buf, 47 | "\x1b[0m {}{:>5}{} \x1b[94m[{}]\x1b[0m \x1b[90m{}:{}\x1b[0m {}", 48 | level_color, 49 | record.level(), 50 | reset, 51 | crate_name, 52 | record.file().unwrap_or("unknown"), 53 | record.line().unwrap_or(0), 54 | record.args() 55 | ) 56 | }); 57 | 58 | builder.try_init() 59 | } 60 | 61 | pub fn init_debug() -> Result<(), log::SetLoggerError> { 62 | Self::init_with_level(LevelFilter::Debug) 63 | } 64 | 65 | pub fn init_trace() -> Result<(), log::SetLoggerError> { 66 | Self::init_with_level(LevelFilter::Trace) 67 | } 68 | 69 | pub fn init_simple() -> Result<(), log::SetLoggerError> { 70 | let mut builder = Builder::from_default_env(); 71 | 72 | builder 73 | .target(Target::Stdout) 74 | .filter_level(LevelFilter::Debug) 75 | .format(|buf, record| { 76 | let crate_name = Self::extract_crate_name(record.file().unwrap_or("unknown")); 77 | let (level_color, reset) = match record.level() { 78 | log::Level::Error => ("\x1b[31m", "\x1b[0m"), 79 | log::Level::Warn => ("\x1b[33m", "\x1b[0m"), 80 | log::Level::Info => ("\x1b[32m", "\x1b[0m"), 81 | log::Level::Debug => ("\x1b[36m", "\x1b[0m"), 82 | log::Level::Trace => ("\x1b[35m", "\x1b[0m"), 83 | }; 84 | 85 | writeln!( 86 | buf, 87 | "{}{:>5}{} \x1b[94m[{}]\x1b[0m {}", 88 | level_color, 89 | record.level(), 90 | reset, 91 | crate_name, 92 | record.args() 93 | ) 94 | }); 95 | 96 | builder.try_init() 97 | } 98 | 99 | pub fn is_enabled(level: log::Level) -> bool { 100 | log::log_enabled!(level) 101 | } 102 | 103 | fn extract_crate_name(file_path: &str) -> &str { 104 | let path_parts: Vec<&str> = file_path.split(['/', '\\']).collect(); 105 | 106 | if let Some(crates_index) = path_parts.iter().position(|&part| part == "crates") { 107 | if crates_index + 1 < path_parts.len() { 108 | return path_parts[crates_index + 1]; 109 | } 110 | } 111 | 112 | if path_parts.len() >= 2 { 113 | path_parts[path_parts.len() - 2] 114 | } else { 115 | "unknown" 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] 2 | 3 | use analyzer::AnalyzerContext; 4 | use common::{Logger, debug, info, warn}; 5 | use compiler::CompilerContext; 6 | use function::ObfuscatorFunction; 7 | use instant::Instant; 8 | use obfuscator::Obfuscator; 9 | use pdb::PDBContext; 10 | use pe::PEContext; 11 | use std::cell::RefCell; 12 | use std::rc::Rc; 13 | 14 | pub mod analyzer; 15 | pub mod branches; 16 | pub mod compiler; 17 | pub mod function; 18 | pub mod instruction; 19 | pub mod obfuscator; 20 | pub mod passes; 21 | pub mod pdb; 22 | pub mod pe; 23 | 24 | pub struct CoreContext { 25 | pub pe_context: Rc>, 26 | pub pdb_context: Rc>, 27 | } 28 | 29 | impl CoreContext { 30 | pub fn new(pe_context: Rc>, pdb_context: Rc>) -> Self { 31 | Self { 32 | pe_context, 33 | pdb_context, 34 | } 35 | } 36 | } 37 | 38 | pub fn run(binary_data: &[u8], pdb_data: &[u8]) -> Result, String> { 39 | Logger::ensure_init(); 40 | 41 | let start_time = Instant::now(); 42 | info!("Starting binary obfuscation process"); 43 | debug!( 44 | "PE binary size: {} bytes, PDB size: {} bytes", 45 | binary_data.len(), 46 | pdb_data.len() 47 | ); 48 | 49 | let pe_context = parse_and_validate_pe(binary_data)?; 50 | let pdb_context = parse_and_validate_pdb(pdb_data)?; 51 | 52 | let core_context = CoreContext::new(pe_context, pdb_context); 53 | 54 | let mut obfuscator_functions = analyze_binary(&core_context)?; 55 | 56 | obfuscate_binary(&mut obfuscator_functions)?; 57 | 58 | let binary_data = compile_binary(&core_context, &mut obfuscator_functions)?; 59 | 60 | let elapsed = start_time.elapsed(); 61 | info!( 62 | "Successfully completed obfuscation of {} functions in {:.2}ms", 63 | obfuscator_functions.len(), 64 | elapsed.as_secs_f64() * 1000.0, 65 | ); 66 | 67 | Ok(binary_data) 68 | } 69 | 70 | fn parse_and_validate_pe(binary_data: &[u8]) -> Result>, String> { 71 | debug!("Parsing and validating PE binary"); 72 | let pe_context = PEContext::new(binary_data.to_vec()); 73 | if !pe_context.is_supported() { 74 | warn!("PE binary is not supported"); 75 | return Err("PE is not supported".to_string()); 76 | } 77 | debug!("PE binary successfully parsed and validated"); 78 | Ok(Rc::new(RefCell::new(pe_context))) 79 | } 80 | 81 | fn parse_and_validate_pdb(pdb_data: &[u8]) -> Result>, String> { 82 | debug!("Parsing and validating PDB file"); 83 | let pdb_context = PDBContext::new(pdb_data.to_vec()); 84 | if !pdb_context.is_supported() { 85 | warn!("PDB file is not supported"); 86 | return Err("PDB is not supported".to_string()); 87 | } 88 | debug!("PDB file successfully parsed and validated"); 89 | Ok(Rc::new(RefCell::new(pdb_context))) 90 | } 91 | 92 | fn analyze_binary(core_context: &CoreContext) -> Result, String> { 93 | info!("Starting binary analysis phase"); 94 | let analyzer_context = AnalyzerContext::new(core_context); 95 | let obfuscator_functions = analyzer_context.analyze()?; 96 | info!( 97 | "Binary analysis completed, found {} functions", 98 | obfuscator_functions.len() 99 | ); 100 | Ok(obfuscator_functions) 101 | } 102 | 103 | fn obfuscate_binary(functions: &mut [ObfuscatorFunction]) -> Result<(), String> { 104 | info!( 105 | "Starting obfuscation phase for {} functions", 106 | functions.len() 107 | ); 108 | let obfuscator = Obfuscator::new(); 109 | obfuscator.obfuscate(functions)?; 110 | info!("Obfuscation phase completed successfully"); 111 | Ok(()) 112 | } 113 | 114 | fn compile_binary( 115 | core_context: &CoreContext, 116 | functions: &mut [ObfuscatorFunction], 117 | ) -> Result, String> { 118 | info!( 119 | "Starting compilation phase for {} functions", 120 | functions.len() 121 | ); 122 | let mut compiler_context = CompilerContext::new(core_context.pe_context.clone()); 123 | compiler_context.compile_functions(functions)?; 124 | let binary_data = compiler_context.get_binary_data(); 125 | info!( 126 | "Compilation phase completed, generated {} bytes", 127 | binary_data.len() 128 | ); 129 | Ok(binary_data) 130 | } 131 | -------------------------------------------------------------------------------- /crates/core/src/analyzer.rs: -------------------------------------------------------------------------------- 1 | use crate::pdb::{PDBContext, PDBFunction}; 2 | use crate::pe::PEContext; 3 | use crate::{ 4 | CoreContext, 5 | function::{Decodable, ObfuscatorFunction, StateManaged}, 6 | }; 7 | use common::info; 8 | use std::cell::RefCell; 9 | use std::rc::Rc; 10 | 11 | pub struct AnalyzerContext { 12 | pe_context: Rc>, 13 | pdb_context: Rc>, 14 | } 15 | 16 | impl AnalyzerContext { 17 | pub fn new(core_context: &CoreContext) -> Self { 18 | Self { 19 | pe_context: core_context.pe_context.clone(), 20 | pdb_context: core_context.pdb_context.clone(), 21 | } 22 | } 23 | 24 | fn filter_by_size(&self, pdb_functions: &[PDBFunction]) -> Vec { 25 | let total = pdb_functions.len(); 26 | let size_filtered: Vec = pdb_functions 27 | .iter() 28 | .filter(|f| f.size > 5) 29 | .cloned() 30 | .collect(); 31 | let filtered_count = total - size_filtered.len(); 32 | info!( 33 | "Size filter: {} functions remaining (filtered out {} ≤5 bytes)", 34 | size_filtered.len(), 35 | filtered_count 36 | ); 37 | size_filtered 38 | } 39 | 40 | fn decode_functions(&self, pdb_functions: Vec) -> Vec { 41 | let mut failed_decodes = 0; 42 | let functions: Vec = pdb_functions 43 | .iter() 44 | .filter_map(|f| { 45 | let mut func = ObfuscatorFunction::new(f); 46 | match func.decode(&self.pe_context.borrow()) { 47 | Ok(_) => Some(func), 48 | Err(_) => { 49 | failed_decodes += 1; 50 | None 51 | } 52 | } 53 | }) 54 | .collect(); 55 | 56 | info!( 57 | "Decode: {} functions successfully decoded, {} failed", 58 | functions.len(), 59 | failed_decodes 60 | ); 61 | functions 62 | } 63 | 64 | fn analyze_functions(&self, functions: &mut [ObfuscatorFunction]) -> Result<(), String> { 65 | for func in functions.iter_mut() { 66 | func.capture_original_state(); 67 | func.build_branch_map(); 68 | } 69 | Ok(()) 70 | } 71 | 72 | fn filter_by_exception( 73 | &self, 74 | mut functions: Vec, 75 | ) -> Result, String> { 76 | let exception_functions = self.pe_context.borrow().get_exception_functions()?; 77 | let before = functions.len(); 78 | functions.retain(|f| { 79 | !exception_functions 80 | .iter() 81 | .any(|ef| ef.begin_address == f.rva) 82 | }); 83 | 84 | let filtered_count = before - functions.len(); 85 | info!( 86 | "Exception filter: {} functions remaining (filtered out {} with unwind info)", 87 | functions.len(), 88 | filtered_count 89 | ); 90 | Ok(functions) 91 | } 92 | 93 | pub fn analyze(&self) -> Result, String> { 94 | let pdb_functions = self 95 | .pdb_context 96 | .borrow() 97 | .get_functions() 98 | .map_err(|e| e.to_string())?; 99 | 100 | info!("Retrieved {} functions from PDB", pdb_functions.len()); 101 | 102 | let size_filtered = self.filter_by_size(&pdb_functions); 103 | if size_filtered.is_empty() { 104 | return Err("No functions to analyze".to_string()); 105 | } 106 | 107 | let decoded_functions = self.decode_functions(size_filtered); 108 | if decoded_functions.is_empty() { 109 | return Err("No functions to analyze".to_string()); 110 | } 111 | 112 | let mut functions = self.filter_by_exception(decoded_functions)?; 113 | if functions.is_empty() { 114 | return Err("No functions to analyze".to_string()); 115 | } 116 | 117 | // DEBUG: only 1 function 118 | //functions = functions.iter().filter(|f| f.name.contains("__scrt_fastfail")).cloned().collect(); 119 | 120 | self.analyze_functions(&mut functions)?; 121 | 122 | info!( 123 | "Analysis completed: {} functions ready for obfuscation", 124 | functions.len() 125 | ); 126 | Ok(functions) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /crates/core/src/pe/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::pe::{PEContext, PEType}; 2 | use goblin::pe::PE; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct UnwindFunction { 6 | pub begin_address: u32, 7 | pub end_address: u32, 8 | } 9 | 10 | impl PEContext { 11 | pub fn new(pe_data: Vec) -> Self { 12 | Self { pe_data } 13 | } 14 | 15 | pub fn parse(&self) -> Result { 16 | PE::parse(&self.pe_data).map_err(|e| e.to_string()) 17 | } 18 | 19 | fn get_pe_type(&self) -> Result { 20 | let pe = match self.parse() { 21 | Ok(pe) => pe, 22 | Err(e) => { 23 | return Err(e.to_string()); 24 | } 25 | }; 26 | let characteristics = pe.header.coff_header.characteristics; 27 | if characteristics & goblin::pe::characteristic::IMAGE_FILE_EXECUTABLE_IMAGE 28 | == goblin::pe::characteristic::IMAGE_FILE_EXECUTABLE_IMAGE 29 | { 30 | return Ok(PEType::EXE); 31 | } else if characteristics & goblin::pe::characteristic::IMAGE_FILE_DLL 32 | == goblin::pe::characteristic::IMAGE_FILE_DLL 33 | { 34 | return Ok(PEType::DLL); 35 | } else if characteristics & goblin::pe::characteristic::IMAGE_FILE_SYSTEM 36 | == goblin::pe::characteristic::IMAGE_FILE_SYSTEM 37 | { 38 | return Ok(PEType::SYS); 39 | } 40 | 41 | Err("Unsupported PE type".to_string()) 42 | } 43 | 44 | fn get_pe_machine(&self) -> u16 { 45 | let pe = match self.parse() { 46 | Ok(pe) => pe, 47 | Err(_) => return 0, 48 | }; 49 | pe.header.coff_header.machine 50 | } 51 | 52 | pub fn is_supported(&self) -> bool { 53 | let pe_type = match self.get_pe_type() { 54 | Ok(pe_type) => pe_type, 55 | Err(_) => return false, 56 | }; 57 | 58 | let pe_machine = self.get_pe_machine(); 59 | 60 | if pe_machine != goblin::pe::header::COFF_MACHINE_X86_64 { 61 | return false; 62 | } 63 | 64 | matches!(pe_type, PEType::EXE | PEType::DLL) 65 | } 66 | 67 | pub fn read_data(&self, offset: usize, size: usize) -> Result, String> { 68 | if offset + size > self.pe_data.len() { 69 | return Err("Read would exceed file bounds".to_string()); 70 | } 71 | Ok(self.pe_data[offset..offset + size].to_vec()) 72 | } 73 | 74 | pub fn write_data(&mut self, offset: usize, data: &[u8]) -> Result<(), String> { 75 | if offset + data.len() > self.pe_data.len() { 76 | return Err("Write would exceed file bounds".to_string()); 77 | } 78 | self.pe_data[offset..offset + data.len()].copy_from_slice(data); 79 | Ok(()) 80 | } 81 | 82 | pub fn rva_to_file_offset(&self, rva: u32) -> Result { 83 | let pe = self.parse()?; 84 | 85 | if rva 86 | < pe.header 87 | .optional_header 88 | .ok_or("Optional header not found".to_string())? 89 | .windows_fields 90 | .size_of_headers 91 | { 92 | return Ok(rva as usize); 93 | } 94 | 95 | for section in &pe.sections { 96 | let section_start = section.virtual_address; 97 | let section_end = section_start + section.virtual_size; 98 | 99 | if rva >= section_start && rva < section_end { 100 | let offset_in_section = rva - section_start; 101 | return Ok((section.pointer_to_raw_data + offset_in_section) as usize); 102 | } 103 | } 104 | 105 | Err(format!("RVA {rva:#x} not found in any section")) 106 | } 107 | 108 | pub fn file_offset_to_rva(&self, file_offset: usize) -> Result { 109 | let pe = self.parse()?; 110 | let file_offset = file_offset as u32; 111 | 112 | if file_offset 113 | < pe.header 114 | .optional_header 115 | .ok_or("Optional header not found".to_string())? 116 | .windows_fields 117 | .size_of_headers 118 | { 119 | return Ok(file_offset); 120 | } 121 | 122 | for section in &pe.sections { 123 | let section_file_start = section.pointer_to_raw_data; 124 | let section_file_end = section_file_start + section.size_of_raw_data; 125 | 126 | if file_offset >= section_file_start && file_offset < section_file_end { 127 | let offset_in_section = file_offset - section_file_start; 128 | return Ok(section.virtual_address + offset_in_section); 129 | } 130 | } 131 | 132 | Err(format!( 133 | "File offset {file_offset:#x} not found in any section" 134 | )) 135 | } 136 | 137 | pub fn read_data_at_rva(&self, rva: u32, size: usize) -> Result, String> { 138 | let file_offset = self.rva_to_file_offset(rva)?; 139 | self.read_data(file_offset, size) 140 | } 141 | 142 | pub fn write_data_at_rva(&mut self, rva: u32, data: &[u8]) -> Result<(), String> { 143 | let file_offset = self.rva_to_file_offset(rva)?; 144 | self.write_data(file_offset, data) 145 | } 146 | 147 | pub fn get_exception_functions( 148 | &self, 149 | ) -> Result, String> { 150 | let pe = self.parse()?; 151 | let exception_data = pe.exception_data.ok_or("Exception data not found")?; 152 | 153 | Ok(exception_data 154 | .functions() 155 | .filter_map(|f| f.as_ref().ok().cloned()) 156 | .filter(|function| { 157 | exception_data 158 | .get_unwind_info(*function, &pe.sections) 159 | .is_ok_and(|info| info.handler.is_some()) 160 | }) 161 | .collect()) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /crates/core/src/branches.rs: -------------------------------------------------------------------------------- 1 | use crate::{function::ObfuscatorFunction, instruction::InstructionWithId}; 2 | use common::{debug, warn}; 3 | use iced_x86::*; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct BranchInfo { 7 | pub source_id: usize, 8 | pub target_id: usize, 9 | pub original_target: u64, 10 | } 11 | 12 | pub struct BranchManager; 13 | 14 | impl BranchManager { 15 | pub fn new() -> Self { 16 | Self 17 | } 18 | 19 | pub fn get_branch_target(instruction: &Instruction) -> u32 { 20 | instruction.near_branch_target() as u32 21 | } 22 | 23 | pub fn set_branch_target(instruction: &mut Instruction, target_rva: u32) -> Result<(), String> { 24 | let op_kind = instruction.op0_kind(); 25 | match op_kind { 26 | OpKind::NearBranch16 => instruction.set_near_branch16(target_rva as u16), 27 | OpKind::NearBranch32 => instruction.set_near_branch32(target_rva), 28 | OpKind::NearBranch64 => instruction.set_near_branch64(target_rva as u64), 29 | _ => return Err(format!("Invalid branch operand kind: {op_kind:#?}")), 30 | } 31 | Ok(()) 32 | } 33 | 34 | pub fn build_branch_map( 35 | &self, 36 | instructions: &[InstructionWithId], 37 | function_rva: u32, 38 | function_size: u32, 39 | ) -> Vec { 40 | let mut branch_map = Vec::new(); 41 | 42 | for inst_with_id in instructions { 43 | let instruction = &inst_with_id.instruction; 44 | 45 | if !self.is_branch_instruction(instruction) { 46 | continue; 47 | } 48 | 49 | let target_rva = Self::get_branch_target(instruction); 50 | debug!("{instruction}"); 51 | debug!("Target RVA: {target_rva:#x}"); 52 | 53 | if target_rva < function_rva || target_rva >= function_rva + function_size { 54 | debug!( 55 | "Branch at RVA {:#x} targets outside function (target: {target_rva:#x}, function: {function_rva:#x}-{:#x})", 56 | instruction.ip(), 57 | function_rva + function_size, 58 | ); 59 | continue; 60 | } 61 | 62 | if let Some(target_inst) = instructions 63 | .iter() 64 | .find(|inst| inst.instruction.ip() == target_rva as u64) 65 | { 66 | debug!("Target instruction: {:#?}", target_inst.instruction); 67 | debug!("================================================"); 68 | 69 | branch_map.push(BranchInfo { 70 | source_id: inst_with_id.id, 71 | target_id: target_inst.id, 72 | original_target: target_rva as u64, 73 | }); 74 | } else { 75 | warn!( 76 | "Target instruction not found for internal branch at RVA {:#x} (target: {target_rva:#x})", 77 | instruction.ip(), 78 | ); 79 | debug!("{instruction}"); 80 | } 81 | } 82 | 83 | branch_map 84 | } 85 | 86 | pub fn fix_branches( 87 | &self, 88 | instructions: &mut [InstructionWithId], 89 | branch_map: &[BranchInfo], 90 | ) -> Result<(), String> { 91 | for branch_info in branch_map { 92 | let target_inst = instructions 93 | .iter() 94 | .find(|inst| inst.id == branch_info.target_id) 95 | .ok_or_else(|| { 96 | format!( 97 | "Target instruction with ID {} not found", 98 | branch_info.target_id 99 | ) 100 | })?; 101 | 102 | let target_ip = target_inst.instruction.ip() as u32; 103 | let target_str = target_inst.instruction.to_string(); 104 | 105 | let source_inst = instructions 106 | .iter_mut() 107 | .find(|inst| inst.id == branch_info.source_id) 108 | .ok_or_else(|| { 109 | format!( 110 | "Source instruction with ID {} not found", 111 | branch_info.source_id 112 | ) 113 | })?; 114 | 115 | debug!( 116 | "Fixing branch from {:#x} to {:#x}", 117 | source_inst.instruction.ip(), 118 | target_ip 119 | ); 120 | debug!("Source instruction: {:#?}", source_inst.instruction); 121 | debug!("Target instruction: {target_str}"); 122 | debug!("================================================"); 123 | 124 | Self::set_branch_target(&mut source_inst.instruction, target_ip)?; 125 | } 126 | 127 | Ok(()) 128 | } 129 | 130 | fn is_branch_instruction(&self, instruction: &Instruction) -> bool { 131 | matches!( 132 | instruction.flow_control(), 133 | FlowControl::ConditionalBranch | FlowControl::UnconditionalBranch 134 | ) 135 | } 136 | } 137 | 138 | impl Default for BranchManager { 139 | fn default() -> Self { 140 | Self::new() 141 | } 142 | } 143 | 144 | impl ObfuscatorFunction { 145 | pub fn get_branch_target(&self, instruction: &Instruction) -> u32 { 146 | BranchManager::get_branch_target(instruction) 147 | } 148 | 149 | pub fn set_branch_target( 150 | &self, 151 | instruction: &mut Instruction, 152 | target_rva: u32, 153 | ) -> Result<(), String> { 154 | BranchManager::set_branch_target(instruction, target_rva) 155 | } 156 | 157 | pub fn build_branch_map(&mut self) { 158 | let branch_manager = BranchManager::new(); 159 | self.branch_map = branch_manager.build_branch_map(&self.instructions, self.rva, self.size); 160 | } 161 | 162 | pub fn fix_branches(&mut self) -> Result<(), String> { 163 | let branch_manager = BranchManager::new(); 164 | branch_manager.fix_branches(&mut self.instructions, &self.branch_map) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /crates/core/src/function.rs: -------------------------------------------------------------------------------- 1 | use crate::branches::BranchInfo; 2 | use crate::instruction::{InstructionContext, InstructionWithId}; 3 | use crate::pdb::PDBFunction; 4 | use crate::pe::PEContext; 5 | use common::{debug, warn}; 6 | use iced_x86::*; 7 | 8 | pub trait Decodable { 9 | fn decode(&mut self, pe_context: &PEContext) -> Result<(), String>; 10 | } 11 | 12 | pub trait Encodable { 13 | fn encode(&mut self, rva: u32) -> Result, String>; 14 | } 15 | 16 | pub trait StateManaged { 17 | fn capture_original_state(&mut self); 18 | fn get_original_rva(&self) -> u32; 19 | fn get_original_size(&self) -> u32; 20 | fn get_original_instructions(&self) -> Result<&[Instruction], String>; 21 | } 22 | 23 | pub trait AddressUpdatable { 24 | fn update_rva(&mut self, rva: u32); 25 | fn update_size(&mut self, size: u32); 26 | } 27 | 28 | #[derive(Clone)] 29 | pub struct OriginalFunctionState { 30 | pub rva: u32, 31 | pub size: u32, 32 | pub instructions: Vec, 33 | } 34 | 35 | #[derive(Clone)] 36 | pub struct ObfuscatorFunction { 37 | pub name: String, 38 | pub rva: u32, 39 | pub size: u32, 40 | pub instructions: Vec, 41 | pub original: Option, 42 | pub branch_map: Vec, 43 | pub instruction_context: InstructionContext, 44 | } 45 | 46 | impl ObfuscatorFunction { 47 | pub fn new(pdb_function: &PDBFunction) -> Self { 48 | Self { 49 | name: pdb_function.name.clone(), 50 | rva: pdb_function.rva, 51 | size: pdb_function.size, 52 | instructions: vec![], 53 | original: None, 54 | branch_map: vec![], 55 | instruction_context: InstructionContext::new(), 56 | } 57 | } 58 | 59 | pub fn get_original(&self) -> Option<&OriginalFunctionState> { 60 | self.original.as_ref() 61 | } 62 | } 63 | 64 | impl AddressUpdatable for ObfuscatorFunction { 65 | fn update_rva(&mut self, rva: u32) { 66 | self.rva = rva; 67 | } 68 | 69 | fn update_size(&mut self, size: u32) { 70 | self.size = size; 71 | } 72 | } 73 | 74 | impl StateManaged for ObfuscatorFunction { 75 | fn capture_original_state(&mut self) { 76 | if self.original.is_some() { 77 | warn!( 78 | "Original state already captured for function {}, skipping", 79 | self.name 80 | ); 81 | return; 82 | } 83 | 84 | debug!( 85 | "Capturing original state for function {} (RVA: {:#x}, size: {}, instructions: {})", 86 | self.name, 87 | self.rva, 88 | self.size, 89 | self.instructions.len() 90 | ); 91 | 92 | let original_instructions: Vec = self 93 | .instructions 94 | .iter() 95 | .map(|inst| inst.instruction) 96 | .collect(); 97 | self.original = Some(OriginalFunctionState { 98 | rva: self.rva, 99 | size: self.size, 100 | instructions: original_instructions, 101 | }); 102 | } 103 | 104 | fn get_original_rva(&self) -> u32 { 105 | self.original 106 | .as_ref() 107 | .map(|orig| orig.rva) 108 | .unwrap_or(self.rva) 109 | } 110 | 111 | fn get_original_size(&self) -> u32 { 112 | self.original 113 | .as_ref() 114 | .map(|orig| orig.size) 115 | .unwrap_or(self.size) 116 | } 117 | 118 | fn get_original_instructions(&self) -> Result<&[Instruction], String> { 119 | if let Some(orig) = &self.original { 120 | Ok(&orig.instructions) 121 | } else { 122 | Err("Cannot get original instructions when original state is not captured".to_string()) 123 | } 124 | } 125 | } 126 | 127 | impl Decodable for ObfuscatorFunction { 128 | fn decode(&mut self, pe_context: &PEContext) -> Result<(), String> { 129 | debug!( 130 | "Decoding function {} at RVA {:#x} with size {}", 131 | self.name, self.rva, self.size 132 | ); 133 | 134 | let bytes = pe_context 135 | .read_data_at_rva(self.rva, self.size as usize) 136 | .map_err(|e| { 137 | format!( 138 | "Failed to read function bytes at RVA {:#x}: {}", 139 | self.rva, e 140 | ) 141 | })?; 142 | 143 | debug!("Read {} bytes for function {}", bytes.len(), self.name); 144 | 145 | let mut instructions = Vec::new(); 146 | let mut decoder = 147 | Decoder::with_ip(64, &bytes, self.rva as u64, iced_x86::DecoderOptions::NONE); 148 | 149 | let mut invalid_instruction_found = false; 150 | 151 | while decoder.can_decode() { 152 | let instruction = decoder.decode(); 153 | if instruction.code() == Code::INVALID { 154 | invalid_instruction_found = true; 155 | debug!("Invalid instruction found at RVA {:#x}", instruction.ip()); 156 | break; 157 | } 158 | instructions.push(self.instruction_context.create_instruction(instruction)); 159 | } 160 | 161 | if invalid_instruction_found { 162 | return Err(format!( 163 | "Invalid instruction found in function {}", 164 | self.name 165 | )); 166 | } 167 | 168 | instructions.shrink_to_fit(); 169 | 170 | debug!( 171 | "Successfully decoded function {} into {} instructions", 172 | self.name, 173 | instructions.len() 174 | ); 175 | 176 | self.instructions = instructions; 177 | 178 | Ok(()) 179 | } 180 | } 181 | 182 | fn adjust_instruction_addrs(code: &mut [InstructionWithId], start_addr: u32) { 183 | let mut new_ip = start_addr; 184 | for inst_with_id in code.iter_mut() { 185 | inst_with_id.instruction.set_ip(new_ip as u64); 186 | new_ip = inst_with_id.instruction.next_ip() as u32; 187 | } 188 | } 189 | 190 | impl Encodable for ObfuscatorFunction { 191 | fn encode(&mut self, rva: u32) -> Result, String> { 192 | debug!( 193 | "Encoding function {} with {} instructions at RVA {:#x}", 194 | self.name, 195 | self.instructions.len(), 196 | rva 197 | ); 198 | 199 | adjust_instruction_addrs(&mut self.instructions, rva); 200 | 201 | self.fix_branches()?; 202 | 203 | let instructions: Vec = self 204 | .instructions 205 | .iter() 206 | .map(|inst| inst.instruction) 207 | .collect(); 208 | 209 | let block = InstructionBlock::new(&instructions, rva as u64); 210 | 211 | let result = match BlockEncoder::encode(64, block, BlockEncoderOptions::NONE) { 212 | Ok(result) => result, 213 | Err(e) => { 214 | return Err(format!("Failed to encode function {}: {e}", self.name)); 215 | } 216 | }; 217 | 218 | debug!( 219 | "Successfully encoded function {} into {} bytes", 220 | self.name, 221 | result.code_buffer.len() 222 | ); 223 | 224 | Ok(result.code_buffer) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /crates/cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Arg, ArgAction, Command}; 2 | use common::{Logger, error, info}; 3 | use log::LevelFilter; 4 | use std::fs::File; 5 | use std::io::{Read, Write}; 6 | use std::path::{Path, PathBuf}; 7 | use std::process; 8 | 9 | fn load_file(path: &Path) -> Result, Box> { 10 | let mut file = 11 | File::open(path).map_err(|e| format!("Failed to open file '{}': {}", path.display(), e))?; 12 | 13 | let mut buffer = Vec::new(); 14 | file.read_to_end(&mut buffer) 15 | .map_err(|e| format!("Failed to read file '{}': {}", path.display(), e))?; 16 | 17 | if buffer.is_empty() { 18 | return Err(format!("File '{}' is empty", path.display()).into()); 19 | } 20 | 21 | Ok(buffer) 22 | } 23 | 24 | fn save_file(path: &Path, data: &[u8]) -> Result<(), Box> { 25 | if let Some(parent) = path.parent() { 26 | std::fs::create_dir_all(parent).map_err(|e| { 27 | format!( 28 | "Failed to create output directory '{}': {}", 29 | parent.display(), 30 | e 31 | ) 32 | })?; 33 | } 34 | 35 | let mut file = File::create(path) 36 | .map_err(|e| format!("Failed to create output file '{}': {}", path.display(), e))?; 37 | 38 | file.write_all(data) 39 | .map_err(|e| format!("Failed to write to output file '{}': {}", path.display(), e))?; 40 | 41 | Ok(()) 42 | } 43 | 44 | fn validate_file_exists(path: &Path, file_type: &str) -> Result<(), String> { 45 | if !path.exists() { 46 | return Err(format!( 47 | "{} file '{}' does not exist", 48 | file_type, 49 | path.display() 50 | )); 51 | } 52 | 53 | if !path.is_file() { 54 | return Err(format!( 55 | "{} path '{}' is not a file", 56 | file_type, 57 | path.display() 58 | )); 59 | } 60 | 61 | Ok(()) 62 | } 63 | 64 | fn generate_output_path(input_path: &Path) -> PathBuf { 65 | let parent = input_path.parent().unwrap_or(Path::new(".")); 66 | let stem = input_path 67 | .file_stem() 68 | .unwrap_or(std::ffi::OsStr::new("output")); 69 | let extension = input_path 70 | .extension() 71 | .unwrap_or(std::ffi::OsStr::new("exe")); 72 | 73 | parent.join(format!( 74 | "{}_obfuscated.{}", 75 | stem.to_string_lossy(), 76 | extension.to_string_lossy() 77 | )) 78 | } 79 | 80 | fn main() { 81 | let app = Command::new("bin-obfuscator") 82 | .version("0.1.0") 83 | .author("vasie1337") 84 | .arg(Arg::new("binary") 85 | .help("Path to the PE binary file to obfuscate") 86 | .long_help("Path to the Windows PE executable file (.exe, .dll) that will be obfuscated.\n\ 87 | The file must be a valid x86-64 PE binary.") 88 | .required(true) 89 | .value_name("BINARY_PATH") 90 | .index(1)) 91 | .arg(Arg::new("pdb") 92 | .help("Path to the corresponding PDB debug file") 93 | .long_help("Path to the Program Database (.pdb) file that contains debug information\n\ 94 | for the binary. This file is essential for the obfuscation process.") 95 | .required(true) 96 | .value_name("PDB_PATH") 97 | .index(2)) 98 | .arg(Arg::new("output") 99 | .short('o') 100 | .long("output") 101 | .help("Output path for the obfuscated binary") 102 | .long_help("Specify the output path for the obfuscated binary.\n\ 103 | If not provided, defaults to '_obfuscated.' in the same directory.") 104 | .value_name("OUTPUT_PATH")) 105 | .arg(Arg::new("verbose") 106 | .short('v') 107 | .long("verbose") 108 | .help("Enable verbose output") 109 | .long_help("Increase verbosity level. Use multiple times for more detailed output:\n\ 110 | -v: Show detailed information\n\ 111 | -vv: Show debug information\n\ 112 | -vvv: Show trace information") 113 | .action(ArgAction::Count)) 114 | .arg(Arg::new("quiet") 115 | .short('q') 116 | .long("quiet") 117 | .help("Suppress non-error output") 118 | .long_help("Run in quiet mode, only showing error messages.\n\ 119 | Cannot be used together with verbose flags.") 120 | .action(ArgAction::SetTrue) 121 | .conflicts_with("verbose")); 122 | 123 | let matches = app.get_matches(); 124 | 125 | let log_level = if matches.get_flag("quiet") { 126 | LevelFilter::Error 127 | } else { 128 | match matches.get_count("verbose") { 129 | 0 => LevelFilter::Info, 130 | 1 => LevelFilter::Debug, 131 | _ => LevelFilter::Trace, 132 | } 133 | }; 134 | 135 | Logger::ensure_init_with_level(log_level); 136 | 137 | let binary_path = Path::new(matches.get_one::("binary").unwrap()); 138 | let pdb_path = Path::new(matches.get_one::("pdb").unwrap()); 139 | 140 | let output_path = if let Some(output) = matches.get_one::("output") { 141 | PathBuf::from(output) 142 | } else { 143 | generate_output_path(binary_path) 144 | }; 145 | 146 | info!("x86-64 PE Binary Obfuscator v0.1.0"); 147 | 148 | if let Err(e) = validate_file_exists(binary_path, "Binary") { 149 | error!("{e}"); 150 | process::exit(1); 151 | } 152 | 153 | if let Err(e) = validate_file_exists(pdb_path, "PDB") { 154 | error!("{e}"); 155 | process::exit(1); 156 | } 157 | 158 | info!("Input binary: {}", binary_path.display()); 159 | info!("PDB file: {}", pdb_path.display()); 160 | 161 | info!("Loading input files..."); 162 | 163 | let pe_data = match load_file(binary_path) { 164 | Ok(data) => { 165 | info!( 166 | "Loaded binary: {:.2} MB", 167 | data.len() as f64 / 1024.0 / 1024.0 168 | ); 169 | data 170 | } 171 | Err(e) => { 172 | error!("Failed to load binary: {e}"); 173 | process::exit(1); 174 | } 175 | }; 176 | 177 | let pdb_data = match load_file(pdb_path) { 178 | Ok(data) => { 179 | info!("Loaded PDB: {:.2} MB", data.len() as f64 / 1024.0 / 1024.0); 180 | data 181 | } 182 | Err(e) => { 183 | error!("Failed to load PDB: {e}"); 184 | process::exit(1); 185 | } 186 | }; 187 | 188 | info!("Starting obfuscation process..."); 189 | 190 | let obfuscated_data = match core::run(&pe_data, &pdb_data) { 191 | Ok(data) => { 192 | info!("Obfuscation completed successfully"); 193 | data 194 | } 195 | Err(e) => { 196 | error!("Obfuscation failed: {e}"); 197 | process::exit(1); 198 | } 199 | }; 200 | 201 | info!("Saving obfuscated binary..."); 202 | 203 | if let Err(e) = save_file(&output_path, &obfuscated_data) { 204 | error!("Failed to save output: {e}"); 205 | process::exit(1); 206 | } 207 | 208 | info!( 209 | "Successfully created obfuscated binary: {}", 210 | output_path.display() 211 | ); 212 | info!( 213 | "Original size: {:.2} MB, Obfuscated size: {:.2} MB", 214 | pe_data.len() as f64 / 1024.0 / 1024.0, 215 | obfuscated_data.len() as f64 / 1024.0 / 1024.0 216 | ); 217 | } 218 | -------------------------------------------------------------------------------- /crates/core/src/pe/sections.rs: -------------------------------------------------------------------------------- 1 | use crate::pe::PEContext; 2 | 3 | impl PEContext { 4 | pub fn create_executable_section( 5 | &mut self, 6 | name: &str, 7 | bytes: &[u8], 8 | ) -> Result<(u32, u32), String> { 9 | const EXECUTABLE_CHARACTERISTICS: u32 = 0x60000020; // IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ 10 | self.create_section(name, bytes.len() as u32, EXECUTABLE_CHARACTERISTICS) 11 | .and_then(|(virtual_address, virtual_size)| { 12 | self.write_data_at_rva(virtual_address as u32, bytes) 13 | .map(|_| (virtual_address, virtual_size)) 14 | }) 15 | } 16 | 17 | pub fn get_next_section_rva(&self) -> Result { 18 | let pe = self.parse()?; 19 | 20 | if pe.sections.is_empty() { 21 | return Err("PE file has no sections".to_string()); 22 | } 23 | 24 | let section_alignment = pe 25 | .header 26 | .optional_header 27 | .ok_or("Missing optional header")? 28 | .windows_fields 29 | .section_alignment; 30 | 31 | let last_section = match pe.sections.last() { 32 | Some(section) => section, 33 | None => return Err("PE file has no sections".to_string()), 34 | }; 35 | 36 | let last_section_end = last_section.virtual_address + last_section.virtual_size; 37 | 38 | let next_rva = align_up(last_section_end, section_alignment); 39 | 40 | Ok(next_rva) 41 | } 42 | 43 | fn create_section( 44 | &mut self, 45 | name: &str, 46 | size: u32, 47 | characteristics: u32, 48 | ) -> Result<(u32, u32), String> { 49 | if name.len() > 8 { 50 | return Err("Section name cannot be longer than 8 characters".to_string()); 51 | } 52 | 53 | let ( 54 | section_alignment, 55 | file_alignment, 56 | last_section_info, 57 | num_sections, 58 | optional_header_size, 59 | nt_headers_offset, 60 | ) = { 61 | let pe = self.parse()?; 62 | 63 | if pe.sections.is_empty() { 64 | return Err("PE file has no sections".to_string()); 65 | } 66 | 67 | let optional_header = pe.header.optional_header.ok_or("Missing optional header")?; 68 | 69 | let section_alignment = optional_header.windows_fields.section_alignment; 70 | let file_alignment = optional_header.windows_fields.file_alignment; 71 | 72 | let last_section = match pe.sections.last() { 73 | Some(section) => section, 74 | None => return Err("PE file has no sections".to_string()), 75 | }; 76 | 77 | let last_section_info = ( 78 | last_section.virtual_address, 79 | last_section.virtual_size, 80 | last_section.pointer_to_raw_data, 81 | last_section.size_of_raw_data, 82 | ); 83 | let num_sections = pe.sections.len(); 84 | let optional_header_size = pe.header.coff_header.size_of_optional_header as usize; 85 | let nt_headers_offset = self.get_nt_headers_offset()?; 86 | 87 | ( 88 | section_alignment, 89 | file_alignment, 90 | last_section_info, 91 | num_sections, 92 | optional_header_size, 93 | nt_headers_offset, 94 | ) 95 | }; 96 | 97 | let ( 98 | last_virtual_address, 99 | last_virtual_size, 100 | last_pointer_to_raw_data, 101 | last_size_of_raw_data, 102 | ) = last_section_info; 103 | 104 | let virtual_size = size; 105 | let virtual_address = align_up(last_virtual_address + last_virtual_size, section_alignment); 106 | let size_of_raw_data = align_up(size, file_alignment); 107 | let pointer_to_raw_data = align_up( 108 | last_pointer_to_raw_data + last_size_of_raw_data, 109 | file_alignment, 110 | ); 111 | let new_image_size = align_up(virtual_address + virtual_size, section_alignment); 112 | 113 | let new_buffer_size = pointer_to_raw_data + size_of_raw_data; 114 | if self.pe_data.len() < new_buffer_size as usize { 115 | self.pe_data.resize(new_buffer_size as usize, 0); 116 | } 117 | 118 | let section_headers_offset = nt_headers_offset + 4 + 20 + optional_header_size; // NT signature + COFF header + optional header 119 | let section_header_size = 40; 120 | 121 | let new_section_offset = section_headers_offset + num_sections * section_header_size; 122 | 123 | if new_section_offset + section_header_size > pointer_to_raw_data as usize { 124 | return Err("Not enough space for new section header".to_string()); 125 | } 126 | 127 | let section_header = Self::create_section_header( 128 | name, 129 | virtual_size, 130 | virtual_address, 131 | size_of_raw_data, 132 | pointer_to_raw_data, 133 | characteristics, 134 | ); 135 | 136 | // Write the section header 137 | self.pe_data[new_section_offset..new_section_offset + section_header_size] 138 | .copy_from_slice(§ion_header); 139 | 140 | // Update number of sections in COFF header 141 | let num_sections_offset = nt_headers_offset + 4 + 2; // NT signature + machine field 142 | let new_num_sections = (num_sections + 1) as u16; 143 | self.pe_data[num_sections_offset..num_sections_offset + 2] 144 | .copy_from_slice(&new_num_sections.to_le_bytes()); 145 | 146 | // Update size of image in optional header 147 | let size_of_image_offset = nt_headers_offset + 4 + 20 + 56; // NT + COFF + offset to SizeOfImage 148 | self.pe_data[size_of_image_offset..size_of_image_offset + 4] 149 | .copy_from_slice(&new_image_size.to_le_bytes()); 150 | 151 | Ok((virtual_address, virtual_size)) 152 | } 153 | 154 | fn get_nt_headers_offset(&self) -> Result { 155 | if self.pe_data.len() < 64 { 156 | return Err("PE file too small to contain DOS header".to_string()); 157 | } 158 | 159 | let nt_headers_offset = u32::from_le_bytes([ 160 | self.pe_data[60], 161 | self.pe_data[61], 162 | self.pe_data[62], 163 | self.pe_data[63], 164 | ]) as usize; 165 | 166 | if nt_headers_offset >= self.pe_data.len() { 167 | return Err("Invalid NT headers offset".to_string()); 168 | } 169 | 170 | Ok(nt_headers_offset) 171 | } 172 | 173 | fn create_section_header( 174 | name: &str, 175 | virtual_size: u32, 176 | virtual_address: u32, 177 | size_of_raw_data: u32, 178 | pointer_to_raw_data: u32, 179 | characteristics: u32, 180 | ) -> [u8; 40] { 181 | let mut section_header = [0u8; 40]; 182 | 183 | // Section name (8 bytes) 184 | let name_bytes = name.as_bytes(); 185 | section_header[..name_bytes.len()].copy_from_slice(name_bytes); 186 | 187 | // Virtual size (4 bytes at offset 8) 188 | section_header[8..12].copy_from_slice(&virtual_size.to_le_bytes()); 189 | 190 | // Virtual address (4 bytes at offset 12) 191 | section_header[12..16].copy_from_slice(&virtual_address.to_le_bytes()); 192 | 193 | // Size of raw data (4 bytes at offset 16) 194 | section_header[16..20].copy_from_slice(&size_of_raw_data.to_le_bytes()); 195 | 196 | // Pointer to raw data (4 bytes at offset 20) 197 | section_header[20..24].copy_from_slice(&pointer_to_raw_data.to_le_bytes()); 198 | 199 | // Pointer to relocations (4 bytes at offset 24) - unused, set to 0 200 | section_header[24..28].copy_from_slice(&0u32.to_le_bytes()); 201 | 202 | // Pointer to line numbers (4 bytes at offset 28) - unused, set to 0 203 | section_header[28..32].copy_from_slice(&0u32.to_le_bytes()); 204 | 205 | // Number of relocations (2 bytes at offset 32) - unused, set to 0 206 | section_header[32..34].copy_from_slice(&0u16.to_le_bytes()); 207 | 208 | // Number of line numbers (2 bytes at offset 34) - unused, set to 0 209 | section_header[34..36].copy_from_slice(&0u16.to_le_bytes()); 210 | 211 | // Characteristics (4 bytes at offset 36) 212 | section_header[36..40].copy_from_slice(&characteristics.to_le_bytes()); 213 | 214 | section_header 215 | } 216 | } 217 | 218 | fn align_up(value: u32, alignment: u32) -> u32 { 219 | (value + alignment - 1) & !(alignment - 1) 220 | } 221 | -------------------------------------------------------------------------------- /crates/core/src/passes/mutation.rs: -------------------------------------------------------------------------------- 1 | use super::Pass; 2 | use crate::function::ObfuscatorFunction; 3 | use crate::instruction::InstructionWithId; 4 | use iced_x86::{Code, Instruction, OpKind}; 5 | use rand::Rng; 6 | 7 | pub struct MutationPass; 8 | 9 | impl MutationPass { 10 | pub fn new() -> Self { 11 | Self 12 | } 13 | 14 | fn create_instruction(&self, context: &crate::instruction::InstructionContext, instruction: Instruction) -> Option { 15 | let instruction = InstructionWithId { 16 | id: context.next_id(), 17 | instruction, 18 | }; 19 | 20 | instruction.re_encode(0).ok().map(|instruction| InstructionWithId { 21 | id: context.next_id(), 22 | instruction, 23 | }) 24 | } 25 | 26 | fn mutate_lea(&self, instruction: &InstructionWithId, context: &crate::instruction::InstructionContext) -> Vec { 27 | let mut result = Vec::new(); 28 | 29 | if instruction.instruction.memory_displ_size() != 0 { 30 | let dest_reg = instruction.instruction.op0_register(); 31 | let random_value = rand::rng().random_range(0..=i16::MAX) as i32; 32 | 33 | let displacement = instruction.instruction.memory_displacement64(); 34 | let mut new_instruction = instruction.clone(); 35 | new_instruction.instruction.set_memory_displacement64(displacement + random_value as u64); 36 | result.push(new_instruction); 37 | 38 | if let Some(pushf_instr) = self.create_instruction( 39 | context, 40 | Instruction::with(Code::Pushfq), 41 | ) { 42 | result.push(pushf_instr); 43 | } 44 | 45 | if let Some(sub_instr) = self.create_instruction( 46 | context, 47 | Instruction::with2(Code::Sub_rm64_imm32, dest_reg, random_value).unwrap(), 48 | ) { 49 | result.push(sub_instr); 50 | } 51 | 52 | if let Some(popfq_instr) = self.create_instruction( 53 | context, 54 | Instruction::with(Code::Popfq), 55 | ) { 56 | result.push(popfq_instr); 57 | } 58 | } else { 59 | result.push(instruction.clone()); 60 | } 61 | 62 | result 63 | } 64 | 65 | fn mutate_call(&self, instruction: &InstructionWithId, _context: &crate::instruction::InstructionContext) -> Vec { 66 | let mut result = Vec::new(); 67 | 68 | result.push(instruction.clone()); 69 | 70 | result 71 | } 72 | 73 | fn mutate_add(&self, instruction: &InstructionWithId, context: &crate::instruction::InstructionContext) -> Vec { 74 | let mut result = Vec::new(); 75 | let op_kinds: Vec = instruction.instruction.op_kinds().collect(); 76 | 77 | match (op_kinds[0], op_kinds[1]) { 78 | (OpKind::Register, OpKind::Register) => { 79 | let dest_reg = instruction.instruction.op0_register(); 80 | let src_reg = instruction.instruction.op1_register(); 81 | 82 | if let Some(pushf_inst) = self.create_instruction( 83 | context, 84 | Instruction::with(Code::Pushfq), 85 | ) { 86 | result.push(pushf_inst); 87 | } 88 | 89 | if let Some(clc_inst) = self.create_instruction( 90 | context, 91 | Instruction::with(Code::Clc), 92 | ) { 93 | result.push(clc_inst); 94 | } 95 | 96 | if let Some(mut adc_inst) = self.create_instruction( 97 | context, 98 | Instruction::with2(Code::Adc_r64_rm64, dest_reg, src_reg).unwrap(), 99 | ) { 100 | adc_inst.set_id(instruction.get_id()); 101 | result.push(adc_inst); 102 | } 103 | 104 | if let Some(popf_inst) = self.create_instruction( 105 | context, 106 | Instruction::with(Code::Popfq), 107 | ) { 108 | result.push(popf_inst); 109 | } 110 | } 111 | _ => { 112 | result.push(instruction.clone()); 113 | } 114 | } 115 | 116 | result 117 | } 118 | 119 | fn mutate_or(&self, instruction: &InstructionWithId, context: &crate::instruction::InstructionContext) -> Vec { 120 | let mut result = Vec::new(); 121 | let op_kinds: Vec = instruction.instruction.op_kinds().collect(); 122 | 123 | match (op_kinds[0], op_kinds[1]) { 124 | (OpKind::Register, OpKind::Register) => { 125 | let dest_reg = instruction.instruction.op0_register(); 126 | let src_reg = instruction.instruction.op1_register(); 127 | 128 | if let Some(pushf_inst) = self.create_instruction( 129 | context, 130 | Instruction::with(Code::Pushfq), 131 | ) { 132 | result.push(pushf_inst); 133 | } 134 | 135 | if let Some(mut andn_inst) = self.create_instruction( 136 | context, 137 | Instruction::with3(Code::VEX_Andn_r64_r64_rm64, dest_reg, dest_reg, src_reg).unwrap(), 138 | ) { 139 | andn_inst.set_id(instruction.get_id()); 140 | result.push(andn_inst); 141 | } 142 | 143 | if let Some(mut blsi_inst) = self.create_instruction( 144 | context, 145 | Instruction::with2(Code::VEX_Blsi_r64_rm64, dest_reg, src_reg).unwrap(), 146 | ) { 147 | blsi_inst.set_id(instruction.get_id()); 148 | result.push(blsi_inst); 149 | } 150 | 151 | if let Some(mut tzcnt_inst) = self.create_instruction( 152 | context, 153 | Instruction::with2(Code::Tzcnt_r64_rm64, dest_reg, src_reg).unwrap(), 154 | ) { 155 | tzcnt_inst.set_id(instruction.get_id()); 156 | result.push(tzcnt_inst); 157 | } 158 | 159 | if let Some(popf_inst) = self.create_instruction( 160 | context, 161 | Instruction::with(Code::Popfq), 162 | ) { 163 | result.push(popf_inst); 164 | } 165 | } 166 | _ => { 167 | result.push(instruction.clone()); 168 | } 169 | } 170 | 171 | result 172 | } 173 | 174 | fn mutate_inc(&self, instruction: &InstructionWithId, context: &crate::instruction::InstructionContext) -> Vec { 175 | let mut result = Vec::new(); 176 | let op_kinds: Vec = instruction.instruction.op_kinds().collect(); 177 | 178 | match op_kinds[0] { 179 | OpKind::Register => { 180 | let reg = instruction.instruction.op0_register(); 181 | 182 | if let Some(pushf_inst) = self.create_instruction( 183 | context, 184 | Instruction::with(Code::Pushfq), 185 | ) { 186 | result.push(pushf_inst); 187 | } 188 | 189 | if let Some(clc_inst) = self.create_instruction( 190 | context, 191 | Instruction::with(Code::Clc), 192 | ) { 193 | result.push(clc_inst); 194 | } 195 | 196 | if let Some(mut adc_inst) = self.create_instruction( 197 | context, 198 | Instruction::with2(Code::Adc_rm64_imm8, reg, 1).unwrap(), 199 | ) { 200 | adc_inst.set_id(instruction.get_id()); 201 | result.push(adc_inst); 202 | } 203 | 204 | if let Some(popf_inst) = self.create_instruction( 205 | context, 206 | Instruction::with(Code::Popfq), 207 | ) { 208 | result.push(popf_inst); 209 | } 210 | } 211 | _ => { 212 | result.push(instruction.clone()); 213 | } 214 | } 215 | 216 | result 217 | } 218 | 219 | fn mutate_dec(&self, instruction: &InstructionWithId, context: &crate::instruction::InstructionContext) -> Vec { 220 | let mut result = Vec::new(); 221 | let op_kinds: Vec = instruction.instruction.op_kinds().collect(); 222 | 223 | match op_kinds[0] { 224 | OpKind::Register => { 225 | let reg = instruction.instruction.op0_register(); 226 | 227 | if let Some(pushf_inst) = self.create_instruction( 228 | context, 229 | Instruction::with(Code::Pushfq), 230 | ) { 231 | result.push(pushf_inst); 232 | } 233 | 234 | if let Some(clc_inst) = self.create_instruction( 235 | context, 236 | Instruction::with(Code::Clc), 237 | ) { 238 | result.push(clc_inst); 239 | } 240 | 241 | if let Some(mut sbb_inst) = self.create_instruction( 242 | context, 243 | Instruction::with2(Code::Sbb_rm64_imm8, reg, 1).unwrap(), 244 | ) { 245 | sbb_inst.set_id(instruction.get_id()); 246 | result.push(sbb_inst); 247 | } 248 | 249 | if let Some(popf_inst) = self.create_instruction( 250 | context, 251 | Instruction::with(Code::Popfq), 252 | ) { 253 | result.push(popf_inst); 254 | } 255 | } 256 | _ => { 257 | result.push(instruction.clone()); 258 | } 259 | } 260 | 261 | result 262 | } 263 | 264 | fn mutate_push(&self, instruction: &InstructionWithId, context: &crate::instruction::InstructionContext) -> Vec { 265 | let mut result = Vec::new(); 266 | let op_kinds: Vec = instruction.instruction.op_kinds().collect(); 267 | 268 | match op_kinds[0] { 269 | OpKind::Register => { 270 | let reg = instruction.instruction.op0_register(); 271 | 272 | if let Some(mut mov_inst) = self.create_instruction( 273 | context, 274 | Instruction::with2(Code::Mov_rm64_r64, iced_x86::MemoryOperand::new(iced_x86::Register::RSP, iced_x86::Register::None, 1, -8, 8, false, iced_x86::Register::None), reg).unwrap(), 275 | ) { 276 | mov_inst.set_id(instruction.get_id()); 277 | result.push(mov_inst); 278 | } 279 | 280 | if let Some(mut sub_inst) = self.create_instruction( 281 | context, 282 | Instruction::with2(Code::Sub_rm64_imm8, iced_x86::Register::RSP, 8).unwrap(), 283 | ) { 284 | sub_inst.set_id(instruction.get_id()); 285 | result.push(sub_inst); 286 | } 287 | } 288 | _ => { 289 | result.push(instruction.clone()); 290 | } 291 | } 292 | 293 | result 294 | } 295 | } 296 | 297 | impl Pass for MutationPass { 298 | fn name(&self) -> &'static str { 299 | "Mutation" 300 | } 301 | 302 | fn apply(&self, function: &mut ObfuscatorFunction) -> Result<(), String> { 303 | let mut result = Vec::with_capacity(function.instructions.len() * 3); 304 | 305 | for instruction in function.instructions.iter() { 306 | match instruction.instruction.code() { 307 | Code::Lea_r64_m => { 308 | let mut mutated = self.mutate_lea(instruction, &function.instruction_context); 309 | result.append(&mut mutated); 310 | } 311 | Code::Call_rm64 => { 312 | let mut mutated = self.mutate_call(instruction, &function.instruction_context); 313 | result.append(&mut mutated); 314 | } 315 | Code::Add_r64_rm64 | Code::Add_rm64_r64 => { 316 | let mut mutated = self.mutate_add(instruction, &function.instruction_context); 317 | result.append(&mut mutated); 318 | } 319 | Code::Or_r64_rm64 | Code::Or_rm64_r64 => { 320 | let mut mutated = self.mutate_or(instruction, &function.instruction_context); 321 | result.append(&mut mutated); 322 | } 323 | Code::Inc_rm64 => { 324 | let mut mutated = self.mutate_inc(instruction, &function.instruction_context); 325 | result.append(&mut mutated); 326 | } 327 | Code::Dec_rm64 => { 328 | let mut mutated = self.mutate_dec(instruction, &function.instruction_context); 329 | result.append(&mut mutated); 330 | } 331 | Code::Push_r64 => { 332 | let mut mutated = self.mutate_push(instruction, &function.instruction_context); 333 | result.append(&mut mutated); 334 | } 335 | _ => { 336 | result.push(instruction.clone()); 337 | } 338 | } 339 | } 340 | 341 | function.instructions = result; 342 | Ok(()) 343 | } 344 | } 345 | 346 | impl Default for MutationPass { 347 | fn default() -> Self { 348 | Self::new() 349 | } 350 | } 351 | --------------------------------------------------------------------------------