├── .gitignore ├── README.md ├── Cargo.toml ├── example ├── Cargo.toml └── src │ └── lib.rs └── client ├── examples ├── current.rs ├── remote.rs └── dummy.rs ├── Cargo.toml └── src ├── error.rs ├── mmap.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | **/.idea/ 2 | target/ 3 | Cargo.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mmap 2 | 3 | Simple manual mapper written in Rust. 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["client", "example"] 3 | resolver = "2" 4 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | winapi = { version = "0.3.9", features = ["winnt", "libloaderapi", "winuser"] } -------------------------------------------------------------------------------- /client/examples/current.rs: -------------------------------------------------------------------------------- 1 | use client::Process; 2 | use simple_logger::SimpleLogger; 3 | 4 | fn main() { 5 | SimpleLogger::new().init().unwrap(); 6 | 7 | let bytes = include_bytes!("../../target/release/example.dll"); 8 | Process::current() 9 | .expect("Failed to open process") 10 | .manual_map(bytes) 11 | .expect("Failed to map process"); 12 | } 13 | -------------------------------------------------------------------------------- /client/examples/remote.rs: -------------------------------------------------------------------------------- 1 | use client::Process; 2 | use simple_logger::SimpleLogger; 3 | 4 | fn main() { 5 | SimpleLogger::new().init().unwrap(); 6 | 7 | let bytes = include_bytes!("../../target/release/example.dll"); 8 | 9 | let process = Process::by_name("dummy.exe").expect("Failed to open process"); 10 | process.manual_map(bytes).expect("Failed to map process"); 11 | } 12 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | log = "0.4.14" 8 | thiserror = "1.0.30" 9 | pelite = "0.10.0" 10 | 11 | winapi = { version = "0.3.9", features = [ 12 | "winnt", 13 | "libloaderapi", 14 | "winuser", 15 | "errhandlingapi", 16 | "processthreadsapi", 17 | "synchapi", 18 | "winbase" 19 | ] } 20 | ntapi = "0.4.1" 21 | 22 | [dev-dependencies] 23 | simple_logger = "5.0.0" 24 | -------------------------------------------------------------------------------- /client/examples/dummy.rs: -------------------------------------------------------------------------------- 1 | use winapi::um::libloaderapi::LoadLibraryA; 2 | 3 | fn main() { 4 | // Dummy process that does nothing and is just for for the injection testing. 5 | 6 | // Make sure all the required libraries are loaded. 7 | // 8 | unsafe { 9 | LoadLibraryA("user32.dll\0".as_ptr() as _); 10 | LoadLibraryA("kernel32.dll\0".as_ptr() as _); 11 | } 12 | 13 | println!("Started the dummy process."); 14 | println!("Process id: {:?}", std::process::id()); 15 | 16 | loop {} 17 | } 18 | -------------------------------------------------------------------------------- /example/src/lib.rs: -------------------------------------------------------------------------------- 1 | use winapi::shared::minwindef::{BOOL, DWORD, HINSTANCE, LPVOID, TRUE}; 2 | use winapi::um::winnt::DLL_PROCESS_ATTACH; 3 | use winapi::um::winuser::MessageBoxA; 4 | 5 | #[no_mangle] 6 | #[allow(non_snake_case)] 7 | pub unsafe extern "system" fn DllMain( 8 | _module: HINSTANCE, 9 | call_reason: DWORD, 10 | _reserved: LPVOID, 11 | ) -> BOOL { 12 | if call_reason == DLL_PROCESS_ATTACH { 13 | MessageBoxA( 14 | 0 as _, 15 | "Rust DLL injected!\0".as_ptr() as _, 16 | "Rust DLL\0".as_ptr() as _, 17 | 0x0, 18 | ); 19 | 20 | TRUE 21 | } else { 22 | TRUE 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum ClientError { 5 | #[error("allocation failed: {0}")] 6 | Alloc(u32), 7 | 8 | #[error("failed to map image: {0}")] 9 | MapImage(ManualMapError), 10 | 11 | #[error("failed to copy image into the target process: {0}")] 12 | CopyMemory(u32), 13 | 14 | #[error("failed to create remote thread: {0}")] 15 | CreateRemoteThread(u32), 16 | } 17 | 18 | #[derive(Error, Debug)] 19 | pub enum ManualMapError { 20 | #[error("failed to rebase image ({0})")] 21 | Rebase(pelite::Error), 22 | 23 | #[error("failed to resolve imports ({0})")] 24 | Imports(pelite::Error), 25 | } 26 | -------------------------------------------------------------------------------- /client/src/mmap.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ManualMapError; 2 | use pelite::image::{IMAGE_REL_BASED_DIR64, IMAGE_REL_BASED_HIGHLOW}; 3 | use pelite::pe64::imports::Desc; 4 | use pelite::pe64::imports::Import; 5 | use pelite::pe64::PeFile; 6 | use pelite::pe64::PeObject; 7 | use pelite::pe64::{Pe, Va}; 8 | use pelite::util::CStr; 9 | use pelite::Align; 10 | use std::{iter, ptr, slice}; 11 | 12 | pub struct ManualMapper<'a> { 13 | pe: PeFile<'a>, 14 | } 15 | 16 | impl<'a> ManualMapper<'a> { 17 | pub fn new(bytes: &'a [u8]) -> Self { 18 | log::info!("Loading image with {} bytes", bytes.len()); 19 | 20 | Self { 21 | pe: PeFile::from_bytes(bytes).unwrap(), 22 | } 23 | } 24 | 25 | pub fn image_size(&self) -> usize { 26 | self.pe.optional_header().SizeOfImage as usize 27 | } 28 | 29 | pub fn mmap_image( 30 | &mut self, 31 | image_base: usize, 32 | resolve_import: F, 33 | ) -> Result<(Vec, usize), ManualMapError> 34 | where 35 | F: Fn(&CStr, &CStr) -> Option, 36 | { 37 | // Note: TLS and SEH are currently not supported 38 | 39 | let mut image = unsafe { self.copy() }; 40 | unsafe { self.rebase(image.as_mut(), image_base)? }; 41 | unsafe { self.resolve_imports(image.as_mut(), resolve_import)? }; 42 | 43 | let entry_point = image_base + self.pe.optional_header().AddressOfEntryPoint as usize; 44 | 45 | Ok((image, entry_point)) 46 | } 47 | 48 | /// Copies the headers and raw section data from to the destination image that should be mapped. 49 | unsafe fn copy(&mut self) -> Vec { 50 | let mut image = vec![0; self.image_size()]; 51 | 52 | let src = self.pe.image().as_ptr(); 53 | 54 | for section in self.pe.section_headers() { 55 | // Skip useless sections 56 | // 57 | if let Ok(".rsrc") | Ok(".reloc") | Ok(".idata") = section.name() { 58 | log::info!("Skipping section {}", section.name().unwrap()); 59 | continue; 60 | } 61 | 62 | // Get the src pointer depending on src alignment 63 | // 64 | let pointer = match self.pe.align() { 65 | Align::File => section.PointerToRawData as usize, 66 | Align::Section => section.VirtualAddress as usize, 67 | }; 68 | 69 | // Write section data 70 | // 71 | ptr::copy_nonoverlapping( 72 | src.add(pointer), 73 | image.as_mut_ptr().offset(section.VirtualAddress as isize), 74 | section.SizeOfRawData as usize, 75 | ); 76 | } 77 | 78 | image 79 | } 80 | 81 | unsafe fn rebase(&mut self, image: &mut [u8], image_base: usize) -> Result<(), ManualMapError> { 82 | log::info!("Rebasing image to {:x}", image_base); 83 | 84 | // Offset all absolute pointers by this delta to correct them from the old ImageBase 85 | // to the new base of the allocated memory. 86 | // 87 | let delta = image_base.wrapping_sub(self.pe.optional_header().ImageBase as usize); 88 | 89 | log::info!("Rebase delta: {:x}", delta); 90 | 91 | // If the module is loaded at its preferred base address then no relocation is necessary 92 | // 93 | if delta == 0 { 94 | log::info!("No rebasing necessary"); 95 | return Ok(()); 96 | } 97 | 98 | // Correct all base relocations by this delta 99 | // 100 | let relocs = self.pe.base_relocs().map_err(ManualMapError::Rebase)?; 101 | 102 | let image_ptr = image.as_mut_ptr() as *mut u8; 103 | for block in relocs.iter_blocks() { 104 | for word in block.words() { 105 | let rva = block.rva_of(word); 106 | 107 | // These only apply for x64 108 | // 109 | let ty = block.type_of(word); 110 | if ty == IMAGE_REL_BASED_HIGHLOW || ty == IMAGE_REL_BASED_DIR64 { 111 | let ptr = image_ptr.offset(rva as isize) as *mut usize; 112 | let original = ptr.read_volatile(); 113 | let fixed_address = original.wrapping_add(delta); 114 | ptr.write_volatile(fixed_address); 115 | 116 | log::info!( 117 | "Adjusted data at {:p} (rva = {:x}) from {:x} to {:x}", 118 | ptr, 119 | rva, 120 | original, 121 | fixed_address 122 | ); 123 | } 124 | } 125 | } 126 | 127 | Ok(()) 128 | } 129 | 130 | unsafe fn resolve_imports( 131 | &mut self, 132 | image: &mut [u8], 133 | resolve_import: F, 134 | ) -> Result<(), ManualMapError> 135 | where 136 | F: Fn(&CStr, &CStr) -> Option, 137 | { 138 | for import_descriptor in self.pe.imports().map_err(ManualMapError::Imports)? { 139 | self.resolve_import(image, import_descriptor, &resolve_import)?; 140 | } 141 | 142 | Ok(()) 143 | } 144 | 145 | unsafe fn resolve_import( 146 | &mut self, 147 | image: &mut [u8], 148 | desc: Desc, 149 | resolve_import: &F, 150 | ) -> Result<(), ManualMapError> 151 | where 152 | F: Fn(&CStr, &CStr) -> Option, 153 | { 154 | let dll_name = desc.dll_name().unwrap(); 155 | 156 | // Grab the import name table for the desired imports and the export table from the dependency 157 | // 158 | let int = desc 159 | .int() 160 | .map_err(ManualMapError::Imports)? 161 | .collect::>(); 162 | 163 | // Grab the IAT to write the pointers to 164 | // 165 | let iat_ptr = image.as_mut_ptr().offset(desc.image().FirstThunk as isize) as *mut Va; 166 | let iat = slice::from_raw_parts_mut(iat_ptr, int.len()); 167 | 168 | // Resolve the imported functions one by one 169 | // 170 | for (import, dest) in iter::zip(int, iat) { 171 | if let Ok(Import::ByName { name, .. }) = import { 172 | let Some(import) = resolve_import(dll_name, name) else { 173 | log::error!("Couldn't find import for: {:?} - {:?}", dll_name, name); 174 | continue; 175 | }; 176 | 177 | log::info!("Resolved import {}", name); 178 | 179 | // And write the exported VA to the IAT 180 | // 181 | *dest = import as Va; 182 | } 183 | } 184 | 185 | Ok(()) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /client/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ClientError; 2 | use crate::mmap::ManualMapper; 3 | use ntapi::ntexapi::{ 4 | NtQuerySystemInformation, SystemProcessInformation, SYSTEM_PROCESS_INFORMATION, 5 | }; 6 | use pelite::util::CStr; 7 | use std::ptr; 8 | use winapi::shared::minwindef::{FALSE, ULONG}; 9 | use winapi::shared::ntdef::NULL; 10 | use winapi::shared::ntstatus::{STATUS_INFO_LENGTH_MISMATCH, STATUS_SUCCESS}; 11 | use winapi::um::errhandlingapi::GetLastError; 12 | use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; 13 | use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA}; 14 | use winapi::um::memoryapi::{VirtualAllocEx, WriteProcessMemory}; 15 | 16 | use winapi::um::processthreadsapi::{CreateRemoteThread, GetCurrentProcess, OpenProcess}; 17 | use winapi::um::synchapi::WaitForSingleObject; 18 | use winapi::um::winbase::INFINITE; 19 | use winapi::um::winnt::{ 20 | DLL_PROCESS_ATTACH, HANDLE, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE, 21 | PROCESS_ALL_ACCESS, PVOID, 22 | }; 23 | 24 | pub mod error; 25 | pub mod mmap; 26 | 27 | pub type DllEntrypoint = unsafe extern "C" fn(module: u64, reason: u32, reserved: u64) -> bool; 28 | 29 | #[derive(Debug)] 30 | pub struct Process { 31 | handle: HANDLE, 32 | } 33 | 34 | impl Process { 35 | /// Opens the current process. 36 | pub fn current() -> Option { 37 | let handle = unsafe { GetCurrentProcess() }; 38 | 39 | Some(Self { handle }) 40 | } 41 | 42 | /// Returns the name of the specified process structure. 43 | /// There's some exceptions: 44 | /// - 0: Idle 45 | /// - 4: System 46 | fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: usize) -> String { 47 | let name = &process.ImageName; 48 | if name.Buffer.is_null() { 49 | match process_id { 50 | 0 => "Idle".to_owned(), 51 | 4 => "System".to_owned(), 52 | _ => format!(" Process {}", process_id), 53 | } 54 | } else { 55 | let slice = unsafe { 56 | std::slice::from_raw_parts( 57 | name.Buffer, 58 | // The length is in bytes, not the length of string 59 | name.Length as usize / std::mem::size_of::(), 60 | ) 61 | }; 62 | 63 | String::from_utf16_lossy(slice) 64 | } 65 | } 66 | 67 | /// Returns a list of the currently running processes. 68 | fn get_process_list() -> Option> { 69 | // https://github.com/GuillaumeGomez/sysinfo/blob/master/src/windows/system.rs#L274 70 | 71 | let mut process_information: Vec = Vec::with_capacity(512 * 1024); 72 | 73 | // 74 | // Get the process information 75 | // 76 | loop { 77 | let mut cb_needed = 0; 78 | let nt_status = unsafe { 79 | NtQuerySystemInformation( 80 | SystemProcessInformation, 81 | process_information.as_mut_ptr() as PVOID, 82 | process_information.capacity() as ULONG, 83 | &mut cb_needed, 84 | ) 85 | }; 86 | match nt_status { 87 | STATUS_SUCCESS => break, 88 | STATUS_INFO_LENGTH_MISMATCH => { 89 | let new_buffer_size = if cb_needed == 0 { 90 | process_information.capacity() * 2 91 | } else { 92 | // allocating a few more kilo bytes just in case there are some new process 93 | // kicked in since new call to NtQuerySystemInformation 94 | const EXTRA_BUFFER: usize = 1024 * 10; 95 | cb_needed as usize + EXTRA_BUFFER 96 | }; 97 | process_information.reserve(new_buffer_size); 98 | } 99 | _ => return None, 100 | } 101 | } 102 | 103 | // Parse the data block to get process information 104 | let mut process_list: Vec<(i32, String)> = Vec::with_capacity(500); 105 | let mut process_information_offset = 0; 106 | 107 | loop { 108 | // Convert to struct 109 | // 110 | 111 | #[allow(clippy::cast_ptr_alignment)] 112 | let p = unsafe { 113 | process_information 114 | .as_ptr() 115 | .offset(process_information_offset) 116 | as *const SYSTEM_PROCESS_INFORMATION 117 | }; 118 | let pi = unsafe { &*p }; 119 | 120 | // Add to list 121 | // 122 | let pid = pi.UniqueProcessId as i32; 123 | let name = Self::get_process_name(pi, pi.UniqueProcessId as usize); 124 | 125 | process_list.push((pid, name)); 126 | 127 | if pi.NextEntryOffset == 0 { 128 | break; 129 | } 130 | 131 | process_information_offset += pi.NextEntryOffset as isize; 132 | } 133 | 134 | Some(process_list) 135 | } 136 | 137 | /// Returns the process id of the specified process name. 138 | pub fn by_name(name: &str) -> Option { 139 | let process_list = Self::get_process_list()?; 140 | 141 | for (pid, process_name) in process_list { 142 | log::info!("Found process {:?}", process_name); 143 | 144 | if process_name == name { 145 | return Self::open(pid as u32); 146 | } 147 | } 148 | 149 | None 150 | } 151 | 152 | /// Opens the specified process. 153 | pub fn open(pid: u32) -> Option { 154 | let handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) }; 155 | if handle != INVALID_HANDLE_VALUE { 156 | Some(Self { handle }) 157 | } else { 158 | None 159 | } 160 | } 161 | 162 | /// Checks whether we opened the current process. 163 | pub fn is_current(&self) -> bool { 164 | // Here `INVALID_HANDLE_VALUE` (which is just `-1`) is used a special constant 165 | // to indicate that the handle is for the current process. 166 | // 167 | // See: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess 168 | // 169 | self.handle == INVALID_HANDLE_VALUE 170 | } 171 | 172 | /// Checks whether we opened a remote process. 173 | pub fn is_remote(&self) -> bool { 174 | !self.is_current() 175 | } 176 | 177 | /// Allocates RWX memory in the specified process. 178 | /// 179 | /// ## Parameters 180 | /// 181 | /// - `process`: The process to allocate memory in. If `None`, the current process is used. 182 | /// - `size`: The size of the memory to allocate. 183 | /// 184 | /// ## Returns 185 | /// 186 | /// Pointer to the allocated memory. If the allocation failed, an error is returned. 187 | /// 188 | pub unsafe fn alloc(&self, image_size: usize) -> Result<*mut u8, ClientError> { 189 | let memory = VirtualAllocEx( 190 | self.handle, 191 | ptr::null_mut(), 192 | image_size, 193 | MEM_COMMIT | MEM_RESERVE, 194 | PAGE_EXECUTE_READWRITE, 195 | ); 196 | if memory != NULL { 197 | Ok(memory as *mut u8) 198 | } else { 199 | Err(ClientError::Alloc(GetLastError())) 200 | } 201 | } 202 | 203 | /// Copies the specified memory to the destination pointer in the current process. 204 | pub unsafe fn copy_memory(&self, dst_ptr: *mut u8, data: Vec) -> Result<(), ClientError> { 205 | let result = WriteProcessMemory( 206 | self.handle, 207 | dst_ptr as _, 208 | data.as_ptr() as _, 209 | data.len(), 210 | ptr::null_mut(), 211 | ); 212 | if result != FALSE { 213 | Ok(()) 214 | } else { 215 | Err(ClientError::CopyMemory(GetLastError())) 216 | } 217 | } 218 | 219 | /// Manual maps the specified dll into the specified process. 220 | /// 221 | /// ## Parameters 222 | /// 223 | /// - `process`: If `None`, the current process is used. 224 | /// - `dll`: The bytes of the dll that should be injected. 225 | /// 226 | pub fn manual_map(&self, dll: &'static [u8]) -> Result<(), ClientError> { 227 | let mut mm = ManualMapper::new(dll); 228 | 229 | // Allocate memory 230 | // 231 | log::info!("Allocating memory in target process"); 232 | let image_size = mm.image_size(); 233 | let memory = unsafe { self.alloc(image_size) }?; 234 | 235 | // Map image and copy into target process 236 | // 237 | log::info!("Mapping image into target process"); 238 | let (image, entrypoint) = mm 239 | .mmap_image(memory as *mut _ as usize, |module, import| { 240 | resolve_import(module, import) 241 | }) 242 | .map_err(ClientError::MapImage)?; 243 | 244 | unsafe { self.copy_memory(memory, image) }?; 245 | 246 | // Call entrypoint 247 | // 248 | unsafe { self.call_entrypoint(memory as usize, entrypoint)? }; 249 | 250 | Ok(()) 251 | } 252 | 253 | /// Calls the `DllEntrypoint` of the mapped image in the specified process. There are different 254 | /// implementations required for remote and local processes. 255 | /// 256 | /// ## Local Process 257 | /// 258 | /// We can just create a function pointer to the entry point and call it. This works, because the 259 | /// memory is in the same address space. 260 | /// 261 | /// ## Remote Process 262 | /// 263 | /// Remote processes are a little more tricky. We have to create a thread in the target process 264 | /// but we can't call DllEntrypoint directly because it expects 3 parameters. Instead, we have to 265 | /// create a stub shellcode, that will just call the DllEntrypoint with the correct parameters. 266 | /// 267 | pub unsafe fn call_entrypoint( 268 | &self, 269 | image_base: usize, 270 | entrypoint: usize, 271 | ) -> Result<(), ClientError> { 272 | if self.is_remote() { 273 | // Create the shellcode that calls the entry point. 274 | // 275 | #[rustfmt::skip] 276 | let mut shellcode = vec![ 277 | 0x48, 0x83, 0xEC, 0x28, // sub rsp, 28h 278 | 0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rcx, image_base ; hinstDLL 279 | 0x48, 0xc7, 0xc2, 0x01, 0x00, 0x00, 0x00, // mov rdx, 1 ; fdwReason 280 | 0x4d, 0x31, 0xC0, // xor r8, r8 ; lpvReserved 281 | 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rax, entrypoint 282 | 0xFF, 0xD0, // call rax 283 | 0x48, 0x83, 0xC4, 0x28, // add rsp, 28h 284 | 0xC3, // ret 285 | ]; 286 | 287 | (shellcode.as_mut_ptr().offset(6) as *mut u64).write_volatile(image_base as u64); 288 | (shellcode.as_mut_ptr().offset(26) as *mut u64).write_volatile(entrypoint as u64); 289 | 290 | // Allocate the shellcode in the target process and call it 291 | // 292 | let memory = self.alloc(shellcode.len())?; 293 | log::info!("Shellcode memory allocated at {:x}", memory as usize); 294 | 295 | log::info!("Copying shellcode into target process"); 296 | self.copy_memory(memory, shellcode)?; 297 | 298 | log::info!("Creating thread to execute shellcode in target process"); 299 | let thread_handle = CreateRemoteThread( 300 | self.handle, 301 | ptr::null_mut(), 302 | 0, 303 | Some(std::mem::transmute(memory as usize)), 304 | ptr::null_mut(), 305 | 0, 306 | ptr::null_mut(), 307 | ); 308 | if thread_handle == INVALID_HANDLE_VALUE { 309 | return Err(ClientError::CreateRemoteThread(GetLastError())); 310 | } 311 | 312 | log::info!("Waiting for thread to finish"); 313 | WaitForSingleObject(thread_handle, INFINITE); 314 | } else { 315 | log::info!("Calling entrypoint in current process at {:x}", entrypoint); 316 | 317 | let entrypoint = core::mem::transmute::<_, DllEntrypoint>(entrypoint); 318 | 319 | (entrypoint)(0, DLL_PROCESS_ATTACH, 0); 320 | } 321 | 322 | Ok(()) 323 | } 324 | } 325 | 326 | impl Drop for Process { 327 | fn drop(&mut self) { 328 | unsafe { CloseHandle(self.handle) }; 329 | } 330 | } 331 | 332 | /// Finds the specified import address in the specified module and returns it. 333 | /// 334 | /// ## Why does this work for remote injection? 335 | /// 336 | /// We can abuse a neat little detail in Windows. When we load a library into a process, it 337 | /// will always be mapped at the same location. Because of this, it doesn't matter whether we 338 | /// load it inside this process or the target process. 339 | /// 340 | /// This is because of Copy On Write. When we load a library into a process, it 341 | /// will be **copied** into the process's address space. This means that the library will be 342 | /// mapped at the same location in both processes. 343 | /// 344 | pub fn resolve_import(module: &CStr, symbol: &CStr) -> Option { 345 | unsafe { 346 | let handle = LoadLibraryA(module.as_ptr() as _); 347 | match GetProcAddress(handle, symbol.as_ptr() as _) as usize { 348 | 0 => None, 349 | n => Some(n), 350 | } 351 | } 352 | } 353 | --------------------------------------------------------------------------------