├── .gitignore ├── reloader ├── src │ ├── helpers.rs │ ├── error.rs │ ├── syscall.rs │ ├── helpers │ │ ├── syscall.rs │ │ ├── library.rs │ │ └── general.rs │ ├── function_wrappers.rs │ └── lib.rs └── Cargo.toml ├── objparse ├── Cargo.toml └── src │ ├── error.rs │ └── lib.rs ├── .rustfmt.toml └── Cargo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /reloader/src/helpers.rs: -------------------------------------------------------------------------------- 1 | pub mod general; 2 | pub mod library; 3 | pub mod syscall; 4 | -------------------------------------------------------------------------------- /objparse/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "objparse" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = ["debug"] 8 | debug = [] 9 | 10 | [dependencies] 11 | thiserror = { workspace = true } 12 | object = { workspace = true } 13 | windows-sys = { workspace = true } 14 | -------------------------------------------------------------------------------- /reloader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reloader" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [features] 7 | default = ["debug"] 8 | debug = ["objparse/debug"] 9 | 10 | [dependencies] 11 | objparse = { path = "../objparse" } 12 | thiserror = { workspace = true } 13 | object = { workspace = true } 14 | windows-sys = { workspace = true } 15 | phnt = { workspace = true } 16 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | fn_single_line = true 3 | hard_tabs = true 4 | trailing_semicolon = true 5 | use_field_init_shorthand = true 6 | use_try_shorthand = true 7 | struct_lit_single_line = true 8 | condense_wildcard_suffixes = true 9 | control_brace_style = "ClosingNextLine" 10 | overflow_delimited_expr = false 11 | use_small_heuristics = "Default" 12 | imports_granularity = "Crate" -------------------------------------------------------------------------------- /objparse/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | pub type Result = core::result::Result; 4 | 5 | #[repr(u16)] 6 | #[derive(Debug, Error)] 7 | pub enum Error { 8 | #[error("PE headers")] 9 | PeHeaders, 10 | #[error("Export table")] 11 | ExportTable, 12 | #[error("Import table")] 13 | ImportTable, 14 | #[error("Debug table")] 15 | DebugTable, 16 | #[error("TLS table")] 17 | TlsTable, 18 | } 19 | -------------------------------------------------------------------------------- /reloader/src/error.rs: -------------------------------------------------------------------------------- 1 | pub type Result = core::result::Result; 2 | 3 | #[repr(u16)] 4 | #[derive(Debug)] 5 | pub enum Error { 6 | ObjParse, 7 | SelfFind, 8 | ModuleByHash, 9 | ModuleByAscii, 10 | ExportVaByHash, 11 | ExportVaByAscii, 12 | Allocation, 13 | Protect, 14 | Flush, 15 | RelocationType, 16 | SplitString, 17 | ParseNumber, 18 | SyscallNumber, 19 | LdrLoadDll, 20 | } 21 | 22 | impl From for Error { 23 | fn from(_value: objparse::error::Error) -> Self { Self::ObjParse } 24 | } 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["reloader", "objparse"] 3 | resolver = "3" 4 | 5 | [workspace.dependencies] 6 | object = { version = "0.36.7", default-features = false, features = [ 7 | "read", 8 | "pe", 9 | ] } 10 | thiserror = "2.0.12" 11 | pelite = { version = "0.10.0", default-features = false } 12 | windows-core = "0.61.0" 13 | phnt = { version = "0.1.2", features = ["regenerate"] } 14 | 15 | [workspace.dependencies.windows-sys] 16 | version = "0.59.0" 17 | features = [ 18 | "Win32_Foundation", 19 | "Win32_System_Memory", 20 | "Win32_System_SystemServices", 21 | "Win32_System_Kernel", 22 | "Win32_System_Threading", 23 | ] 24 | -------------------------------------------------------------------------------- /reloader/src/syscall.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | #[cfg_attr(feature = "debug", inline(never))] 4 | pub unsafe fn syscall3(number: u32, arg1: u64, arg2: u64, arg3: u64) -> u64 { 5 | let output: u64; 6 | unsafe { 7 | asm!( 8 | "syscall", 9 | in("rax") number, 10 | in("r10") arg1, 11 | in("rdx") arg2, 12 | in("r8") arg3, 13 | lateout("rax") output, 14 | lateout("rcx") _, 15 | lateout("r11") _ 16 | ) 17 | }; 18 | output 19 | } 20 | 21 | #[cfg_attr(feature = "debug", inline(never))] 22 | pub unsafe fn syscall5(number: u32, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) -> u64 { 23 | let output: u64; 24 | unsafe { 25 | asm!( 26 | "sub rsp, 0x38", 27 | "mov [rsp+0x28], {arg5}", 28 | "syscall", 29 | "add rsp, 0x38", 30 | arg5 = in(reg) arg5, 31 | in("rax") number, 32 | in("r10") arg1, 33 | in("rdx") arg2, 34 | in("r8") arg3, 35 | in("r9") arg4, 36 | lateout("rax") output, 37 | lateout("rcx") _, 38 | lateout("r11") _ 39 | ) 40 | }; 41 | output 42 | } 43 | 44 | #[cfg_attr(feature = "debug", inline(never))] 45 | pub unsafe fn syscall6( 46 | number: u32, 47 | arg1: u64, 48 | arg2: u64, 49 | arg3: u64, 50 | arg4: u64, 51 | arg5: u64, 52 | arg6: u64, 53 | ) -> u64 { 54 | let output: u64; 55 | unsafe { 56 | asm!( 57 | "sub rsp, 0x38", 58 | "mov [rsp+0x30], {arg6}", 59 | "mov [rsp+0x28], {arg5}", 60 | "syscall", 61 | "add rsp, 0x38", 62 | arg5 = in(reg) arg5, 63 | arg6 = in(reg) arg6, 64 | in("rax") number, 65 | in("r10") arg1, 66 | in("rdx") arg2, 67 | in("r8") arg3, 68 | in("r9") arg4, 69 | lateout("rax") output, 70 | lateout("rcx") _, 71 | lateout("r11") _ 72 | ) 73 | }; 74 | output 75 | } 76 | -------------------------------------------------------------------------------- /reloader/src/helpers/syscall.rs: -------------------------------------------------------------------------------- 1 | use objparse::ExportTable; 2 | 3 | use crate::{ 4 | error::{Error, Result}, 5 | helpers::general::fnv1a_hash_32, 6 | }; 7 | use core::mem::MaybeUninit; 8 | 9 | pub const SYSCALL_TABLE_SIZE: usize = 512; 10 | 11 | #[cfg_attr(feature = "debug", inline(never))] 12 | pub fn gen_syscall_table( 13 | exports: &ExportTable, 14 | base: *mut u8, 15 | output: &mut [u32; SYSCALL_TABLE_SIZE], 16 | ) -> usize { 17 | let mut scratch_table = [MaybeUninit::<(u32, *mut u8)>::uninit(); SYSCALL_TABLE_SIZE]; 18 | let mut num_syscalls = 0; 19 | 20 | // Iterate through exports which match the names of syscalls 21 | unsafe { 22 | exports 23 | .iter_string_addr(base) 24 | .filter(|(name, _)| { 25 | // Our condition is - name must start with zW 26 | let name = name.to_bytes(); 27 | let name_0 = match name.first() { 28 | Some(&x) => x, 29 | None => return false, 30 | }; 31 | let name_1 = match name.get(1) { 32 | Some(&x) => x, 33 | None => return false, 34 | }; 35 | if name_0 != b'Z' { 36 | return false; 37 | } 38 | if name_1 != b'w' { 39 | return false; 40 | } 41 | true 42 | }) 43 | .enumerate() 44 | .for_each(|(n, (name, addr))| { 45 | // Turn each function name into a hash 46 | let name_hash = fnv1a_hash_32(name.to_bytes()); 47 | 48 | scratch_table.get_unchecked_mut(n).write((name_hash, addr)); 49 | 50 | num_syscalls += 1; 51 | }) 52 | }; 53 | 54 | let working_slice = 55 | unsafe { <[_]>::assume_init_mut(scratch_table.get_unchecked_mut(..num_syscalls)) }; 56 | // Sort the filled entries by address 57 | working_slice.sort_unstable_by_key(|(_, addr)| *addr); 58 | 59 | // Copy hashes over to output slice 60 | for i in 0..num_syscalls { 61 | unsafe { *output.get_unchecked_mut(i) = working_slice.get_unchecked(i).0 }; 62 | } 63 | num_syscalls 64 | } 65 | 66 | #[cfg_attr(feature = "debug", inline(never))] 67 | pub fn find_syscall_by_hash(table: &[u32; SYSCALL_TABLE_SIZE], hash: u32) -> Result { 68 | table 69 | .iter() 70 | .position(|&table_hash| table_hash == hash) 71 | .map(|x| x as u32) 72 | .ok_or(Error::SyscallNumber) 73 | } 74 | -------------------------------------------------------------------------------- /reloader/src/function_wrappers.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | SyscallNumbers, 3 | error::{Error, Result}, 4 | syscall::{syscall3, syscall5, syscall6}, 5 | }; 6 | use core::ptr::null_mut; 7 | use phnt::ffi::UNICODE_STRING; 8 | use windows_sys::Win32::{ 9 | Foundation::STATUS_SUCCESS, 10 | System::Memory::{MEM_COMMIT, MEM_RESERVE, PAGE_PROTECTION_FLAGS, PAGE_READWRITE}, 11 | }; 12 | 13 | #[cfg_attr(feature = "debug", inline(never))] 14 | pub fn allocate_memory(syscall_numbers: &SyscallNumbers, size: usize) -> Result<*mut u8> { 15 | let mut allocated_ptr = null_mut::(); 16 | let mut region_size = size; 17 | 18 | let nt_status = unsafe { 19 | syscall6( 20 | syscall_numbers.sys_no_zwallocatevirtualmemory, 21 | -1i64 as _, 22 | &mut allocated_ptr as *mut _ as _, 23 | 0, 24 | &mut region_size as *mut _ as _, 25 | (MEM_RESERVE | MEM_COMMIT) as _, 26 | PAGE_READWRITE as _, 27 | ) 28 | }; 29 | if nt_status != STATUS_SUCCESS as _ { 30 | return Err(Error::Allocation); 31 | } 32 | 33 | if allocated_ptr.is_null() { 34 | return Err(Error::Allocation); 35 | } 36 | Ok(allocated_ptr) 37 | } 38 | 39 | #[cfg_attr(feature = "debug", inline(never))] 40 | pub fn protect_memory( 41 | syscall_numbers: &SyscallNumbers, 42 | base: *mut u8, 43 | size: usize, 44 | protection: PAGE_PROTECTION_FLAGS, 45 | ) -> Result { 46 | let base_address = base; 47 | let mut region_size = size; 48 | let mut old_permissions = u32::default(); 49 | let nt_status = unsafe { 50 | syscall5( 51 | syscall_numbers.sys_no_zwprotectvirtualmemory, 52 | -1i64 as _, 53 | &base_address as *const _ as _, 54 | &mut region_size as *mut _ as _, 55 | protection as _, 56 | &mut old_permissions as *mut _ as _, 57 | ) 58 | }; 59 | if nt_status != STATUS_SUCCESS as _ { 60 | return Err(Error::Protect); 61 | } 62 | Ok(old_permissions) 63 | } 64 | 65 | #[cfg_attr(feature = "debug", inline(never))] 66 | pub fn flush_instruction_cache(syscall_numbers: &SyscallNumbers) -> Result<()> { 67 | let nt_status = unsafe { 68 | syscall3( 69 | syscall_numbers.sys_no_zwflushinstructioncache, 70 | -1i64 as _, 71 | 0, 72 | 0, 73 | ) 74 | }; 75 | if nt_status != STATUS_SUCCESS as _ { 76 | return Err(Error::Flush); 77 | } 78 | Ok(()) 79 | } 80 | 81 | #[cfg_attr(feature = "debug", inline(never))] 82 | pub fn load_dll( 83 | ldrloaddll: unsafe extern "system" fn( 84 | *const u16, 85 | *const u32, 86 | *const UNICODE_STRING, 87 | *mut *mut u8, 88 | ) -> i32, 89 | unicode_string: *const UNICODE_STRING, 90 | ) -> Result<*mut u8> { 91 | let mut module_handle = null_mut::(); 92 | unsafe { ldrloaddll(null_mut(), null_mut(), unicode_string, &mut module_handle) }; 93 | if module_handle.is_null() { 94 | return Err(Error::LdrLoadDll); 95 | } 96 | Ok(module_handle) 97 | } 98 | -------------------------------------------------------------------------------- /reloader/src/helpers/library.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | LoaderContext, 3 | error::{Error, Result}, 4 | function_wrappers::load_dll, 5 | helpers::general::{ 6 | LinkedListPointer, ascii_ascii_eq, ascii_wstr_eq, fnv1a_hash_32, fnv1a_hash_32_wstr, 7 | }, 8 | }; 9 | use core::{ 10 | ffi::CStr, 11 | mem::{MaybeUninit, transmute}, 12 | ptr::addr_of_mut, 13 | }; 14 | use objparse::ExportTable; 15 | use phnt::ffi::{LDR_DATA_TABLE_ENTRY, PEB_LDR_DATA, UNICODE_STRING, UNICODE_STRING64}; 16 | 17 | const LIBRARY_CONVERSION_BUFFER_SIZE: usize = 64; 18 | 19 | #[cfg_attr(feature = "debug", inline(never))] 20 | pub fn get_library_base( 21 | peb_ldr: *mut PEB_LDR_DATA, 22 | library_name: *const u8, 23 | 24 | context: &LoaderContext, 25 | ) -> Result<*mut u8> { 26 | let loaded_library_base = match find_loaded_module_by_ascii(peb_ldr, library_name as _) { 27 | Ok(base) => base, 28 | Err(_) => { 29 | let name_ascii = unsafe { CStr::from_ptr(library_name as _) }; 30 | 31 | let mut buffer_space = [MaybeUninit::uninit(); LIBRARY_CONVERSION_BUFFER_SIZE]; 32 | buffer_space 33 | .iter_mut() 34 | .zip(name_ascii.to_bytes_with_nul().iter()) 35 | .for_each(|(wchar, &ascii)| { 36 | wchar.write(ascii as u16); 37 | }); 38 | 39 | let unicode_string = UNICODE_STRING64 { 40 | Length: (name_ascii.to_bytes().len() * 2) as _, 41 | MaximumLength: (name_ascii.to_bytes_with_nul().len() * 2) as _, 42 | Buffer: MaybeUninit::slice_as_ptr(&buffer_space) as _, 43 | }; 44 | 45 | let unicode_string = 46 | unsafe { transmute::(unicode_string) }; 47 | 48 | // Now load the library 49 | load_dll(context.ldr_load_dll, &raw const unicode_string)? 50 | } 51 | }; 52 | if loaded_library_base.is_null() { 53 | return Err(Error::LdrLoadDll); 54 | } 55 | Ok(loaded_library_base) 56 | } 57 | 58 | #[cfg_attr(feature = "debug", inline(never))] 59 | pub fn find_loaded_module_by_hash(ldr: *mut PEB_LDR_DATA, hash: u32) -> Result<*mut u8> { 60 | // Get list head 61 | let head = unsafe { addr_of_mut!((*ldr).InLoadOrderModuleList) }; 62 | // Get initial entry 63 | let first = unsafe { (*head).Flink }; 64 | 65 | let mut iter = LinkedListPointer::new(first); 66 | 67 | while let Some(entry) = iter.next_until(head) { 68 | let ldr_data_ptr = entry as *mut LDR_DATA_TABLE_ENTRY; 69 | let ldr_data = unsafe { &*ldr_data_ptr }; 70 | 71 | // Make a slice of wchars from the base name 72 | let dll_name = &ldr_data.BaseDllName; 73 | 74 | let dll_name_wstr = dll_name.as_slice(); 75 | 76 | if fnv1a_hash_32_wstr(dll_name_wstr) == hash { 77 | // Return the base address for this DLL 78 | return Ok(ldr_data.DllBase as _); 79 | } 80 | } 81 | Err(Error::ModuleByHash) 82 | } 83 | 84 | #[cfg_attr(feature = "debug", inline(never))] 85 | pub fn find_loaded_module_by_ascii(ldr: *mut PEB_LDR_DATA, ascii: *const i8) -> Result<*mut u8> { 86 | let ascii = unsafe { CStr::from_ptr(ascii) }; 87 | 88 | // Get list head 89 | let head = unsafe { addr_of_mut!((*ldr).InLoadOrderModuleList) }; 90 | // Get initial entry 91 | let first = unsafe { (*head).Flink }; 92 | 93 | let mut iter = LinkedListPointer::new(first); 94 | 95 | while let Some(entry) = iter.next_until(head) { 96 | let ldr_data_ptr = entry as *mut LDR_DATA_TABLE_ENTRY; 97 | 98 | let ldr_data = unsafe { &*ldr_data_ptr }; 99 | 100 | // Make a slice of wchars from the base name 101 | let dll_name = &ldr_data.BaseDllName; 102 | 103 | let dll_name_wstr = dll_name.as_slice(); 104 | 105 | if ascii_wstr_eq(ascii, dll_name_wstr) { 106 | // Return the base address for this DLL 107 | return Ok(ldr_data.DllBase as _); 108 | } 109 | } 110 | Err(Error::ModuleByAscii) 111 | } 112 | 113 | #[cfg_attr(feature = "debug", inline(never))] 114 | pub fn find_export_by_hash(exports: &ExportTable, base: *mut u8, hash: u32) -> Result<*mut u8> { 115 | unsafe { 116 | exports 117 | .iter_string_addr(base) 118 | .find(|(name, _)| fnv1a_hash_32(name.to_bytes()) == hash) 119 | .map(|(_, addr)| addr) 120 | .ok_or(Error::ExportVaByHash) 121 | } 122 | } 123 | 124 | #[cfg_attr(feature = "debug", inline(never))] 125 | pub fn find_export_by_ascii( 126 | exports: &ExportTable, 127 | base: *mut u8, 128 | string: &CStr, 129 | ) -> Result<*mut u8> { 130 | unsafe { 131 | exports 132 | .iter_string_addr(base) 133 | .find(|(name, _)| ascii_ascii_eq(name.to_bytes(), string.to_bytes())) 134 | .map(|(_, addr)| addr) 135 | .ok_or(Error::ExportVaByAscii) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /reloader/src/helpers/general.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use core::{ 3 | arch::asm, 4 | ffi::CStr, 5 | mem::{MaybeUninit, transmute}, 6 | ptr, 7 | ptr::null_mut, 8 | sync::atomic::{Ordering, compiler_fence}, 9 | }; 10 | use objparse::PeHeaders; 11 | use phnt::ffi::{LIST_ENTRY, TEB}; 12 | 13 | pub struct LinkedListPointer(*mut LIST_ENTRY); 14 | 15 | impl LinkedListPointer { 16 | pub fn new(start: *mut LIST_ENTRY) -> Self { Self(start) } 17 | 18 | pub fn next(&mut self) -> *mut LIST_ENTRY { 19 | let cur = self.0; 20 | self.0 = unsafe { (*self.0).Flink }; 21 | cur 22 | } 23 | 24 | pub fn prev(&mut self) -> *mut LIST_ENTRY { 25 | let cur = self.0; 26 | self.0 = unsafe { (*self.0).Blink }; 27 | cur 28 | } 29 | 30 | pub fn next_until(&mut self, finish: *mut LIST_ENTRY) -> Option<*mut LIST_ENTRY> { 31 | if self.0.is_null() { 32 | return None; 33 | } 34 | let cur = self.0; 35 | let next = unsafe { (*self.0).Flink }; 36 | if ptr::eq(next, finish) { 37 | self.0 = null_mut(); 38 | } 39 | else { 40 | self.0 = next; 41 | } 42 | Some(cur) 43 | } 44 | 45 | pub fn prev_until(&mut self, finish: *mut LIST_ENTRY) -> Option<*mut LIST_ENTRY> { 46 | if self.0.is_null() { 47 | return None; 48 | } 49 | let cur = self.0; 50 | let prev = unsafe { (*self.0).Blink }; 51 | if ptr::eq(prev, finish) { 52 | self.0 = null_mut(); 53 | } 54 | else { 55 | self.0 = prev; 56 | } 57 | Some(cur) 58 | } 59 | } 60 | 61 | #[cfg_attr(feature = "debug", inline(never))] 62 | pub const fn fnv1a_hash_32(chars: &[u8]) -> u32 { 63 | const FNV_OFFSET_BASIS_32: u32 = 0x811c9dc5; 64 | const FNV_PRIME_32: u32 = 0x01000193; 65 | 66 | let mut hash = FNV_OFFSET_BASIS_32; 67 | 68 | let mut i = 0; 69 | while i < chars.len() { 70 | let c = unsafe { char::from_u32_unchecked(chars[i] as u32).to_ascii_lowercase() }; 71 | hash ^= c as u32; 72 | hash = hash.wrapping_mul(FNV_PRIME_32); 73 | i += 1; 74 | } 75 | hash 76 | } 77 | 78 | #[cfg_attr(feature = "debug", inline(never))] 79 | pub const fn fnv1a_hash_32_wstr(wchars: &[u16]) -> u32 { 80 | const FNV_OFFSET_BASIS_32: u32 = 0x811c9dc5; 81 | const FNV_PRIME_32: u32 = 0x01000193; 82 | 83 | let mut hash = FNV_OFFSET_BASIS_32; 84 | 85 | let mut i = 0; 86 | while i < wchars.len() { 87 | let c = unsafe { char::from_u32_unchecked(wchars[i] as u32).to_ascii_lowercase() }; 88 | hash ^= c as u32; 89 | hash = hash.wrapping_mul(FNV_PRIME_32); 90 | i += 1; 91 | } 92 | hash 93 | } 94 | 95 | #[cfg_attr(feature = "debug", inline(never))] 96 | pub fn simple_memcpy(dest: *mut u8, src: *mut u8, len: usize) { 97 | let n_bytes = len; // Iterate backwards to avoid optimizing..? 98 | for i in (0..n_bytes).rev() { 99 | compiler_fence(Ordering::Acquire); 100 | unsafe { *dest.add(i) = *src.add(i) }; 101 | } 102 | } 103 | 104 | #[cfg_attr(feature = "debug", inline(never))] 105 | pub fn memset_uninit_array( 106 | dest: &mut [MaybeUninit; L], 107 | value: T, 108 | ) -> &mut [T; L] { 109 | let n_bytes = dest.len(); // Iterate backwards to avoid optimizing..? 110 | for i in (0..n_bytes).rev() { 111 | compiler_fence(Ordering::Acquire); 112 | unsafe { dest.get_unchecked_mut(i).write(value) }; 113 | } 114 | unsafe { transmute::<_, &mut [T; L]>(dest) } 115 | } 116 | 117 | #[cfg_attr(feature = "debug", inline(never))] 118 | pub fn ascii_wstr_eq(ascii: &CStr, wstr: &[u16]) -> bool { 119 | // Check if the lengths are equal 120 | if wstr.len() != ascii.to_bytes().len() { 121 | return false; 122 | } 123 | 124 | // Check if they are equal 125 | if wstr 126 | .iter() 127 | .copied() 128 | .zip(ascii.to_bytes().iter().copied()) 129 | .map(|(a, b)| ((a as u8).to_ascii_lowercase(), b.to_ascii_lowercase())) 130 | .any(|(a, b)| a != b) 131 | { 132 | return false; 133 | } 134 | true 135 | } 136 | 137 | #[cfg_attr(feature = "debug", inline(never))] 138 | pub fn ascii_ascii_eq(a: &[u8], b: &[u8]) -> bool { 139 | // Check if the lengths are equal 140 | if a.len() != b.len() { 141 | return false; 142 | } 143 | 144 | // Check if they are equal 145 | if a.iter() 146 | .copied() 147 | .zip(b.iter().copied()) 148 | .map(|(a, b)| (a.to_ascii_lowercase(), b.to_ascii_lowercase())) 149 | .any(|(a, b)| a != b) 150 | { 151 | return false; 152 | } 153 | true 154 | } 155 | 156 | #[cfg_attr(feature = "debug", inline(never))] 157 | pub fn get_ip() -> usize { 158 | let rip: usize; 159 | #[cfg(target_arch = "x86_64")] 160 | unsafe { 161 | asm!("lea {rip}, [rip]", rip = out(reg) rip) 162 | }; 163 | #[cfg(target_arch = "x86")] 164 | unsafe { 165 | asm!("lea {eip}, [eip]", rip = out(reg) rip) 166 | }; 167 | rip 168 | } 169 | 170 | #[cfg_attr(feature = "debug", inline(never))] 171 | pub fn get_teb() -> *mut TEB { 172 | let teb: *mut TEB; 173 | unsafe { 174 | #[cfg(target_arch = "x86_64")] 175 | asm!("mov {teb}, gs:[0x30]", teb = out(reg) teb); 176 | #[cfg(target_arch = "x86")] 177 | asm!("mov {teb}, fs:[0x18]", teb = out(reg) teb); 178 | } 179 | teb 180 | } 181 | 182 | #[cfg_attr(feature = "debug", inline(never))] 183 | pub fn find_pe_base(start: usize) -> Result<*mut u8> { 184 | let mut aligned = start & !0xfff; 185 | loop { 186 | if aligned == 0 { 187 | return Err(Error::SelfFind); 188 | } 189 | 190 | match unsafe { PeHeaders::parse(aligned as _) } { 191 | Ok(_) => break Ok(aligned as _), 192 | Err(_) => aligned -= 0x1000, 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /objparse/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::missing_safety_doc)] 2 | 3 | pub mod error; 4 | 5 | use crate::error::{Error, Result}; 6 | use core::{ffi::CStr, mem::size_of, slice}; 7 | use object::{ 8 | pe::{ 9 | self, ImageDataDirectory, ImageDebugDirectory, ImageDosHeader, ImageExportDirectory, 10 | ImageImportDescriptor, ImageSectionHeader, ImageTlsDirectory64, 11 | IMAGE_DIRECTORY_ENTRY_DEBUG, IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DIRECTORY_ENTRY_IMPORT, 12 | IMAGE_DIRECTORY_ENTRY_TLS, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE, 13 | }, 14 | read::pe::{ImageNtHeaders, ImageOptionalHeader}, 15 | LittleEndian, 16 | }; 17 | use windows_sys::Win32::System::SystemServices::PIMAGE_TLS_CALLBACK; 18 | 19 | pub struct PeHeaders { 20 | pub dos_header: &'static ImageDosHeader, 21 | #[cfg(target_arch = "x86_64")] 22 | pub nt_header: &'static pe::ImageNtHeaders64, 23 | #[cfg(target_arch = "x86")] 24 | pub nt_header: &'static pe::ImageNtHeaders32, 25 | pub data_directories: &'static [ImageDataDirectory], 26 | pub section_headers: &'static [ImageSectionHeader], 27 | } 28 | 29 | impl PeHeaders { 30 | #[cfg_attr(feature = "debug", inline(never))] 31 | pub unsafe fn parse(address: *const u8) -> Result { 32 | let dos_header_ptr = address; 33 | let dos_header = unsafe { &*dos_header_ptr.cast::() }; 34 | if dos_header.e_magic.get(LittleEndian) != IMAGE_DOS_SIGNATURE { 35 | return Err(Error::PeHeaders); 36 | } 37 | let nt_header_offset = dos_header.nt_headers_offset() as usize; 38 | // Sanity check 39 | if nt_header_offset > 1024 { 40 | return Err(Error::PeHeaders); 41 | } 42 | let nt_header_ptr = unsafe { address.add(nt_header_offset) }; 43 | #[cfg(target_arch = "x86_64")] 44 | let nt_header = unsafe { &*nt_header_ptr.cast::() }; 45 | #[cfg(target_arch = "x86")] 46 | let nt_header = unsafe { &*nt_header_ptr.cast::() }; 47 | if nt_header.signature.get(LittleEndian) != IMAGE_NT_SIGNATURE { 48 | return Err(Error::PeHeaders); 49 | } 50 | if !nt_header.is_valid_optional_magic() { 51 | return Err(Error::PeHeaders); 52 | } 53 | #[cfg(target_arch = "x86_64")] 54 | let data_directories_ptr = unsafe { nt_header_ptr.add(size_of::()) }; 55 | #[cfg(target_arch = "x86")] 56 | let data_directories_ptr = unsafe { nt_header_ptr.add(size_of::()) }; 57 | let num_data_directories = nt_header.optional_header().number_of_rva_and_sizes() as _; 58 | let data_directories = unsafe { 59 | slice::from_raw_parts( 60 | data_directories_ptr.cast::(), 61 | num_data_directories, 62 | ) 63 | }; 64 | let section_headers_ptr = unsafe { 65 | data_directories_ptr.add(num_data_directories * size_of::()) 66 | }; 67 | let num_section_headers = nt_header.file_header().number_of_sections.get(LittleEndian) as _; 68 | let section_headers = unsafe { 69 | slice::from_raw_parts( 70 | section_headers_ptr.cast::(), 71 | num_section_headers, 72 | ) 73 | }; 74 | 75 | Ok(Self { 76 | dos_header, 77 | nt_header, 78 | data_directories, 79 | section_headers, 80 | }) 81 | } 82 | 83 | #[cfg_attr(feature = "debug", inline(never))] 84 | pub unsafe fn export_table_mem(&self, image_base: *const u8) -> Result { 85 | let export_table_data_dir = self 86 | .data_directories 87 | .get(IMAGE_DIRECTORY_ENTRY_EXPORT) 88 | .ok_or(Error::ExportTable)?; 89 | let export_table_rva = export_table_data_dir.virtual_address.get(LittleEndian); 90 | let export_table_ptr = unsafe { image_base.add(export_table_rva as _) }; 91 | let export_table_size = export_table_data_dir.size.get(LittleEndian); 92 | Ok(ExportTable::parse( 93 | export_table_ptr, 94 | export_table_rva as _, 95 | export_table_size, 96 | )) 97 | } 98 | 99 | #[cfg_attr(feature = "debug", inline(never))] 100 | pub unsafe fn import_table_mem(&self, image_base: *const u8) -> Result { 101 | let import_table_data_dir = self 102 | .data_directories 103 | .get(IMAGE_DIRECTORY_ENTRY_IMPORT) 104 | .ok_or(Error::ImportTable)?; 105 | let import_table_rva = import_table_data_dir.virtual_address.get(LittleEndian); 106 | let import_table_size = import_table_data_dir.size.get(LittleEndian); 107 | let import_table_ptr = unsafe { image_base.add(import_table_rva as _) }; 108 | Ok(ImportTable::parse(import_table_ptr, import_table_size as _)) 109 | } 110 | 111 | #[cfg_attr(feature = "debug", inline(never))] 112 | pub unsafe fn debug_table_mem(&self, image_base: *const u8) -> Result { 113 | let debug_table_data_dir = self 114 | .data_directories 115 | .get(IMAGE_DIRECTORY_ENTRY_DEBUG) 116 | .ok_or(Error::DebugTable)?; 117 | let debug_table_rva = debug_table_data_dir.virtual_address.get(LittleEndian); 118 | let debug_table_size = debug_table_data_dir.size.get(LittleEndian); 119 | let debug_table_ptr = unsafe { image_base.add(debug_table_rva as _) }; 120 | Ok(DebugTable::parse(debug_table_ptr, debug_table_size as _)) 121 | } 122 | 123 | #[cfg_attr(feature = "debug", inline(never))] 124 | pub unsafe fn tls_table_mem(&self, image_base: *const u8) -> Result> { 125 | let tls_table_data_dir = self 126 | .data_directories 127 | .get(IMAGE_DIRECTORY_ENTRY_TLS) 128 | .ok_or(Error::TlsTable)?; 129 | let tls_table_rva = tls_table_data_dir.virtual_address.get(LittleEndian); 130 | let _tls_table_size = tls_table_data_dir.size.get(LittleEndian); 131 | if tls_table_rva == 0 { 132 | return Ok(None); 133 | } 134 | let tls_table_ptr = unsafe { image_base.add(tls_table_rva as _) }; 135 | Ok(Some(TlsDir::parse(tls_table_ptr))) 136 | } 137 | } 138 | 139 | pub struct ExportTable { 140 | pub export_directory: &'static ImageExportDirectory, 141 | pub address_table: &'static [u32], 142 | pub name_table: &'static [u32], 143 | pub ordinal_table: &'static [u16], 144 | pub start_address: *const u8, 145 | pub size: u32, 146 | } 147 | 148 | impl ExportTable { 149 | #[cfg_attr(feature = "debug", inline(never))] 150 | pub unsafe fn parse(address: *const u8, rva: usize, size: u32) -> Self { 151 | let export_directory_ptr = address; 152 | let export_directory = unsafe { &*export_directory_ptr.cast::() }; 153 | 154 | let address_table_ptr = unsafe { 155 | address 156 | .add(export_directory.address_of_functions.get(LittleEndian) as _) 157 | .wrapping_sub(rva) 158 | .cast::() 159 | }; 160 | let address_table_len = export_directory.number_of_functions.get(LittleEndian) as _; 161 | let address_table = unsafe { slice::from_raw_parts(address_table_ptr, address_table_len) }; 162 | 163 | let name_table_ptr = unsafe { 164 | address 165 | .add(export_directory.address_of_names.get(LittleEndian) as _) 166 | .wrapping_sub(rva) 167 | .cast::() 168 | }; 169 | let name_table_len = export_directory.number_of_names.get(LittleEndian) as _; 170 | let name_table = unsafe { slice::from_raw_parts(name_table_ptr, name_table_len) }; 171 | 172 | let ordinal_table_ptr = unsafe { 173 | address 174 | .add(export_directory.address_of_name_ordinals.get(LittleEndian) as _) 175 | .wrapping_sub(rva) 176 | .cast::() 177 | }; 178 | let ordinal_table_len = export_directory.number_of_names.get(LittleEndian) as _; 179 | let ordinal_table = unsafe { slice::from_raw_parts(ordinal_table_ptr, ordinal_table_len) }; 180 | 181 | Self { 182 | export_directory, 183 | address_table, 184 | name_table, 185 | ordinal_table, 186 | start_address: address, 187 | size, 188 | } 189 | } 190 | 191 | #[cfg_attr(feature = "debug", inline(never))] 192 | pub fn iter_name_ord(&self) -> impl Iterator + '_ { 193 | self.name_table 194 | .iter() 195 | .copied() 196 | .zip(self.ordinal_table.iter().copied()) 197 | } 198 | 199 | #[cfg_attr(feature = "debug", inline(never))] 200 | pub unsafe fn iter_string_addr( 201 | &self, 202 | image_base: *mut u8, 203 | ) -> impl Iterator { 204 | self.iter_name_ord().map(move |(name_rva, ord)| { 205 | let string_ptr = unsafe { image_base.add(name_rva as _) }; 206 | let string = unsafe { CStr::from_ptr(string_ptr as _) }; 207 | let address_rva = unsafe { *self.address_table.get_unchecked(ord as usize) }; 208 | let address = unsafe { image_base.add(address_rva as _) }; 209 | (string, address) 210 | }) 211 | } 212 | } 213 | 214 | pub struct ImportTable { 215 | pub import_descriptors: &'static [ImageImportDescriptor], 216 | } 217 | 218 | impl ImportTable { 219 | #[cfg_attr(feature = "debug", inline(never))] 220 | pub fn parse(address: *const u8, size: usize) -> Self { 221 | let number_of_entries = size / size_of::() - 1; 222 | let import_descriptor_ptr = address.cast::(); 223 | let import_descriptors = 224 | unsafe { slice::from_raw_parts(import_descriptor_ptr, number_of_entries) }; 225 | 226 | Self { import_descriptors } 227 | } 228 | } 229 | 230 | pub struct DebugTable { 231 | pub debug_descriptors: &'static [ImageDebugDirectory], 232 | } 233 | 234 | impl DebugTable { 235 | #[cfg_attr(feature = "debug", inline(never))] 236 | pub fn parse(address: *const u8, size: usize) -> Self { 237 | let number_of_entries = size / size_of::() - 1; 238 | let debug_descriptor_ptr = address.cast::(); 239 | let debug_descriptors = 240 | unsafe { slice::from_raw_parts(debug_descriptor_ptr, number_of_entries) }; 241 | 242 | Self { debug_descriptors } 243 | } 244 | } 245 | 246 | pub struct TlsDir { 247 | pub tls_dir: &'static ImageTlsDirectory64, 248 | } 249 | 250 | impl TlsDir { 251 | #[cfg_attr(feature = "debug", inline(never))] 252 | pub fn parse(address: *const u8) -> Self { 253 | #[cfg(target_arch = "x86_64")] 254 | let tls_dir = unsafe { &*address.cast::() }; 255 | #[cfg(target_arch = "x86")] 256 | let tls_dir = unsafe { &*address.cast::() }; 257 | 258 | Self { tls_dir } 259 | } 260 | 261 | #[cfg_attr(feature = "debug", inline(never))] 262 | pub fn callbacks(&self) -> TlsCallbacks { 263 | let callback_addr = 264 | self.tls_dir.address_of_call_backs.get(LittleEndian) as *const PIMAGE_TLS_CALLBACK; 265 | TlsCallbacks { callback_addr } 266 | } 267 | } 268 | 269 | pub struct TlsCallbacks { 270 | callback_addr: *const PIMAGE_TLS_CALLBACK, 271 | } 272 | 273 | type TlsCallback = unsafe extern "system" fn( 274 | dllhandle: *mut std::ffi::c_void, 275 | reason: u32, 276 | reserved: *mut std::ffi::c_void, 277 | ); 278 | 279 | impl Iterator for TlsCallbacks { 280 | type Item = TlsCallback; 281 | 282 | fn next(&mut self) -> Option { 283 | let ret = unsafe { *self.callback_addr }; 284 | unsafe { self.callback_addr = self.callback_addr.add(1) }; 285 | ret 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /reloader/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(maybe_uninit_uninit_array_transpose)] 2 | #![feature(maybe_uninit_slice)] 3 | #![no_std] 4 | 5 | mod error; 6 | mod function_wrappers; 7 | mod helpers; 8 | mod syscall; 9 | 10 | use crate::{ 11 | error::{Error, Result}, 12 | function_wrappers::{allocate_memory, flush_instruction_cache, protect_memory}, 13 | helpers::{ 14 | general::{ 15 | find_pe_base, fnv1a_hash_32, get_ip, get_teb, memset_uninit_array, simple_memcpy, 16 | }, 17 | library::{ 18 | find_export_by_ascii, find_export_by_hash, find_loaded_module_by_hash, get_library_base, 19 | }, 20 | syscall::{SYSCALL_TABLE_SIZE, find_syscall_by_hash, gen_syscall_table}, 21 | }, 22 | }; 23 | use core::{ 24 | arch::asm, 25 | ffi::CStr, 26 | mem::{MaybeUninit, size_of, transmute}, 27 | ptr::{addr_of_mut, null_mut}, 28 | slice, 29 | str::from_utf8_unchecked, 30 | }; 31 | use object::{ 32 | LittleEndian, 33 | pe::{ 34 | IMAGE_DIRECTORY_ENTRY_BASERELOC, IMAGE_REL_BASED_ABSOLUTE, IMAGE_REL_BASED_DIR64, 35 | IMAGE_REL_BASED_HIGHLOW, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE, 36 | ImageThunkData64, 37 | }, 38 | read::pe::{ImageNtHeaders, ImageOptionalHeader, ImageThunkData}, 39 | }; 40 | use objparse::PeHeaders; 41 | use phnt::ffi::{PEB_LDR_DATA, TEB, UNICODE_STRING}; 42 | use windows_sys::Win32::System::{ 43 | Memory::{ 44 | PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY, 45 | PAGE_NOACCESS, PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY, 46 | }, 47 | SystemServices::DLL_PROCESS_ATTACH, 48 | }; 49 | 50 | // Abusing fnv1a hash to find the strings we're looking for 51 | // Can't just do a string comparison because the segments aren't properly loaded yet 52 | 53 | const NTDLL_HASH: u32 = fnv1a_hash_32("ntdll.dll".as_bytes()); 54 | 55 | const ZWFLUSHINSTRUCTIONCACHE_HASH: u32 = fnv1a_hash_32("ZwFlushInstructionCache".as_bytes()); 56 | const ZWALLOCATEVIRTUALMEMORY_HASH: u32 = fnv1a_hash_32("ZwAllocateVirtualMemory".as_bytes()); 57 | const ZWPROTECTVIRTUALMEMORY_HASH: u32 = fnv1a_hash_32("ZwProtectVirtualMemory".as_bytes()); 58 | 59 | const LDRLOADDLL_HASH: u32 = fnv1a_hash_32("LdrLoadDll".as_bytes()); 60 | 61 | const GET_TICK_COUNT: u32 = fnv1a_hash_32("NtGetTickCount".as_bytes()); 62 | 63 | pub struct SyscallNumbers { 64 | sys_no_zwallocatevirtualmemory: u32, 65 | sys_no_zwprotectvirtualmemory: u32, 66 | sys_no_zwflushinstructioncache: u32, 67 | } 68 | 69 | pub struct ImportantStructures { 70 | teb: *mut TEB, 71 | peb_ldr: *mut PEB_LDR_DATA, 72 | ntdll_base: *mut u8, 73 | } 74 | 75 | pub struct LoaderContext { 76 | syscall_numbers: SyscallNumbers, 77 | ldr_load_dll: unsafe extern "system" fn( 78 | *const u16, 79 | *const u32, 80 | *const UNICODE_STRING, 81 | *mut *mut u8, 82 | ) -> i32, 83 | get_tick_count: unsafe extern "system" fn() -> u32, 84 | } 85 | 86 | #[unsafe(no_mangle)] 87 | #[cfg_attr(feature = "debug", inline(never))] 88 | pub extern "system" fn reflective_loader(reserved: usize) { 89 | match reflective_loader_impl(reserved, false) { 90 | Ok(x) => x, 91 | Err(e) => handle_error(e), 92 | } 93 | } 94 | 95 | #[unsafe(no_mangle)] 96 | #[cfg_attr(feature = "debug", inline(never))] 97 | pub extern "system" fn reflective_loader_wow64(reserved: usize) { 98 | unsafe { 99 | asm!( 100 | ".code32", 101 | "push ebp", 102 | "mov ebp, esp", 103 | "and esp, 0xfffffff0", 104 | "push 0x33", 105 | "call 1337f", 106 | "1337:", 107 | "add dword ptr [esp], 5", 108 | "retf", 109 | ".code64", 110 | ); 111 | match reflective_loader_impl(reserved, true) { 112 | Ok(x) => x, 113 | Err(e) => handle_error(e), 114 | } 115 | asm!( 116 | ".code64", 117 | "call 1337f", 118 | "1337:", 119 | "mov dword ptr [rsp + 4], 0x23", 120 | "add dword ptr [rsp], 0xd", 121 | "retf", 122 | ".code32", 123 | "mov esp, ebp", 124 | "pop ebp", 125 | ".code64", 126 | ); 127 | } 128 | } 129 | 130 | #[cfg_attr(feature = "debug", inline(never))] 131 | fn handle_error(error: Error) -> ! { 132 | #[cfg(feature = "debug")] 133 | { 134 | let error_code = error as u16; 135 | 136 | // Write error code to invalid address for rudimentary debugging 137 | unsafe { *(0xdeadbeefdeadbeef as *mut _) = error_code }; 138 | } 139 | panic!() 140 | } 141 | 142 | #[cfg_attr(feature = "debug", inline(never))] 143 | fn reflective_loader_impl(reserved: usize, wow64: bool) -> Result<()> { 144 | // Find ourselves 145 | let pe_base = find_pe_base(get_ip())?; 146 | 147 | // Find global structures 148 | let important_structures = find_structures()?; 149 | 150 | // Get information needed for loading a DLL 151 | let context = get_context(important_structures.ntdll_base)?; 152 | 153 | if wow64 { 154 | fixup_wow64_pre(&important_structures, &context); 155 | } 156 | 157 | // Load ourself as a DLL 158 | let (allocated_ptr, size_of_image, entry_point) = 159 | load_dll(pe_base, important_structures.peb_ldr, &context)?; 160 | 161 | if wow64 { 162 | fixup_wow64_post( 163 | &important_structures, 164 | &context, 165 | allocated_ptr, 166 | size_of_image, 167 | ); 168 | } 169 | 170 | // Call entry point 171 | let entry_point_callable = 172 | unsafe { transmute::<*mut u8, unsafe extern "system" fn(usize, u32, usize)>(entry_point) }; 173 | 174 | unsafe { entry_point_callable(allocated_ptr as _, DLL_PROCESS_ATTACH, reserved) }; 175 | 176 | Ok(()) 177 | } 178 | 179 | #[cfg_attr(feature = "debug", inline(never))] 180 | fn fixup_wow64_pre(important_structures: &ImportantStructures, context: &LoaderContext) { 181 | // The process state in 64 bit mode is totally broken, we need to get it to some barely-functional state - enough to load some dependency DLLs etc. 182 | // Some dependencies will conflict with the 32 bit environment, but we will just assume that everything is fine here. 183 | // Ideally we'd be reimplementing some actual NTDLL functionality rather than these easy hacks 184 | 185 | let teb = unsafe { &mut (*important_structures.teb) }; 186 | 187 | // Initialize thread activation context 188 | let tick_count = unsafe { (context.get_tick_count)() }; 189 | let context_stack = &mut teb.ActivationStack; 190 | context_stack.StackId = tick_count; 191 | context_stack.NextCookieSequenceNumber = 1; 192 | context_stack.Flags = 2; 193 | context_stack.ActiveFrame = null_mut(); 194 | teb.ActivationContextStackPointer = context_stack; 195 | 196 | // Initialize TLS 197 | teb.ThreadLocalStoragePointer = addr_of_mut!(teb.ThreadLocalStoragePointer) as _; 198 | } 199 | 200 | #[cfg_attr(feature = "debug", inline(never))] 201 | fn fixup_wow64_post( 202 | important_structures: &ImportantStructures, 203 | context: &LoaderContext, 204 | pe_base: *mut u8, 205 | allocated_size: usize, 206 | ) { 207 | // TODO 208 | } 209 | 210 | #[cfg_attr(feature = "debug", inline(never))] 211 | fn find_structures() -> Result { 212 | // Locate other important data structures 213 | let teb = get_teb(); 214 | let peb = unsafe { (*teb).ProcessEnvironmentBlock }; 215 | let peb_ldr = unsafe { (*peb).Ldr }; 216 | 217 | // Traverse loaded modules to find ntdll.dll 218 | let ntdll_base = find_loaded_module_by_hash(peb_ldr, NTDLL_HASH)?; 219 | 220 | Ok(ImportantStructures { 221 | teb, 222 | peb_ldr, 223 | ntdll_base, 224 | }) 225 | } 226 | 227 | #[cfg_attr(feature = "debug", inline(never))] 228 | fn get_context(ntdll_base: *mut u8) -> Result { 229 | let ntdll = unsafe { PeHeaders::parse(ntdll_base) }?; 230 | 231 | // Locate the export table for ntdll.dll 232 | let ntdll_export_table = unsafe { ntdll.export_table_mem(ntdll_base) }?; 233 | 234 | // Create the syscall table 235 | let mut syscall_table = MaybeUninit::<[u32; SYSCALL_TABLE_SIZE]>::uninit().transpose(); 236 | let syscall_table = memset_uninit_array(&mut syscall_table, 0); 237 | gen_syscall_table(&ntdll_export_table, ntdll_base, syscall_table); 238 | 239 | // Find some important syscall numbers 240 | let sys_no_zwallocatevirtualmemory = 241 | find_syscall_by_hash(syscall_table, ZWALLOCATEVIRTUALMEMORY_HASH)?; 242 | let sys_no_zwprotectvirtualmemory = 243 | find_syscall_by_hash(syscall_table, ZWPROTECTVIRTUALMEMORY_HASH)?; 244 | let sys_no_zwflushinstructioncache = 245 | find_syscall_by_hash(syscall_table, ZWFLUSHINSTRUCTIONCACHE_HASH)?; 246 | 247 | let syscall_numbers = SyscallNumbers { 248 | sys_no_zwallocatevirtualmemory, 249 | sys_no_zwprotectvirtualmemory, 250 | sys_no_zwflushinstructioncache, 251 | }; 252 | 253 | let ldrloaddll = find_export_by_hash(&ntdll_export_table, ntdll_base, LDRLOADDLL_HASH)?; 254 | let ldrloaddll = unsafe { 255 | transmute::< 256 | *mut u8, 257 | unsafe extern "system" fn( 258 | *const u16, 259 | *const u32, 260 | *const phnt::ffi::UNICODE_STRING, 261 | *mut *mut u8, 262 | ) -> i32, 263 | >(ldrloaddll) 264 | }; 265 | 266 | let get_tick_count = find_export_by_hash(&ntdll_export_table, ntdll_base, GET_TICK_COUNT)?; 267 | 268 | let context = LoaderContext { 269 | syscall_numbers, 270 | ldr_load_dll: ldrloaddll, 271 | get_tick_count: unsafe { 272 | transmute::<*mut u8, unsafe extern "system" fn() -> u32>(get_tick_count) 273 | }, 274 | }; 275 | Ok(context) 276 | } 277 | 278 | #[cfg_attr(feature = "debug", inline(never))] 279 | fn load_dll( 280 | pe_base: *mut u8, 281 | peb_ldr: *mut PEB_LDR_DATA, 282 | context: &LoaderContext, 283 | ) -> Result<(*mut u8, usize, *mut u8)> { 284 | let pe = unsafe { PeHeaders::parse(pe_base) }?; 285 | 286 | // Allocate space to map the PE into 287 | let size_of_image = pe 288 | .nt_header 289 | .optional_header() 290 | .size_of_image 291 | .get(LittleEndian) as usize; 292 | 293 | let allocated_ptr = allocate_memory(&context.syscall_numbers, size_of_image)?; 294 | 295 | let header_size = copy_header(allocated_ptr, &pe, pe_base); 296 | 297 | map_sections(allocated_ptr, &pe, pe_base); 298 | 299 | process_imports(allocated_ptr, &pe, peb_ldr, context)?; 300 | 301 | process_relocations(allocated_ptr, &pe)?; 302 | 303 | set_permissions(allocated_ptr, header_size, &pe, context)?; 304 | 305 | let entry_point = unsafe { 306 | allocated_ptr.add( 307 | pe.nt_header 308 | .optional_header() 309 | .address_of_entry_point 310 | .get(LittleEndian) as _, 311 | ) 312 | }; 313 | 314 | // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing 315 | flush_instruction_cache(&context.syscall_numbers)?; 316 | 317 | process_tls(allocated_ptr, &pe); 318 | 319 | Ok((allocated_ptr, size_of_image, entry_point)) 320 | } 321 | 322 | #[cfg_attr(feature = "debug", inline(never))] 323 | fn copy_header(dest: *mut u8, pe: &PeHeaders, pe_base: *mut u8) -> usize { 324 | // Copy over header data 325 | let header_size = pe 326 | .nt_header 327 | .optional_header() 328 | .size_of_headers 329 | .get(LittleEndian) as _; 330 | simple_memcpy(dest, pe_base, header_size); 331 | header_size 332 | } 333 | 334 | #[cfg_attr(feature = "debug", inline(never))] 335 | fn map_sections(dest: *mut u8, pe: &PeHeaders, pe_base: *mut u8) { 336 | // Map sections 337 | pe.section_headers.iter().for_each(|section| { 338 | let dest = unsafe { dest.add(section.virtual_address.get(LittleEndian) as _) }; 339 | let src = unsafe { pe_base.add(section.pointer_to_raw_data.get(LittleEndian) as _) }; 340 | let size = section.size_of_raw_data.get(LittleEndian) as _; 341 | simple_memcpy(dest, src, size); 342 | }); 343 | } 344 | 345 | #[cfg_attr(feature = "debug", inline(never))] 346 | fn process_imports( 347 | dest: *mut u8, 348 | pe: &PeHeaders, 349 | peb_ldr: *mut PEB_LDR_DATA, 350 | context: &LoaderContext, 351 | ) -> Result<()> { 352 | // Process import table 353 | let import_table = unsafe { pe.import_table_mem(dest) }?; 354 | for idt in import_table.import_descriptors { 355 | // Load the library 356 | let name_rva = idt.name.get(LittleEndian) as usize; 357 | let library_name = unsafe { dest.add(name_rva) }; 358 | 359 | // Load library if it is not already loaded and get the base 360 | let loaded_library_base = get_library_base(peb_ldr, library_name as _, context)?; 361 | 362 | // Find the exports of the loaded library 363 | let loaded_library = unsafe { PeHeaders::parse(loaded_library_base) }?; 364 | let loaded_library_exports = 365 | unsafe { loaded_library.export_table_mem(loaded_library_base) }?; 366 | 367 | let ilt_rva = idt.original_first_thunk.get(LittleEndian) as usize; 368 | let iat_rva = idt.first_thunk.get(LittleEndian) as usize; 369 | 370 | let mut ilt_ptr = unsafe { dest.add(ilt_rva).cast::() }; 371 | let mut iat_ptr = unsafe { dest.add(iat_rva).cast::() }; 372 | 373 | // Look through each entry in the ILT until we find a null entry 374 | while unsafe { ilt_ptr.read().raw() != 0 } { 375 | let ilt_entry = unsafe { ilt_ptr.read() }; 376 | 377 | // Resolve the function VA - taking forwarding into account 378 | 379 | // First step - get an address from direct dependency 380 | let mut resolved_address = match ilt_entry.is_ordinal() { 381 | true => { 382 | // Load from ordinal 383 | let ordinal = ilt_entry.ordinal(); 384 | 385 | // Find matching function in loaded library 386 | let export_rva = unsafe { 387 | *loaded_library_exports 388 | .address_table 389 | .get_unchecked(ordinal as usize) 390 | }; 391 | 392 | unsafe { loaded_library_base.add(export_rva as _) } 393 | } 394 | false => { 395 | // Load from name 396 | let address_rva = ilt_entry.address() as _; 397 | 398 | // Get the name of the function 399 | let string_va = unsafe { dest.add(address_rva).add(size_of::()) }; 400 | let string = unsafe { CStr::from_ptr(string_va as _) }; 401 | 402 | // Find matching function in loaded library 403 | find_export_by_ascii(&loaded_library_exports, loaded_library_base, string)? 404 | } 405 | }; 406 | 407 | // Forwarding 408 | let mut current_export_start = loaded_library_exports.start_address; 409 | let mut current_export_end = 410 | unsafe { current_export_start.add(loaded_library_exports.size as _) as _ }; 411 | let function_va = loop { 412 | let mut buffer = [0u8; 64]; 413 | 414 | if !(current_export_start <= resolved_address 415 | && resolved_address < current_export_end) 416 | { 417 | // The pointer is not located in the exports section and therefore has no more forwarders 418 | break resolved_address; 419 | } 420 | 421 | // We have a pointer to a null-terminated string of the form "MYDLL.expfunc" or "MYDLL.#27" 422 | let string = unsafe { CStr::from_ptr(resolved_address as _) }; 423 | let string = string.to_bytes_with_nul(); 424 | 425 | // Copy string to buffer 426 | for i in 0..string.len() { 427 | unsafe { 428 | let c = *string.get_unchecked(i); 429 | *buffer.get_unchecked_mut(i) = c; 430 | } 431 | } 432 | 433 | // Give an extra 4 bytes for adding "dll\0" 434 | let working_buffer = unsafe { buffer.get_unchecked_mut(..string.len() + 4) }; 435 | 436 | // Find the dot 437 | let dot_index = working_buffer 438 | .iter() 439 | .position(|&b| b == b'.') 440 | .ok_or(Error::SplitString)?; 441 | 442 | // Move things after the dot forwards 443 | for i in (dot_index + 1..working_buffer.len()).rev() { 444 | unsafe { 445 | let c = *working_buffer.get_unchecked(i); 446 | *working_buffer.get_unchecked_mut(i + 4) = c; 447 | }; 448 | } 449 | 450 | // Write DLL 451 | unsafe { 452 | *working_buffer.get_unchecked_mut(dot_index + 1) = b'd'; 453 | 454 | *working_buffer.get_unchecked_mut(dot_index + 2) = b'l'; 455 | *working_buffer.get_unchecked_mut(dot_index + 3) = b'l'; 456 | *working_buffer.get_unchecked_mut(dot_index + 4) = b'\0'; 457 | } 458 | 459 | let (dll_name, rest) = 460 | unsafe { working_buffer.split_at_mut_unchecked(dot_index + 5) }; 461 | 462 | // Load library if it is not already loaded and get the base 463 | let loaded_library_base = get_library_base(peb_ldr, dll_name.as_ptr(), context)?; 464 | 465 | // Find the exports of the loaded library 466 | let loaded_library = unsafe { PeHeaders::parse(loaded_library_base) }?; 467 | let loaded_library_exports = 468 | unsafe { loaded_library.export_table_mem(loaded_library_base) }?; 469 | 470 | // Set resolved address for next loop 471 | resolved_address = if unsafe { *rest.get_unchecked(0) } == b'#' { 472 | // Load from ordinal 473 | let number_string = unsafe { from_utf8_unchecked(rest.get_unchecked(1..)) }; 474 | let ordinal = number_string 475 | .parse::() 476 | .map_err(|_| Error::ParseNumber)?; 477 | 478 | // Find matching function in loaded library 479 | let export_rva = unsafe { 480 | *loaded_library_exports 481 | .address_table 482 | .get_unchecked(ordinal as usize) 483 | }; 484 | 485 | unsafe { loaded_library_base.add(export_rva as _) } 486 | } 487 | else { 488 | // Load from name 489 | 490 | // Get the name of the function 491 | let string = unsafe { CStr::from_ptr(rest.as_ptr() as _) }; 492 | 493 | // Find matching function in loaded library 494 | find_export_by_ascii(&loaded_library_exports, loaded_library_base, string)? 495 | }; 496 | 497 | // Set the new export range 498 | current_export_start = loaded_library_exports.start_address; 499 | current_export_end = 500 | unsafe { current_export_start.add(loaded_library_exports.size as _) as _ }; 501 | }; 502 | 503 | // Write function VA into IAT 504 | unsafe { *iat_ptr = function_va as _ }; 505 | 506 | // Advance to the next entry 507 | ilt_ptr = unsafe { ilt_ptr.add(1) }; 508 | iat_ptr = unsafe { iat_ptr.add(1) }; 509 | } 510 | } 511 | Ok(()) 512 | } 513 | 514 | #[cfg_attr(feature = "debug", inline(never))] 515 | fn process_relocations(dest: *mut u8, pe: &PeHeaders) -> Result<()> { 516 | // Process relocations 517 | let image_base_in_file = pe.nt_header.optional_header().image_base(); 518 | let calculated_offset = dest as isize - image_base_in_file as isize; 519 | 520 | let relocations = unsafe { 521 | pe.data_directories 522 | .get_unchecked(IMAGE_DIRECTORY_ENTRY_BASERELOC) 523 | }; 524 | 525 | // Iterate through the relocation table 526 | let reloc_start_address = 527 | unsafe { dest.add(relocations.virtual_address.get(LittleEndian) as _) }; 528 | let reloc_size_bytes = relocations.size.get(LittleEndian) as _; 529 | 530 | let mut reloc_byte_slice = 531 | unsafe { slice::from_raw_parts(reloc_start_address, reloc_size_bytes) }; 532 | 533 | // Loop over relocation blocks - each has a 8 byte header 534 | while let &[a, b, c, d, e, f, g, h, ref rest @ ..] = reloc_byte_slice { 535 | let rva = u32::from_le_bytes([a, b, c, d]) as usize; 536 | let relocs_bytes = u32::from_le_bytes([e, f, g, h]) as usize - 8; 537 | 538 | let block_va = unsafe { dest.add(rva) }; 539 | 540 | // Loop over the relocations in this block 541 | let (mut relocs_slice, rest) = unsafe { rest.split_at_unchecked(relocs_bytes) }; 542 | while let &[a, b, ref rest @ ..] = relocs_slice { 543 | let reloc = u16::from_le_bytes([a, b]); 544 | 545 | let reloc_type = (reloc & 0xf000) >> 0xc; 546 | let reloc_offset = reloc & 0x0fff; 547 | 548 | let reloc_va = unsafe { block_va.add(reloc_offset as _) }; 549 | 550 | // Apply the relocation 551 | match reloc_type { 552 | IMAGE_REL_BASED_ABSOLUTE => (), 553 | IMAGE_REL_BASED_DIR64 => { 554 | let ptr = reloc_va as *mut u64; 555 | unsafe { *ptr += calculated_offset as u64 }; 556 | } 557 | IMAGE_REL_BASED_HIGHLOW => { 558 | let ptr = reloc_va as *mut u32; 559 | unsafe { *ptr += calculated_offset as u32 }; 560 | } 561 | _ => return Err(Error::RelocationType), 562 | } 563 | 564 | relocs_slice = rest; 565 | } 566 | 567 | reloc_byte_slice = rest; 568 | } 569 | Ok(()) 570 | } 571 | 572 | #[cfg_attr(feature = "debug", inline(never))] 573 | fn set_permissions( 574 | dest: *mut u8, 575 | header_size: usize, 576 | pe: &PeHeaders, 577 | context: &LoaderContext, 578 | ) -> Result<()> { 579 | // Set header permissions 580 | protect_memory(&context.syscall_numbers, dest, header_size, PAGE_READONLY)?; 581 | 582 | // Set section permissions 583 | pe.section_headers 584 | .iter() 585 | .try_for_each(|section| -> core::result::Result<(), Error> { 586 | let virtual_address = 587 | unsafe { dest.add(section.virtual_address.get(LittleEndian) as _) }; 588 | let virtual_size = section.virtual_size.get(LittleEndian); 589 | 590 | // Change permissions 591 | let characteristics = section.characteristics.get(LittleEndian); 592 | let r = characteristics & IMAGE_SCN_MEM_READ != 0; 593 | let w = characteristics & IMAGE_SCN_MEM_WRITE != 0; 594 | let x = characteristics & IMAGE_SCN_MEM_EXECUTE != 0; 595 | let new_permissions = match (r, w, x) { 596 | (false, false, false) => PAGE_NOACCESS, 597 | (true, false, false) => PAGE_READONLY, 598 | (false, true, false) => PAGE_WRITECOPY, 599 | (true, true, false) => PAGE_READWRITE, 600 | (false, false, true) => PAGE_EXECUTE, 601 | (true, false, true) => PAGE_EXECUTE_READ, 602 | (false, true, true) => PAGE_EXECUTE_WRITECOPY, 603 | (true, true, true) => PAGE_EXECUTE_READWRITE, 604 | }; 605 | protect_memory( 606 | &context.syscall_numbers, 607 | virtual_address, 608 | virtual_size as _, 609 | new_permissions, 610 | )?; 611 | Ok(()) 612 | })?; 613 | Ok(()) 614 | } 615 | 616 | #[cfg_attr(feature = "debug", inline(never))] 617 | fn process_tls(dest: *mut u8, pe: &PeHeaders) { 618 | let tls_callbacks = unsafe { pe.tls_table_mem(dest).unwrap().unwrap() }; 619 | 620 | // Calling each TLS callback 621 | for callback in tls_callbacks.callbacks() { 622 | unsafe { callback(dest as _, DLL_PROCESS_ATTACH, null_mut()) }; 623 | } 624 | } 625 | --------------------------------------------------------------------------------