├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── error.rs ├── external │ ├── memory.rs │ ├── mod.rs │ └── process.rs ├── internal │ ├── injections.rs │ ├── macros.rs │ ├── memory.rs │ ├── memory_region.rs │ ├── mod.rs │ └── process_info.rs └── lib.rs └── tests └── internal.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: windows-latest 16 | 17 | strategy: 18 | matrix: 19 | architecture: [x86_64-pc-windows-msvc, i686-pc-windows-msvc] 20 | 21 | name: Test on ${{ matrix.architecture }} 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Add ${{ matrix.architecture }} target 25 | run: rustup target add ${{ matrix.architecture }} 26 | - name: Build 27 | run: cargo build --target=${{ matrix.architecture }} --verbose 28 | - name: Run tests 29 | run: cargo test --target=${{ matrix.architecture }} --verbose -- --test-threads=1 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.34" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" 10 | 11 | [[package]] 12 | name = "memory-rs" 13 | version = "0.2.6" 14 | dependencies = [ 15 | "anyhow", 16 | "windows-sys", 17 | ] 18 | 19 | [[package]] 20 | name = "windows-sys" 21 | version = "0.35.0" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "c700bb45cfcbdb738ce92c41fc13e512514d4eaf6a99e8c87e2260a227175c16" 24 | dependencies = [ 25 | "windows_aarch64_msvc", 26 | "windows_i686_gnu", 27 | "windows_i686_msvc", 28 | "windows_x86_64_gnu", 29 | "windows_x86_64_msvc", 30 | ] 31 | 32 | [[package]] 33 | name = "windows_aarch64_msvc" 34 | version = "0.35.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "db3bc5134e8ce0da5d64dcec3529793f1d33aee5a51fc2b4662e0f881dd463e6" 37 | 38 | [[package]] 39 | name = "windows_i686_gnu" 40 | version = "0.35.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "0343a6f35bf43a07b009b8591b78b10ea03de86b06f48e28c96206cd0f453b50" 43 | 44 | [[package]] 45 | name = "windows_i686_msvc" 46 | version = "0.35.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "1acdcbf4ca63d8e7a501be86fee744347186275ec2754d129ddeab7a1e3a02e4" 49 | 50 | [[package]] 51 | name = "windows_x86_64_gnu" 52 | version = "0.35.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "893c0924c5a990ec73cd2264d1c0cba1773a929e1a3f5dbccffd769f8c4edebb" 55 | 56 | [[package]] 57 | name = "windows_x86_64_msvc" 58 | version = "0.35.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "a29bd61f32889c822c99a8fdf2e93378bd2fae4d7efd2693fab09fcaaf7eff4b" 61 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memory-rs" 3 | version = "0.2.6" 4 | authors = ["Sebastian Aedo "] 5 | license = "MIT" 6 | description = "Library for game hacking in Windows." 7 | edition = "2018" 8 | 9 | [dependencies] 10 | anyhow = "1.0.34" 11 | 12 | [target.'cfg(windows)'.dependencies] 13 | windows-sys = { version = "0.35", features = [ "Win32_System_LibraryLoader", "Win32_Foundation", "Win32_System_Threading", "Win32_System_ProcessStatus", "Win32_System_Memory", "Win32_System_Diagnostics_Debug", "Win32_System_Diagnostics_ToolHelp", "Win32_System_SystemServices", "Win32_Security" ] } 14 | 15 | [features] 16 | default = ["impl-drop"] 17 | impl-drop = [] 18 | 19 | [package.metadata.docs.rs] 20 | targets = ["x86_64-pc-windows-msvc"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sebastián A. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memory-rs 2 | **This is a work in progress.** 3 | 4 | A library written to facilitate game modding with Rust. 5 | 6 | This library has the basics for making code injections, aobscan and code 7 | patching. 8 | 9 | # Usage 10 | The example is a little bit extensive but hopefully makes it clear on how 11 | to use it. 12 | 13 | ```rust 14 | use memory_rs::generate_aob_pattern; 15 | use memory_rs::internal::process_info::ProcessInfo; 16 | use memory_rs::internal::injections::*; 17 | 18 | memory_rs::scoped_no_mangle! { 19 | // In reality, this `my_function_in_assembly` should be an extern to an 20 | // asm label which exposes the start of a shellcode, that you'd inject in 21 | // the future. 22 | my_function_in_assembly: u8 = 0x0; 23 | my_function_in_assembly_jmp_back_addr: usize = 0x0; 24 | } 25 | 26 | 27 | // function wrapper to be called by DllMain 28 | pub unsafe extern "system" fn wrapper(lib: *mut std::ffi::c_void) -> u32 { 29 | // ... 30 | match patch() { 31 | Ok(_) => println!("Everything is OK"), 32 | Err(e) => println!("Error: {}", e), 33 | }; 34 | 35 | 0 36 | } 37 | 38 | fn patch() -> Result<(), Box> { 39 | // Get some basic information of the process (like base address and binary 40 | // size) 41 | let proc_inf = ProcessInfo::new(Some("RDR2.exe"))?; 42 | 43 | let mut enable_hots_addr = { 44 | // Scan for a pattern on the game's memory to get the address of 45 | // where you want to do some injections 46 | let memory_pattern = generate_aob_pattern![ 47 | 0xFF, 0x90, _, 0x01, 0x00, 0x00, 0x84, 0xC0, 0x74, 0x05, 0x4D, 48 | 0x85, 0xE4 49 | ]; 50 | let region = &proc_inf.region; 51 | region.scan_aob(&memory_pattern)? 52 | .ok_or("Couldn't find enable_hots_addr")? - 0xA 53 | }; 54 | 55 | // We use Detour since it's scope sensitive (if it fails it will deinject 56 | // automatically 57 | let mut enable_hots_det = unsafe { Detour::new( 58 | enable_hots_addr, 59 | 16, 60 | &my_function_in_assembly as *const u8 as usize, 61 | Some(&mut my_function_in_assembly_jmp_back_addr)) 62 | }; 63 | 64 | enable_hots_det.inject(); 65 | 66 | let black_bars_addr = { 67 | let memory_pattern = generate_aob_pattern![ 68 | 0x0F, 0x86, _, _, 0xAA, 0xBB 69 | ]; 70 | let region = &proc_inf.region; 71 | region.scan_aob(&memory_pattern)? 72 | .ok_or("Couldn't find black_bars_addr")? 73 | }; 74 | 75 | // nop 5 bytes, when Injection goes out of scope, it will reinject 76 | // the original bytes. 77 | let mut remove_black_bars = Injection::new(black_bars_addr, vec![0x90; 5]); 78 | 79 | remove_black_bars.inject(); 80 | 81 | loop {} 82 | } 83 | 84 | // This will generate the DllMain required to create a DLL in Windows. 85 | memory_rs::main_dll!(wrapper); 86 | ``` 87 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq)] 2 | pub enum ErrorType { 3 | WinAPI, 4 | Internal, 5 | } 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct Error { 9 | kind: ErrorType, 10 | msg: String, 11 | } 12 | 13 | impl Error { 14 | pub fn new(kind: ErrorType, msg: String) -> Error { 15 | Error { kind, msg } 16 | } 17 | 18 | pub fn kind(&self) -> ErrorType { 19 | self.kind 20 | } 21 | 22 | pub fn msg(&self) -> String { 23 | self.msg.clone() 24 | } 25 | } 26 | 27 | impl std::error::Error for Error {} 28 | 29 | impl std::fmt::Display for Error { 30 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 31 | write!(f, "{}", self.msg) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/external/memory.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | use windows_sys::Win32::{ 4 | Foundation::HANDLE, 5 | System::{ 6 | Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory}, 7 | Memory::{ 8 | VirtualAllocEx, VirtualProtectEx, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE, 9 | }, 10 | }, 11 | }; 12 | 13 | /// Kept for legacy purposes. 14 | 15 | pub fn get_aob(h_process: HANDLE, ptr: *const c_void, n: usize) -> Vec { 16 | let mut read = 0; 17 | let mut buffer: Vec = vec![0; n]; 18 | 19 | unsafe { 20 | ReadProcessMemory( 21 | h_process, 22 | ptr as *const c_void, 23 | buffer.as_mut_ptr() as *mut c_void, 24 | n, 25 | &mut read, 26 | ); 27 | } 28 | 29 | assert_eq!(n, read, "get_aob isn't the requested size"); 30 | 31 | buffer 32 | } 33 | 34 | pub fn write_aob(h_process: HANDLE, ptr: usize, source: &[u8]) -> usize { 35 | let mut protection_bytes: u32 = 0x0; 36 | let c_addr = ptr; 37 | let size = source.len(); 38 | let mut written = 0; 39 | 40 | unsafe { 41 | VirtualProtectEx( 42 | h_process, 43 | c_addr as *const c_void, 44 | size, 45 | PAGE_EXECUTE_READWRITE, 46 | &mut protection_bytes as _, 47 | ); 48 | 49 | WriteProcessMemory( 50 | h_process, 51 | c_addr as *const c_void, 52 | source[..].as_ptr() as *const c_void, 53 | size, 54 | &mut written, 55 | ); 56 | 57 | VirtualProtectEx( 58 | h_process, 59 | c_addr as *const c_void, 60 | size, 61 | protection_bytes, 62 | &mut protection_bytes as _, 63 | ); 64 | } 65 | 66 | assert_eq!( 67 | written, 68 | source.len(), 69 | "write_aob didn't write the correct number of bytes" 70 | ); 71 | 72 | written 73 | } 74 | 75 | pub fn write_nops(h_process: HANDLE, ptr: usize, n: usize) { 76 | let nops: Vec = vec![0x90; n]; 77 | write_aob(h_process, ptr, &nops); 78 | } 79 | 80 | pub fn hook_function(h_process: HANDLE, to_hook: usize, f: usize, len: usize) { 81 | assert!(len >= 5, "Not enough space to inject the shellcode"); 82 | 83 | let mut current_protection: u32 = 0x0; 84 | 85 | unsafe { 86 | VirtualProtectEx( 87 | h_process, 88 | to_hook as *const c_void, 89 | len, 90 | PAGE_EXECUTE_READWRITE, 91 | &mut current_protection as _, 92 | ); 93 | } 94 | 95 | // just in case, we nop the space where we are injecting stuff 96 | let nops = vec![0x90; len]; 97 | write_aob(h_process, to_hook, &nops); 98 | 99 | let _diff = f as i64 - to_hook as i64; 100 | let relative_address: u32 = (_diff as u32 - 5) as u32; 101 | let relative_aob: [u8; 4] = relative_address.to_le_bytes(); 102 | 103 | let mut instructions: Vec = vec![0xE8]; 104 | instructions.extend_from_slice(&relative_aob[..]); 105 | 106 | let written = write_aob(h_process, to_hook, &instructions); 107 | assert_eq!(written, 5); 108 | 109 | unsafe { 110 | VirtualProtectEx( 111 | h_process, 112 | to_hook as *const c_void, 113 | len, 114 | current_protection, 115 | &mut current_protection as _, 116 | ); 117 | } 118 | } 119 | 120 | /// This function injects a 121 | /// shellcode on a desired address. 122 | /// # Safety 123 | /// This function is highly unsafe because it will 124 | /// change assembly code of the target program, so 125 | /// be aware of the crashing, wrong-results, etc. 126 | pub unsafe fn inject_shellcode( 127 | h_process: HANDLE, 128 | module_base_address: usize, 129 | entry_point: *const c_void, 130 | instruction_size: usize, 131 | f_start: *const u8, 132 | f_end: *const u8, 133 | ) -> *const c_void { 134 | let f_size = f_end as usize - f_start as usize; 135 | // get the aob of the function 136 | let shellcode_bytes: &'static [u8] = std::slice::from_raw_parts(f_start, f_size); 137 | 138 | let mut shellcode_space: *const c_void = std::ptr::null(); 139 | // try to allocate near module 140 | for i in 1..1000 { 141 | let current_address = module_base_address - (0x1000 * i); 142 | shellcode_space = VirtualAllocEx( 143 | h_process, 144 | current_address as _, 145 | 0x1000_usize, 146 | MEM_RESERVE | MEM_COMMIT, 147 | PAGE_EXECUTE_READWRITE, 148 | ); 149 | 150 | if !shellcode_space.is_null() { 151 | break; 152 | } 153 | } 154 | 155 | let written = write_aob(h_process, shellcode_space as _, &shellcode_bytes.to_vec()); 156 | assert_eq!(written, f_size, "The size of the injection doesnt match"); 157 | 158 | let module_injection_address = module_base_address + (entry_point as usize); 159 | hook_function( 160 | h_process, 161 | module_injection_address, 162 | shellcode_space as _, 163 | instruction_size, 164 | ); 165 | 166 | shellcode_space 167 | } 168 | -------------------------------------------------------------------------------- /src/external/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod memory; 2 | pub mod process; 3 | -------------------------------------------------------------------------------- /src/external/process.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_void, CStr}; 2 | use std::io::Error; 3 | 4 | use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}; 5 | use windows_sys::Win32::System::Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory}; 6 | use windows_sys::Win32::System::Diagnostics::ToolHelp::{ 7 | CreateToolhelp32Snapshot, Module32First, Module32Next, Process32First, Process32Next, 8 | MODULEENTRY32, PROCESSENTRY32, TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, TH32CS_SNAPPROCESS, 9 | }; 10 | use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS}; 11 | 12 | pub struct Process { 13 | pub h_process: HANDLE, 14 | pub module_base_address: usize, 15 | } 16 | 17 | impl Process { 18 | pub fn new(process_name: &str) -> Result { 19 | let process_id = get_process_id(process_name)?; 20 | let module_base_address = get_module_base(process_id, process_name)?; 21 | 22 | let h_process = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false as i32, process_id) }; 23 | 24 | if h_process == 0 { 25 | return Err(Error::last_os_error()); 26 | } 27 | 28 | Ok(Process { 29 | h_process, 30 | module_base_address, 31 | }) 32 | } 33 | 34 | /// Writes an array of bytes (as vectors) into the desired address. 35 | /// It can take relative or absolute values. 36 | pub fn write_aob(&self, ptr: usize, data: &[u8], absolute: bool) { 37 | let addr = if absolute { 38 | ptr as _ 39 | } else { 40 | self.module_base_address + ptr 41 | }; 42 | 43 | crate::external::memory::write_aob(self.h_process, addr as _, &data); 44 | } 45 | 46 | /// Writes `n` nops into the desired address 47 | /// It can take relative or absolute values. 48 | pub fn write_nops(&self, ptr: usize, n: usize, absolute: bool) { 49 | let addr = if absolute { 50 | ptr as _ 51 | } else { 52 | self.module_base_address + ptr 53 | }; 54 | 55 | crate::external::memory::write_nops(self.h_process, addr as _, n); 56 | } 57 | 58 | /// Reads `n` bytes from the desired address 59 | /// It can take relative or absolute values. 60 | pub fn get_aob(&self, ptr: usize, n: usize, absolute: bool) -> Vec { 61 | let addr = if absolute { 62 | ptr as _ 63 | } else { 64 | self.module_base_address + ptr 65 | }; 66 | 67 | let output: Vec = crate::external::memory::get_aob(self.h_process, addr as _, n); 68 | 69 | output 70 | } 71 | 72 | // TODO: Move this function out of process because it should be in 73 | // memory/mod.rs. 74 | pub fn read_value(&self, ptr: usize, absolute: bool) -> OutputType { 75 | let addr = if absolute { 76 | ptr as _ 77 | } else { 78 | self.module_base_address + ptr 79 | }; 80 | 81 | let mut buffer: OutputType = unsafe { std::mem::zeroed() }; 82 | let s_buffer: usize = std::mem::size_of::(); 83 | let mut read: usize = 0; 84 | 85 | unsafe { 86 | ReadProcessMemory( 87 | self.h_process, 88 | addr as *const c_void, 89 | &mut buffer as *mut OutputType as *mut c_void, 90 | s_buffer, 91 | &mut read, 92 | ); 93 | }; 94 | 95 | assert_eq!(read, s_buffer); 96 | buffer 97 | } 98 | 99 | pub fn write_value(&self, ptr: usize, output: InputType, absolute: bool) { 100 | let addr = if absolute { 101 | ptr 102 | } else { 103 | self.module_base_address + ptr 104 | }; 105 | 106 | let s: usize = std::mem::size_of::(); 107 | let mut written: usize = 0; 108 | 109 | unsafe { 110 | WriteProcessMemory( 111 | self.h_process, 112 | addr as *const c_void, 113 | (&output as *const InputType) as *mut c_void, 114 | s, 115 | &mut written, 116 | ); 117 | }; 118 | 119 | assert!(written != 0); 120 | } 121 | 122 | /// Inject an an ASM function which requires the labels start and 123 | /// end as an input, and an entry point where the position will 124 | /// be injected. 125 | /// # Safety 126 | /// This function is highly unsafe. It can fails for so many reasons 127 | /// that the user should be aware when using it. The function 128 | /// maybe could not find a code cave, it could not write the 129 | /// bytes correctly, or it could just simply fail because OS reasons. 130 | pub unsafe fn inject_shellcode( 131 | &self, 132 | entry_point: *const u32, 133 | instruction_size: usize, 134 | f_start: *const u8, 135 | f_end: *const u8, 136 | ) -> *const c_void { 137 | crate::external::memory::inject_shellcode( 138 | self.h_process, 139 | self.module_base_address as _, 140 | entry_point as _, 141 | instruction_size, 142 | f_start, 143 | f_end, 144 | ) 145 | } 146 | 147 | // pub fn read_string_array( 148 | // &self, 149 | // address: *const u32, 150 | // starting_index: usize, 151 | // ending: &[u8], 152 | // ) -> Vec<(usize, String)> { 153 | // let mut c_address = address; 154 | 155 | // let mut data: Vec<(usize, String)> = vec![]; 156 | 157 | // let mut c_index = starting_index; 158 | 159 | // let mut c_string = String::from(""); 160 | // loop { 161 | // let current_read: Vec = self.get_aob(c_address, 2, true); 162 | 163 | // if current_read[..] == *ending { 164 | // break; 165 | // } 166 | // if current_read[0] == 0x00 { 167 | // data.push((c_index, c_string)); 168 | // c_string = String::from(""); 169 | // c_index += 1; 170 | // c_address += 1; 171 | // continue; 172 | // } 173 | 174 | // c_string.push(current_read[0] as char); 175 | // c_address += 1; 176 | // } 177 | 178 | // data 179 | // } 180 | } 181 | 182 | pub fn get_process_id(process_name: &str) -> Result { 183 | let mut process_id: u32 = 0; 184 | let h_snap = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }; 185 | 186 | if h_snap == INVALID_HANDLE_VALUE { 187 | return Err(Error::last_os_error()); 188 | } 189 | 190 | let mut process_entry: PROCESSENTRY32 = unsafe { std::mem::zeroed() }; 191 | process_entry.dwSize = std::mem::size_of:: as _; 192 | 193 | unsafe { 194 | if Process32First(h_snap, &mut process_entry) == 1 { 195 | process_id = loop { 196 | let current_name = CStr::from_ptr(process_entry.szExeFile.as_ptr() as _) 197 | .to_str() 198 | .expect("No string found"); 199 | 200 | if current_name == process_name { 201 | break process_entry.th32ProcessID; 202 | } 203 | 204 | if Process32Next(h_snap, &mut process_entry) == 0 { 205 | break 0; 206 | } 207 | } 208 | } 209 | 210 | CloseHandle(h_snap); 211 | } 212 | 213 | if process_id == 0 { 214 | return Err(Error::last_os_error()); 215 | } 216 | 217 | Ok(process_id) 218 | } 219 | 220 | pub fn get_module_base(process_id: u32, module_name: &str) -> Result { 221 | let mut module_base_address = 0; 222 | let h_snap = 223 | unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, process_id) }; 224 | 225 | if h_snap == INVALID_HANDLE_VALUE { 226 | return Err(Error::last_os_error()); 227 | } 228 | 229 | let mut module_entry: MODULEENTRY32 = unsafe { std::mem::zeroed() }; 230 | module_entry.dwSize = std::mem::size_of:: as _; 231 | 232 | unsafe { 233 | if Module32First(h_snap, &mut module_entry) != 0 { 234 | module_base_address = loop { 235 | let current_name = CStr::from_ptr(module_entry.szModule.as_ptr() as _) 236 | .to_str() 237 | .expect("No string found"); 238 | 239 | if current_name == module_name { 240 | break module_entry.modBaseAddr as usize; 241 | } 242 | 243 | if Module32Next(h_snap, &mut module_entry) == 0 { 244 | break 0; 245 | } 246 | } 247 | } 248 | 249 | CloseHandle(h_snap); 250 | } 251 | 252 | if module_base_address == 0 { 253 | return Err(Error::last_os_error()); 254 | } 255 | 256 | Ok(module_base_address) 257 | } 258 | -------------------------------------------------------------------------------- /src/internal/injections.rs: -------------------------------------------------------------------------------- 1 | use std::ops::DerefMut; 2 | use std::slice::IterMut; 3 | 4 | use crate::internal::memory::{hook_function, write_aob, MemoryPattern}; 5 | use crate::internal::memory_region::*; 6 | use anyhow::{Context, Result}; 7 | 8 | /// Trait specifically designed to extend the Vec struct in order 9 | /// to easily write something like `vec.inject()` when you have a vector 10 | /// of structs that implements Inject. 11 | pub trait Inject { 12 | fn inject(&mut self); 13 | fn remove_injection(&mut self); 14 | } 15 | 16 | /// Struct that contains its entry point and original bytes. 17 | /// The purpose of this struct is that when it goes out of scope, 18 | /// it automatically removes the modified bytes in order to do a clean 19 | /// remove of the DLL. 20 | #[derive(Debug)] 21 | pub struct Detour { 22 | /// Pointer where the detour will be injected. 23 | pub entry_point: usize, 24 | /// Original bytes where the entry_point points. 25 | f_orig: Vec, 26 | 27 | /// New function where the detour will redirect. 28 | new_function: usize, 29 | 30 | /// Optional pointer that will be written the jump back if what you 31 | /// inject isn't technically a function (i.e. doesn't return) 32 | function_end: Option<&'static mut usize>, 33 | } 34 | 35 | impl Detour { 36 | pub fn new( 37 | entry_point: usize, 38 | size: usize, 39 | new_function: usize, 40 | function_end: Option<&'static mut usize>, 41 | ) -> Detour { 42 | let mut f_orig = vec![]; 43 | 44 | unsafe { 45 | let slice_ = std::slice::from_raw_parts(entry_point as *const u8, size); 46 | f_orig.extend_from_slice(slice_); 47 | } 48 | 49 | Detour { 50 | entry_point, 51 | f_orig, 52 | new_function, 53 | function_end, 54 | } 55 | } 56 | 57 | /// Creates a Detour from scan_aob. This function can fail 58 | /// in the case when the scan_aob can't find it's target. 59 | pub fn new_from_aob( 60 | scan: MemoryPattern, 61 | region: &MemoryRegion, 62 | new_function: usize, 63 | function_end: Option<&'static mut usize>, 64 | size_injection: usize, 65 | offset: Option, 66 | ) -> Result { 67 | let mut entry_point = region.scan_aob(&scan)?.context("Couldn't find aob")?; 68 | 69 | if let Some(v) = offset { 70 | entry_point = ((entry_point as isize) + v) as usize; 71 | } 72 | 73 | Ok(Detour::new( 74 | entry_point, 75 | size_injection, 76 | new_function, 77 | function_end, 78 | )) 79 | } 80 | } 81 | 82 | impl Inject for Detour { 83 | fn inject(&mut self) { 84 | unsafe { 85 | hook_function( 86 | self.entry_point, 87 | self.new_function, 88 | self.function_end.as_deref_mut(), 89 | self.f_orig.len(), 90 | ) 91 | .unwrap(); 92 | } 93 | } 94 | 95 | fn remove_injection(&mut self) { 96 | unsafe { 97 | write_aob(self.entry_point, &self.f_orig).unwrap(); 98 | } 99 | } 100 | } 101 | 102 | #[cfg(feature = "impl-drop")] 103 | impl Drop for Detour { 104 | fn drop(&mut self) { 105 | self.remove_injection(); 106 | } 107 | } 108 | 109 | /// `Injection` is a simple structure that contains an address where 110 | /// the instructions to be modified are, and the original bytes with 111 | /// the new ones. This struct is intended to be injected and removed 112 | /// easily. 113 | #[derive(Debug)] 114 | pub struct Injection { 115 | /// Entry point relative to the executable 116 | pub entry_point: usize, 117 | /// Original bytes 118 | pub f_orig: Vec, 119 | /// Bytes to be injected 120 | pub f_new: Vec, 121 | } 122 | 123 | impl Injection { 124 | pub fn new(entry_point: usize, f_new: Vec) -> Injection { 125 | let aob_size = f_new.len(); 126 | let slice = unsafe { std::slice::from_raw_parts(entry_point as *const u8, aob_size) }; 127 | let mut f_orig = Vec::new(); 128 | f_orig.extend_from_slice(slice); 129 | 130 | Injection { 131 | entry_point, 132 | f_orig, 133 | f_new, 134 | } 135 | } 136 | 137 | /// Creates a new injection using the `generate_aob_pattern` macro. 138 | /// # Example 139 | /// ``` 140 | /// # use memory_rs::internal::{injections::*, memory::MemoryPattern}; 141 | /// # use memory_rs::internal::process_info::ProcessInfo; 142 | /// # use memory_rs::generate_aob_pattern; 143 | /// # #[allow(non_upper_case_globals)] 144 | /// static arr: [u8; 5] = [0xEE, 0xAA, 0xFF, 0xBB, 0xFF]; 145 | /// # // avoid removal of arr at compilation 146 | /// # println!("{:x?}", arr); 147 | /// # let proc_inf = ProcessInfo::new(None).unwrap(); 148 | /// let mut injection = Injection::new_from_aob(&proc_inf.region, vec![0x90; 3], 149 | /// generate_aob_pattern![0xAA, _, 0xBB, 0xFF]).unwrap(); 150 | /// // With this we nop the bytes (i.e. we write 0x90) where 151 | /// // generate_aob_pattern has a match. 152 | /// injection.inject(); 153 | /// assert_eq!(&arr[1..4], &[0x90, 0x90, 0x90]); 154 | /// 155 | /// // If we remove the injection (or `injection` gets dropped) the original 156 | /// // array should be restored. 157 | /// injection.remove_injection(); 158 | /// assert_eq!(&arr[1..4], &[0xAA, 0xFF, 0xBB]); 159 | /// 160 | /// ``` 161 | pub fn new_from_aob( 162 | region: &MemoryRegion, 163 | f_new: Vec, 164 | memory_pattern: MemoryPattern, 165 | ) -> Result { 166 | let entry_point = region 167 | .scan_aob(&memory_pattern)? 168 | .context("Couldn't find aob")?; 169 | Ok(Injection::new(entry_point, f_new)) 170 | } 171 | } 172 | 173 | impl Inject for Injection { 174 | fn inject(&mut self) { 175 | unsafe { 176 | write_aob(self.entry_point, &(self.f_new)).unwrap(); 177 | } 178 | } 179 | 180 | fn remove_injection(&mut self) { 181 | unsafe { 182 | write_aob(self.entry_point, &(self.f_orig)).unwrap(); 183 | } 184 | } 185 | } 186 | 187 | #[cfg(feature = "impl-drop")] 188 | impl Drop for Injection { 189 | fn drop(&mut self) { 190 | self.remove_injection(); 191 | } 192 | } 193 | 194 | /// StaticElement are all the variables that aren't changed that often, 195 | /// usually globals. 196 | pub struct StaticElement { 197 | addr: usize, 198 | original_value: Option, 199 | } 200 | 201 | impl StaticElement { 202 | pub fn new(addr: usize) -> StaticElement { 203 | let original_value = unsafe { Some(*(addr as *mut u32)) }; 204 | 205 | StaticElement { 206 | addr, 207 | original_value, 208 | } 209 | } 210 | } 211 | 212 | impl Inject for StaticElement { 213 | fn inject(&mut self) { 214 | unsafe { 215 | let ptr = self.addr as *mut u32; 216 | if self.original_value.is_none() { 217 | self.original_value = Some(*ptr); 218 | } 219 | *ptr = 0; 220 | } 221 | } 222 | 223 | fn remove_injection(&mut self) { 224 | if self.original_value.is_none() { 225 | return; 226 | } 227 | unsafe { 228 | let ptr = self.addr as *mut u32; 229 | *ptr = self.original_value.unwrap(); 230 | } 231 | 232 | self.original_value = None; 233 | } 234 | } 235 | 236 | #[cfg(feature = "impl-drop")] 237 | impl Drop for StaticElement { 238 | fn drop(&mut self) { 239 | self.remove_injection(); 240 | } 241 | } 242 | 243 | impl Inject for Box { 244 | fn inject(&mut self) { 245 | self.deref_mut().inject(); 246 | } 247 | 248 | fn remove_injection(&mut self) { 249 | self.deref_mut().remove_injection(); 250 | } 251 | } 252 | 253 | impl Inject for IterMut<'_, T> { 254 | fn inject(&mut self) { 255 | self.for_each(|x| x.inject()); 256 | } 257 | 258 | fn remove_injection(&mut self) { 259 | self.for_each(|x| x.remove_injection()); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/internal/macros.rs: -------------------------------------------------------------------------------- 1 | /// Macro that creates the MainDLL function. 2 | /// This function has a special signature that needed by WinAPI to 3 | /// create a DLL. 4 | #[macro_export] 5 | macro_rules! main_dll { 6 | ($func:expr) => { 7 | #[no_mangle] 8 | #[allow(non_snake_case)] 9 | pub extern "system" fn DllMain( 10 | lib: windows_sys::Win32::Foundation::HINSTANCE, 11 | reason: u32, 12 | _: usize, 13 | ) -> u32 { 14 | unsafe { 15 | match reason { 16 | windows_sys::Win32::System::SystemServices::DLL_PROCESS_ATTACH => { 17 | windows_sys::Win32::System::Threading::CreateThread( 18 | std::ptr::null_mut(), 19 | 0, 20 | Some($func), 21 | lib as *const std::ffi::c_void, 22 | 0, 23 | std::ptr::null_mut(), 24 | ); 25 | } 26 | _ => (), 27 | }; 28 | } 29 | 30 | return true as u32; 31 | } 32 | }; 33 | } 34 | 35 | /// Macro created by t0mstone 36 | /// You can check the original source at 37 | /// https://github.com/T0mstone/tlibs/blob/master/some_macros/src/lib.rs#L23-L29 38 | #[macro_export] 39 | macro_rules! count_args { 40 | (@one $($t:tt)*) => { 1 }; 41 | ($(($($x:tt)*)),*$(,)?) => { 42 | 0 $(+ $crate::count_args!(@one $($x)*))* 43 | }; 44 | } 45 | 46 | /// Map winapi error to lib error. 47 | #[macro_export] 48 | macro_rules! try_winapi { 49 | ($call:expr) => {{ 50 | let res = $call; 51 | if res == 0 { 52 | let msg = format!( 53 | "{} failed with error code {}", 54 | std::stringify!($call), 55 | std::io::Error::last_os_error() 56 | ); 57 | return Err($crate::error::Error::new($crate::error::ErrorType::WinAPI, msg).into()); 58 | } 59 | }}; 60 | } 61 | 62 | /// Wrap a function to a lambda which returns a Result<_, Error> 63 | /// depending of the condition. 64 | /// You should use it like 65 | /// wrap_winapi!(MyFunction(), MyCondition) where 66 | /// MyCondition **must** start with an x and the comparison. 67 | /// For example 68 | /// ``` 69 | /// # use memory_rs::wrap_winapi; 70 | /// // This should fail since we are saying that 71 | /// // when the return value equals to 0 it's an error. 72 | /// assert!(wrap_winapi!((|| 0)(), x == 0).is_err()); 73 | /// ``` 74 | #[macro_export] 75 | macro_rules! wrap_winapi { 76 | ($call:expr, x $($tt:tt)*) => { 77 | (|| -> Result<_, $crate::error::Error> { 78 | let res = $call; 79 | let x = res as usize; 80 | if x $($tt)* { 81 | let msg = format!( 82 | "{} failed with error `{}`", 83 | std::stringify!($call), 84 | std::io::Error::last_os_error() 85 | ); 86 | return Err($crate::error::Error::new( 87 | $crate::error::ErrorType::WinAPI, 88 | msg, 89 | ) 90 | .into()); 91 | } 92 | Ok(res) 93 | })() 94 | }; 95 | } 96 | 97 | /// Scoped no mangle to avoid repetition 98 | #[macro_export] 99 | macro_rules! scoped_no_mangle { 100 | ($($name:ident: $v:ty = $val:expr;)*) => { 101 | $(#[no_mangle] pub static mut $name: $v = $val;)* 102 | } 103 | } 104 | 105 | /// Returns a MemoryPattern struct 106 | #[macro_export] 107 | macro_rules! generate_aob_pattern { 108 | [$($val:tt),* ] => { 109 | $crate::internal::memory::MemoryPattern::new( 110 | $crate::count_args!($(($val)),*), 111 | |slice: &[u8]| -> bool { 112 | matches!(slice, [$($val),*]) 113 | } 114 | ) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/internal/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, ErrorType}; 2 | use crate::wrap_winapi; 3 | use anyhow::{Context, Result}; 4 | use std::ffi::c_void; 5 | use std::path::PathBuf; 6 | use std::ptr::copy_nonoverlapping; 7 | use windows_sys::Win32::System::Diagnostics::Debug::FlushInstructionCache; 8 | use windows_sys::Win32::System::LibraryLoader::GetModuleFileNameW; 9 | use windows_sys::Win32::System::Memory::{ 10 | VirtualProtect, VirtualQuery, MEMORY_BASIC_INFORMATION, MEM_FREE, PAGE_EXECUTE_READWRITE, 11 | }; 12 | use windows_sys::Win32::System::Threading::GetCurrentProcess; 13 | 14 | pub struct MemProtect { 15 | addr: usize, 16 | size: usize, 17 | prot: u32, 18 | } 19 | 20 | /// Scoped VirtualProtect. 21 | /// # Safety 22 | /// The only unsafe bit is the VirtualProtect, which according to msdn 23 | /// it shouldn't have undefined behavior, so we wrap that function with an 24 | /// `try_winapi!` macro. 25 | impl MemProtect { 26 | pub fn new(addr: usize, size: usize, prot: Option) -> Result { 27 | let new_prot = prot.unwrap_or(PAGE_EXECUTE_READWRITE); 28 | 29 | let mut old_prot = 0u32; 30 | 31 | unsafe { 32 | wrap_winapi!( 33 | VirtualProtect(addr as *const c_void, size, new_prot, &mut old_prot), 34 | x == 0 35 | )?; 36 | } 37 | 38 | Ok(Self { 39 | addr, 40 | size, 41 | prot: old_prot, 42 | }) 43 | } 44 | } 45 | 46 | impl Drop for MemProtect { 47 | fn drop(&mut self) { 48 | let mut _prot = 0; 49 | unsafe { 50 | VirtualProtect(self.addr as _, self.size, self.prot, &mut _prot); 51 | } 52 | } 53 | } 54 | 55 | pub struct MemoryPattern { 56 | pub size: usize, 57 | pub pattern: fn(&[u8]) -> bool, 58 | } 59 | 60 | impl MemoryPattern { 61 | pub fn new(size: usize, pattern: fn(&[u8]) -> bool) -> Self { 62 | MemoryPattern { size, pattern } 63 | } 64 | 65 | pub fn scan(&self, val: &[u8]) -> bool { 66 | (self.pattern)(val) 67 | } 68 | } 69 | 70 | /// Write an array of bytes to the desired ptr address. 71 | /// # Safety 72 | /// This function can cause the target program to crash due to 73 | /// incorrect writing, or it could simply make crash the software in case 74 | /// the virtual protect doesn't succeed. 75 | pub unsafe fn write_aob(ptr: usize, source: &[u8]) -> Result<()> { 76 | let size = source.len(); 77 | 78 | let _mp = MemProtect::new(ptr, size, None)?; 79 | 80 | copy_nonoverlapping(source.as_ptr(), ptr as *mut u8, size); 81 | 82 | let ph = GetCurrentProcess(); 83 | FlushInstructionCache(ph, ptr as *const c_void, size); 84 | 85 | Ok(()) 86 | } 87 | 88 | /// Injects a jmp in the target address. The minimum length of it is 12 bytes. 89 | /// In case the space is bigger than 14 bytes, it'll inject a non-dirty 90 | /// trampoline, and will nop the rest of the instructions. 91 | /// # Safety 92 | /// this function is inherently unsafe since it does a lot of nasty stuff. 93 | pub unsafe fn hook_function( 94 | original_function: usize, 95 | new_function: usize, 96 | new_function_end: Option<&mut usize>, 97 | len: usize, 98 | ) -> Result<()> { 99 | assert!(len >= 12, "Not enough space to inject the shellcode"); 100 | 101 | let ph = GetCurrentProcess(); 102 | 103 | // Unprotect zone we'll write 104 | let _mp = MemProtect::new(original_function, len, None)?; 105 | 106 | let nops = vec![0x90; len]; 107 | write_aob(original_function, &nops).with_context(|| "Couldn't nop original bytes")?; 108 | 109 | // Inject the jmp to the original function 110 | // address as an AoB 111 | let aob: [u8; std::mem::size_of::()] = new_function.to_le_bytes(); 112 | 113 | let injection = if len < 14 { 114 | let mut v = vec![0x48, 0xb8]; 115 | v.extend_from_slice(&aob); 116 | v.extend_from_slice(&[0xff, 0xe0]); 117 | v 118 | } else { 119 | let mut v = if cfg!(target_arch = "x86_64") { 120 | vec![0xff, 0x25, 0x00, 0x00, 0x00, 0x00] 121 | } else { 122 | let mut v = vec![0xFF, 0x25]; 123 | v.extend_from_slice(&(original_function + 6).to_le_bytes()); 124 | v 125 | }; 126 | v.extend_from_slice(&aob); 127 | v 128 | }; 129 | 130 | write_aob(original_function, &injection) 131 | .with_context(|| "Couldn't write the injection to the original function")?; 132 | 133 | FlushInstructionCache(ph, original_function as *const c_void, injection.len()); 134 | 135 | // Inject the jmp back if required 136 | if let Some(p) = new_function_end { 137 | *p = original_function + len; 138 | } 139 | 140 | Ok(()) 141 | } 142 | 143 | /// This function will use the WinAPI to check if the region to scan is valid. 144 | /// A region is not valid when it's free or when VirtualQuery returns an 145 | /// error at the moment of querying that region. 146 | pub fn check_valid_region(start_address: usize, len: usize) -> Result<()> { 147 | if start_address == 0x0 { 148 | return Err(Error::new(ErrorType::Internal, "start_address can't be 0".into()).into()); 149 | } 150 | 151 | if len == 0x0 { 152 | return Err(Error::new(ErrorType::Internal, "len can't be 0".into()).into()); 153 | } 154 | 155 | let mut region_size = 0_usize; 156 | let size_mem_inf = std::mem::size_of::(); 157 | 158 | while region_size < len { 159 | let mut information: MEMORY_BASIC_INFORMATION = unsafe { std::mem::zeroed() }; 160 | unsafe { 161 | wrap_winapi!( 162 | VirtualQuery( 163 | (start_address + region_size) as *const c_void, 164 | &mut information, 165 | size_mem_inf 166 | ), 167 | x == 0 168 | )?; 169 | } 170 | 171 | if information.State == MEM_FREE { 172 | return Err(Error::new( 173 | ErrorType::Internal, 174 | "The region to scan is invalid".to_string(), 175 | ) 176 | .into()); 177 | } 178 | 179 | region_size += information.RegionSize as usize; 180 | } 181 | 182 | Ok(()) 183 | } 184 | 185 | /// Get DLL's parent path 186 | /// # Safety 187 | /// This function can fail on the 188 | /// GetModuleFileNameA, everything else is safe 189 | /// TODO: Find a way to test this one. 190 | pub unsafe fn resolve_module_path(lib: *const c_void) -> Result { 191 | let mut buf: Vec = vec![0x0; 255]; 192 | 193 | wrap_winapi!(GetModuleFileNameW(lib as _, buf.as_mut_ptr(), 255), x == 0)?; 194 | let end_ix = buf 195 | .iter() 196 | .position(|&x| x == 0) 197 | .expect("Invalid utf16 name"); 198 | let name = String::from_utf16(&buf[..end_ix]).unwrap(); 199 | let mut path: PathBuf = name.into(); 200 | path.pop(); 201 | Ok(path) 202 | } 203 | -------------------------------------------------------------------------------- /src/internal/memory_region.rs: -------------------------------------------------------------------------------- 1 | use crate::error::*; 2 | use crate::internal::memory; 3 | use anyhow::Result; 4 | 5 | #[derive(Debug)] 6 | pub struct MemoryRegion { 7 | pub start_address: usize, 8 | pub size: usize, 9 | is_safe: bool, 10 | } 11 | 12 | impl MemoryRegion { 13 | pub fn new(start_address: usize, size: usize, is_safe: bool) -> Result { 14 | let memory_region = Self { 15 | start_address, 16 | size, 17 | is_safe, 18 | }; 19 | 20 | // Do at least one check if the memory is safe 21 | memory::check_valid_region(start_address, size)?; 22 | 23 | Ok(memory_region) 24 | } 25 | 26 | fn check_valid_region(&self) -> Result<()> { 27 | if !self.is_safe { 28 | memory::check_valid_region(self.start_address, self.size)?; 29 | } 30 | 31 | Ok(()) 32 | } 33 | 34 | pub fn scan_aob(&self, pat: &memory::MemoryPattern) -> Result> { 35 | self.check_valid_region()?; 36 | 37 | let data = unsafe { std::slice::from_raw_parts(self.start_address as *mut u8, self.size) }; 38 | let index = data.windows(pat.size).position(pat.pattern); 39 | 40 | match index { 41 | Some(addr) => Ok(Some(self.start_address + addr)), 42 | None => Ok(None), 43 | } 44 | } 45 | 46 | pub fn scan_aob_all_matches(&self, pat: &memory::MemoryPattern) -> Result> { 47 | self.check_valid_region()?; 48 | let data = unsafe { std::slice::from_raw_parts(self.start_address as *mut u8, self.size) }; 49 | let mut iter = data.windows(pat.size); 50 | let mut matches = Vec::new(); 51 | 52 | loop { 53 | let val = iter.position(pat.pattern); 54 | if val.is_none() { 55 | break; 56 | } 57 | 58 | let val = val.unwrap(); 59 | match matches.last() { 60 | Some(&last_val) => matches.push(val + last_val + 0x1), 61 | None => matches.push(self.start_address + val), 62 | }; 63 | } 64 | 65 | Ok(matches) 66 | } 67 | 68 | /// Scan all aob matches aligned at `align`. If None is provided, it will align to 4 by default 69 | pub fn scan_aob_all_matches_aligned( 70 | &self, 71 | pat: &memory::MemoryPattern, 72 | align: Option, 73 | ) -> Result> { 74 | self.check_valid_region()?; 75 | let data = unsafe { std::slice::from_raw_parts(self.start_address as *mut u8, self.size) }; 76 | let align = align.unwrap_or(4); 77 | let padding = (align - (pat.size % align)) % align; 78 | let chunk_size = pat.size + padding; 79 | let mut iter = data.chunks_exact(chunk_size); 80 | let mut matches = Vec::new(); 81 | 82 | loop { 83 | let val = iter.position(|x| pat.scan(&x[..pat.size])); 84 | if val.is_none() { 85 | break; 86 | } 87 | 88 | let val = val.unwrap(); 89 | match matches.last() { 90 | Some(&last_val) => matches.push((val + 0x1) * chunk_size + last_val), 91 | None => matches.push(self.start_address + val), 92 | }; 93 | } 94 | 95 | Ok(matches) 96 | } 97 | 98 | pub fn scan_aligned_value(&self, value: T) -> Result> 99 | where 100 | T: Copy + PartialEq, 101 | { 102 | self.check_valid_region()?; 103 | let size_type = std::mem::size_of::(); 104 | let mut matches = Vec::new(); 105 | 106 | if self.size / size_type == 0 { 107 | return Err(Error::new( 108 | ErrorType::Internal, 109 | "The space to scan is smaller than the type size".into(), 110 | ) 111 | .into()); 112 | } 113 | 114 | let data = unsafe { 115 | std::slice::from_raw_parts(self.start_address as *mut T, self.size / size_type) 116 | }; 117 | let mut iter = data.iter(); 118 | 119 | let match_function = |&x| x == value; 120 | 121 | while let Some(val) = iter.position(match_function) { 122 | match matches.last() { 123 | Some(&last_val) => matches.push((val + 0x1) * size_type + last_val), 124 | None => matches.push((val * size_type) + self.start_address), 125 | }; 126 | } 127 | 128 | Ok(matches) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/internal/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod injections; 2 | pub mod macros; 3 | pub mod memory; 4 | pub mod memory_region; 5 | pub mod process_info; 6 | -------------------------------------------------------------------------------- /src/internal/process_info.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, ErrorType}; 2 | use crate::internal::memory_region::*; 3 | use crate::wrap_winapi; 4 | use anyhow::Result; 5 | use windows_sys::Win32::Foundation::HINSTANCE; 6 | use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW; 7 | use windows_sys::Win32::System::ProcessStatus::{K32GetModuleInformation, MODULEINFO}; 8 | use windows_sys::Win32::System::Threading::GetCurrentProcess; 9 | 10 | /// Struct that contains some very basic information of a executable or DLL. 11 | #[derive(Debug)] 12 | pub struct ProcessInfo { 13 | pub handle: HINSTANCE, 14 | pub region: MemoryRegion, 15 | } 16 | 17 | impl ProcessInfo { 18 | /// Create the ProcessInfo. This function can fail in case where 19 | /// the `GetModuleInformation` fails. 20 | pub fn new(name: Option<&str>) -> Result { 21 | let module = match name { 22 | Some(n) => { 23 | let mut name_ = n.to_string(); 24 | name_.push('\0'); 25 | let name_wide: Vec = name_.encode_utf16().collect(); 26 | unsafe { wrap_winapi!(GetModuleHandleW(name_wide.as_ptr()), x == 0)? } 27 | } 28 | None => unsafe { wrap_winapi!(GetModuleHandleW(std::ptr::null()), x == 0)? }, 29 | }; 30 | 31 | let module_addr = module as usize; 32 | 33 | let module_size: usize; 34 | unsafe { 35 | let process = GetCurrentProcess(); 36 | let mut module_info: MODULEINFO = std::mem::zeroed(); 37 | wrap_winapi!( 38 | K32GetModuleInformation( 39 | process, 40 | module, 41 | &mut module_info, 42 | std::mem::size_of::() as u32, 43 | ), 44 | x == 0 45 | )?; 46 | 47 | module_size = module_info.SizeOfImage as usize; 48 | } 49 | 50 | if module_addr == 0x0 { 51 | return Err( 52 | Error::new(ErrorType::Internal, "Base address can't be 0".to_string()).into(), 53 | ); 54 | } 55 | 56 | if module_size == 0x0 { 57 | return Err(Error::new( 58 | ErrorType::Internal, 59 | "Size of the module can't be 0".to_string(), 60 | ) 61 | .into()); 62 | } 63 | 64 | let region = MemoryRegion::new(module_addr, module_size, true)?; 65 | 66 | Ok(ProcessInfo { 67 | handle: module, 68 | region, 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_os = "windows"))] 2 | fn panic() { 3 | compile_error!("This library only supports Windows for now"); 4 | } 5 | 6 | #[macro_use] 7 | #[cfg(target_os = "windows")] 8 | pub mod internal; 9 | 10 | #[cfg(target_os = "windows")] 11 | pub mod error; 12 | 13 | #[cfg(target_os = "windows")] 14 | pub mod external; 15 | 16 | macro_rules! doctest { 17 | ($x:expr) => { 18 | #[doc = $x] 19 | extern "C" {} 20 | }; 21 | } 22 | doctest!(include_str!("../README.md")); 23 | -------------------------------------------------------------------------------- /tests/internal.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | use memory_rs::internal::injections::*; 3 | use memory_rs::internal::memory::*; 4 | use memory_rs::internal::memory_region::*; 5 | use memory_rs::internal::process_info::ProcessInfo; 6 | 7 | static TO_BE_WRITTEN: [u8; 8] = [0xDE, 0xAD, 0xBE, 0xEF, 0xC0, 0xFF, 0xEE, 0x00]; 8 | static SEARCH_ARRAY: [u8; 10] = [0xDE, 0xAD, 0xBE, 0xEF, 0xC0, 0xFF, 0xEE, 0xC0, 0xCA, 0xDA]; 9 | 10 | #[test] 11 | fn test_write_aob() { 12 | let new_array = vec![0xAA, 0xBB, 0xCC]; 13 | let pointer = TO_BE_WRITTEN.as_ptr() as *const u8 as usize; 14 | 15 | unsafe { write_aob(pointer, &new_array).unwrap() }; 16 | 17 | let result_array: [u8; 8] = [0xAA, 0xBB, 0xCC, 0xEF, 0xC0, 0xFF, 0xEE, 0x00]; 18 | 19 | let equality = TO_BE_WRITTEN.iter().eq(result_array.iter()); 20 | 21 | assert!( 22 | equality, 23 | "Left: {:x?}, Right: {:x?}", 24 | &TO_BE_WRITTEN[..], 25 | &result_array[..] 26 | ); 27 | } 28 | 29 | #[test] 30 | fn test_generate_aob_pattern() { 31 | let mp = memory_rs::generate_aob_pattern![0xAA, _, 0xBB, _]; 32 | let arr = [0xAA, 0xBB, 0xBB, 0xEE]; 33 | 34 | assert_eq!(mp.size, 4); 35 | assert!(mp.scan(&arr)); 36 | } 37 | 38 | #[test] 39 | fn test_scan_aob() { 40 | let p = &SEARCH_ARRAY as *const u8 as usize; 41 | let arr_len = SEARCH_ARRAY.len(); 42 | let mp = memory_rs::generate_aob_pattern![0xFF, _, 0xC0]; 43 | let reg = MemoryRegion::new(p, arr_len, true).unwrap(); 44 | 45 | let addr = reg.scan_aob(&mp).unwrap(); 46 | 47 | assert_eq!(Some(p + 5), addr); 48 | } 49 | 50 | #[test] 51 | fn test_scan_aob_all_matches() { 52 | let p: [u8; 10] = [0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xFF, 0xCC, 0xAA, 0xEE, 0xCC]; 53 | let arr_len = p.len(); 54 | let mp = memory_rs::generate_aob_pattern![0xAA, _, 0xCC]; 55 | let reg = MemoryRegion::new(p.as_ptr() as usize, arr_len, true).unwrap(); 56 | 57 | let addr = reg.scan_aob_all_matches(&mp).unwrap(); 58 | 59 | // Recreate the original array since the pattern repeats every 3 bytes. 60 | let mut v = vec![]; 61 | for a in addr { 62 | let a_ = unsafe { std::slice::from_raw_parts(a as *const u8, 3) }; 63 | v.extend_from_slice(a_); 64 | } 65 | 66 | assert_eq!( 67 | &[0xAA, 0xBB, 0xCC, 0xAA, 0xFF, 0xCC, 0xAA, 0xEE, 0xCC], 68 | &v[..] 69 | ); 70 | } 71 | 72 | #[test] 73 | fn test_scan_aob_all_matches_aligned() { 74 | let p: [u8; 12] = [ 75 | 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xFF, 0xCC, 0x00, 0xAA, 0xEE, 0xCC, 0x00, 76 | ]; 77 | let arr_len = p.len(); 78 | let mp = memory_rs::generate_aob_pattern![0xAA, _, 0xCC]; 79 | let region = MemoryRegion::new(&p as *const u8 as _, arr_len, true).unwrap(); 80 | 81 | let addr = region.scan_aob_all_matches_aligned(&mp, None).unwrap(); 82 | 83 | let ptr = &p as *const u8 as usize; 84 | let results = [ptr, ptr + 4, ptr + 8]; 85 | 86 | assert_eq!(&addr, &results); 87 | } 88 | 89 | #[test] 90 | fn test_scan_aob_not_valid_memory() { 91 | use memory_rs::error; 92 | 93 | let p = 0x12345678; 94 | let len = 0xFFFF; 95 | let _mp = memory_rs::generate_aob_pattern![0xAA, 0xBB, 0xCC, 0xDD]; 96 | let reg = MemoryRegion::new(p, len, false); 97 | 98 | if let Err(e) = reg { 99 | let e: error::Error = e.downcast().unwrap(); 100 | assert_eq!(e.kind(), error::ErrorType::Internal); 101 | assert_eq!(e.msg(), "The region to scan is invalid".to_string()); 102 | } else { 103 | panic!("Should have get an error"); 104 | } 105 | } 106 | 107 | #[test] 108 | fn test_scan_aob_out_of_bounds() { 109 | use memory_rs::error; 110 | 111 | let p = &SEARCH_ARRAY as *const u8 as usize; 112 | let len = 0xFFFFF; 113 | let _mp = memory_rs::generate_aob_pattern![0xAA, 0xBB, 0xCC, 0xDD]; 114 | let reg = MemoryRegion::new(p, len, false); 115 | 116 | if let Err(e) = reg { 117 | let e: error::Error = e.downcast().unwrap(); 118 | assert_eq!(e.kind(), error::ErrorType::Internal); 119 | assert_eq!(e.msg(), "The region to scan is invalid".to_string()); 120 | } else { 121 | panic!("Should have get an error"); 122 | } 123 | } 124 | 125 | fn dummy_function() -> &'static str { 126 | println!("I'm `dummy_function`"); 127 | 128 | return "I'm the original function"; 129 | } 130 | 131 | fn injected_function() -> &'static str { 132 | println!("I'm `injected_function`"); 133 | return "I'm an imposter!"; 134 | } 135 | 136 | #[test] 137 | fn test_injection() { 138 | let original_function = dummy_function as *mut u8 as usize; 139 | let new_function = injected_function as *mut u8 as usize; 140 | 141 | let res = dummy_function(); 142 | 143 | assert_eq!("I'm the original function", res); 144 | 145 | let mut det = Detour::new(original_function, 14, new_function, None); 146 | det.inject(); 147 | 148 | let res = dummy_function(); 149 | 150 | assert_eq!(res, "I'm an imposter!"); 151 | 152 | det.remove_injection(); 153 | 154 | let res = dummy_function(); 155 | 156 | assert_eq!(res, "I'm the original function"); 157 | } 158 | 159 | #[test] 160 | fn test_drop_injection() { 161 | static arr: [u8; 5] = [0xE7, 0x9A, 0x00, 0x9A, 0x9B]; 162 | 163 | { 164 | let mut injection = Injection::new(arr.as_ptr() as usize + 1, vec![0xAA, 0xBB, 0xCC]); 165 | 166 | assert_eq!(&arr, &[0xE7, 0x9A, 0x00, 0x9A, 0x9B]); 167 | 168 | injection.inject(); 169 | 170 | assert_eq!(&arr, &[0xE7, 0xAA, 0xBB, 0xCC, 0x9B]); 171 | } 172 | 173 | assert_eq!(&arr, &[0xE7, 0x9A, 0x00, 0x9A, 0x9B]); 174 | } 175 | 176 | #[test] 177 | fn test_scan_aligned_value() { 178 | let vals: [u32; 4] = [0xC0FFEE, 0x1337, 0xB00BA, 0xC0FFEE]; 179 | let reg = MemoryRegion::new(vals.as_ptr() as *const u32 as usize, 16, true).unwrap(); 180 | 181 | let result = reg.scan_aligned_value(0xC0FFEE_u32).unwrap(); 182 | 183 | assert_eq!( 184 | &result, 185 | &[ 186 | (&vals[0]) as *const u32 as usize, 187 | (&vals[3]) as *const u32 as usize 188 | ] 189 | ); 190 | } 191 | 192 | #[test] 193 | fn test_hashmap() { 194 | use std::collections::HashMap; 195 | static arr: [u8; 4] = [0xAA, 0xBB, 0xCC, 0xDD]; 196 | 197 | let mut hash = HashMap::new(); 198 | hash.insert( 199 | "first_index", 200 | Injection::new(arr.as_ptr() as usize, vec![0xFF]), 201 | ); 202 | 203 | unsafe { 204 | hash.insert( 205 | "second_index", 206 | Injection::new(arr.as_ptr().offset(1) as usize, vec![0xFA]), 207 | ); 208 | } 209 | hash.values_mut().for_each(|x| x.inject()); 210 | 211 | assert_eq!(&arr[..2], &[0xFF, 0xFA]); 212 | 213 | hash.get_mut("first_index").unwrap().remove_injection(); 214 | 215 | assert_eq!(&arr[..2], &[0xAA, 0xFA]); 216 | } 217 | 218 | #[test] 219 | fn test_slice() { 220 | static arr: [u8; 4] = [0xAA, 0xBB, 0xCC, 0xDD]; 221 | 222 | let mut injections = [ 223 | Injection::new(arr.as_ptr() as _, vec![32]), 224 | Injection::new(unsafe { arr.as_ptr().offset(1) as _ }, vec![42]), 225 | ]; 226 | 227 | injections.iter_mut().inject(); 228 | 229 | assert_eq!(arr, [32_u8, 42, 0xCC, 0xDD]); 230 | } 231 | 232 | #[test] 233 | fn test_vector() { 234 | static arr: [u8; 4] = [0xAA, 0xBB, 0xCC, 0xDD]; 235 | 236 | let mut injections = vec![ 237 | Injection::new(arr.as_ptr() as _, vec![32]), 238 | Injection::new(unsafe { arr.as_ptr().offset(1) as _ }, vec![42]), 239 | ]; 240 | 241 | injections.iter_mut().inject(); 242 | 243 | assert_eq!(arr, [32_u8, 42, 0xCC, 0xDD]); 244 | } 245 | 246 | fn test_dyn_vec_original() -> &'static str { 247 | println!("I'm original function"); 248 | "original" 249 | } 250 | 251 | fn test_dyn_vec_injected() -> &'static str { 252 | println!("I'm injected function"); 253 | "injected" 254 | } 255 | 256 | #[test] 257 | fn test_dyn_vec() { 258 | let original_function = test_dyn_vec_original as *mut u8 as usize; 259 | let new_function = test_dyn_vec_injected as *mut u8 as usize; 260 | 261 | static arr: [u8; 4] = [0xAA, 0xBB, 0xCC, 0xDD]; 262 | 263 | let mut v: Vec> = vec![ 264 | Box::new(Injection::new(arr.as_ptr() as _, vec![32])), 265 | Box::new(Detour::new(original_function, 14, new_function, None)), 266 | ]; 267 | 268 | assert_eq!(test_dyn_vec_original(), "original"); 269 | 270 | v.iter_mut().inject(); 271 | 272 | assert_eq!(test_dyn_vec_original(), "injected"); 273 | 274 | v.iter_mut().remove_injection(); 275 | } 276 | 277 | #[test] 278 | fn test_resolve_module_path() { 279 | let name = unsafe { resolve_module_path(std::ptr::null_mut()).unwrap() }; 280 | assert_eq!(name.file_stem().unwrap(), "deps"); 281 | } 282 | 283 | #[test] 284 | fn test_proc_inf() { 285 | let _ = ProcessInfo::new(Some("Kernel32.dll")).unwrap(); 286 | } 287 | --------------------------------------------------------------------------------