├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── fastyboy.rs └── src ├── architecture.rs ├── data_member.rs ├── lib.rs ├── linux.rs ├── local_member.rs ├── macos.rs └── windows.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | strategy: 8 | matrix: 9 | platform: [ubuntu-latest, macos-latest, windows-latest] 10 | runs-on: ${{ matrix.platform }} 11 | name: Check 12 | steps: 13 | - uses: actions/checkout@v1 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: true 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: check 22 | 23 | test: 24 | name: Test Suite 25 | strategy: 26 | matrix: 27 | platform: [ubuntu-latest, macos-latest, windows-latest] 28 | runs-on: ${{ matrix.platform }} 29 | steps: 30 | - uses: actions/checkout@v1 31 | - uses: actions-rs/toolchain@v1 32 | with: 33 | profile: minimal 34 | toolchain: stable 35 | override: true 36 | - uses: actions-rs/cargo@v1 37 | with: 38 | command: test 39 | 40 | fmt: 41 | name: Rustfmt 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v1 45 | - uses: actions-rs/toolchain@v1 46 | with: 47 | profile: minimal 48 | toolchain: stable 49 | override: true 50 | - run: rustup component add rustfmt 51 | - uses: actions-rs/cargo@v1 52 | with: 53 | command: fmt 54 | args: --all -- --check 55 | 56 | clippy: 57 | name: Clippy 58 | strategy: 59 | matrix: 60 | platform: [ubuntu-latest, macos-latest, windows-latest] 61 | runs-on: ${{ matrix.platform }} 62 | steps: 63 | - uses: actions/checkout@v1 64 | - uses: actions-rs/toolchain@v1 65 | with: 66 | profile: minimal 67 | toolchain: stable 68 | override: true 69 | - run: rustup component add clippy 70 | - uses: actions-rs/cargo@v1 71 | with: 72 | command: clippy 73 | args: -- -D warnings 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "process-memory" 3 | version = "0.5.0" 4 | authors = ["Tommoa "] 5 | readme = "README.md" 6 | license = "MIT" 7 | description = "A rust library that can read/write the memory of other processes." 8 | repository = "https://github.com/Tommoa/rs-process-memory" 9 | edition = "2018" 10 | 11 | [[example]] 12 | name = "fastyboy" 13 | crate-type = ["bin"] 14 | 15 | # Feature needed in the fastyboy example to get the pid of a process by name 16 | [target.'cfg(windows)'.dev-dependencies] 17 | windows = { version = "0.43.0", features = [ 18 | "Win32_System_Diagnostics_ToolHelp", 19 | ] } 20 | 21 | [dependencies] 22 | libc = "0.2" 23 | 24 | [target.'cfg(target_os="macos")'.dependencies] 25 | mach = "0.3" 26 | 27 | [target.'cfg(windows)'.dependencies] 28 | windows = { version = "0.43.0", features = [ 29 | "Win32_Foundation", 30 | "Win32_System_Threading", 31 | "Win32_System_Diagnostics_Debug", 32 | ] } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tom Almeida 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 | # process-memory 2 | [![](https://img.shields.io/crates/v/process-memory.svg)](https://crates.io/crates/process-memory) 3 | [![](https://docs.rs/process-memory/badge.svg)](https://docs.rs/process-memory) 4 | ![Continuous Integration](https://github.com/Tommoa/rs-process-memory/workflows/Continuous%20integration/badge.svg) 5 | 6 | This crate is loosely based on [`read-process-memory`](https://github.com/luser/read-process-memory) by luser, but has been extended to be able to write to process memory as well. 7 | 8 | The current supported platforms are: 9 | - Windows 10 | - OSX 11 | - Linux 12 | 13 | Some examples of use cases for this tool are: 14 | - Remote debugging tools 15 | - Game "trainers" 16 | - Rust clones of Cheat Engine 17 | 18 | ## Examples 19 | ```rust 20 | // We need the following from process-memory 21 | # use process_memory::{Memory, DataMember, Pid, TryIntoProcessHandle}; 22 | 23 | // We have a variable with some value 24 | let x = 4_u32; 25 | 26 | 27 | // We need to make sure that we get a handle to a process 28 | // that we want to read from, in this case, ourselves 29 | let handle = ( std::process::id() as Pid ).try_into_process_handle().unwrap(); 30 | 31 | // We make a `DataMember` that has an offset referring to the location 32 | // of our variable x in memory 33 | let member = DataMember::new_offset(handle, vec![&x as *const _ as usize]); 34 | 35 | // Variable member and x now refer to the same memory 36 | assert_eq!(&x as *const _ as usize, member.get_offset().unwrap()); 37 | 38 | // The value of member is the same as x 39 | assert_eq!(x, unsafe { member.read().unwrap() }); 40 | 41 | // We can write to and modify the value of x using member 42 | member.write(&6_u32).unwrap(); 43 | assert_eq!(x, 6_u32); 44 | ``` 45 | 46 | 47 | ```rust 48 | # use process_memory::{Memory, LocalMember}; 49 | 50 | // We have a variable with some value 51 | let x = 4_u32; 52 | 53 | // We make a `LocalMember` that has an offset referring to its location in memory 54 | let member = LocalMember::new_offset(vec![&x as *const _ as usize]); 55 | 56 | // The memory refered to is now the same 57 | assert_eq!(&x as *const _ as usize, member.get_offset().unwrap()); 58 | 59 | // The value of the member is the same as the variable 60 | assert_eq!(x, unsafe { member.read().unwrap() }); 61 | 62 | // We can write to and modify the value of the variable using the member 63 | member.write(&6_u32).unwrap(); 64 | assert_eq!(x, 6_u32); 65 | ``` 66 | 67 | ```no_run 68 | # use process_memory::{Architecture, Memory, DataMember, Pid, ProcessHandleExt, TryIntoProcessHandle}; 69 | # fn get_pid(process_name: &str) -> Pid { 70 | # std::process::id() as Pid 71 | # } 72 | 73 | // We get a handle for a target process with a different architecture to ourselves 74 | let handle = get_pid("32Bit.exe").try_into_process_handle().unwrap() 75 | .set_arch(Architecture::Arch32Bit); 76 | 77 | // We make a `DataMember` that has a series of offsets refering to a known value in 78 | // the target processes memory 79 | let member = DataMember::new_offset(handle, vec![0x01_02_03_04, 0x04, 0x08, 0x10]); 80 | 81 | // The memory offset can now be correctly calculated: 82 | let targetMemoryLocation = member.get_offset().unwrap(); 83 | 84 | // The memory offset can now be used to retrieve and modify values: 85 | let currentValue = unsafe { member.read().unwrap() }; 86 | 87 | member.write(&123_u32).unwrap(); 88 | ``` 89 | -------------------------------------------------------------------------------- /examples/fastyboy.rs: -------------------------------------------------------------------------------- 1 | //! An example program that writes a few variables in the game Mirror's Edge Catalyst. This example 2 | //! is only supported on Windows as Mirror's Edge Catalyst only runs on Windows. 3 | //! 4 | //! The program sets the spawn timer to 1 second (down from the default of 10 seconds), lowers the 5 | //! "level warmup time" to 1 second (also down from a default of 10 seconds) and disables emitters. 6 | //! These modifications should make the time taken to load a level in Mirror's Edge Catalyst 7 | //! significantly shorter. 8 | #[cfg(windows)] 9 | mod windows { 10 | pub(crate) use windows::Win32::{ 11 | Foundation::{CHAR, MAX_PATH}, 12 | System::Diagnostics::ToolHelp::{ 13 | CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, 14 | TH32CS_SNAPPROCESS, 15 | }, 16 | }; 17 | } 18 | #[cfg(not(windows))] 19 | fn main() { 20 | println!("FastyBoy can only be run on systems supporting the game Mirror's Edge Catalyst, which as of writing is only Windows.") 21 | } 22 | 23 | /// A helper function to get a Pid from the name of a process 24 | #[cfg(windows)] 25 | pub fn get_pid(process_name: &str) -> process_memory::Pid { 26 | /// A helper function to turn a CHAR array to a String 27 | fn utf8_to_string(bytes: &[windows::CHAR]) -> String { 28 | use std::ffi::CStr; 29 | unsafe { 30 | CStr::from_ptr(bytes.as_ptr() as *const i8) 31 | .to_string_lossy() 32 | .into_owned() 33 | } 34 | } 35 | 36 | let mut entry = windows::PROCESSENTRY32 { 37 | dwSize: std::mem::size_of::() as u32, 38 | cntUsage: 0, 39 | th32ProcessID: 0, 40 | th32DefaultHeapID: 0, 41 | th32ModuleID: 0, 42 | cntThreads: 0, 43 | th32ParentProcessID: 0, 44 | pcPriClassBase: 0, 45 | dwFlags: 0, 46 | szExeFile: [windows::CHAR(0); windows::MAX_PATH as usize], 47 | }; 48 | unsafe { 49 | // On Error return 0 as the pid. Maybe this function should instead return itself a Result 50 | // to indicate if a pid has been found? 51 | let snapshot = if let Ok(snapshot) = 52 | windows::CreateToolhelp32Snapshot(windows::TH32CS_SNAPPROCESS, 0) 53 | { 54 | snapshot 55 | } else { 56 | return 0; 57 | }; 58 | if windows::Process32First(snapshot, &mut entry) == true { 59 | while windows::Process32Next(snapshot, &mut entry) == true { 60 | if utf8_to_string(&entry.szExeFile) == process_name { 61 | return entry.th32ProcessID; 62 | } 63 | } 64 | } 65 | } 66 | 0 67 | } 68 | 69 | #[cfg(windows)] 70 | fn main() -> std::io::Result<()> { 71 | use process_memory::*; 72 | let process_handle = get_pid("MirrorsEdgeCatalyst.exe").try_into_process_handle()?; 73 | 74 | let mut spawn_timer = DataMember::::new(process_handle); 75 | spawn_timer.set_offset(vec![0x1_42_14_2a_d8, 0xac]); 76 | 77 | let mut level_warmup = DataMember::::new(process_handle); 78 | level_warmup.set_offset(vec![0x1_42_14_2a_d8, 0x9c]); 79 | 80 | let mut emitters_enabled = DataMember::::new(process_handle); 81 | emitters_enabled.set_offset(vec![0x1_42_3e_44_78, 0xac]); 82 | 83 | spawn_timer.write(&1.0)?; 84 | level_warmup.write(&1.0)?; 85 | emitters_enabled.write(&false)?; 86 | 87 | unsafe { 88 | // safety: These are known to be the correct addresses for these types in 89 | // 'MirrorsEdgeCatalyst.exe' 90 | println!("Spawn timer: {}", spawn_timer.read()?); 91 | println!("Level warmup: {}", level_warmup.read()?); 92 | println!("Emitters enabled: {}", emitters_enabled.read()?); 93 | } 94 | Ok(()) 95 | } 96 | -------------------------------------------------------------------------------- /src/architecture.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | 3 | /// Enum representing the architecture of a process 4 | #[derive(Clone, Debug, Copy)] 5 | #[repr(u8)] 6 | pub enum Architecture { 7 | /// 8-bit architecture 8 | #[cfg(any( 9 | target_pointer_width = "8", 10 | target_pointer_width = "16", 11 | target_pointer_width = "32", 12 | target_pointer_width = "64", 13 | target_pointer_width = "128" 14 | ))] 15 | Arch8Bit = 1, 16 | /// 16-bit architecture 17 | #[cfg(any( 18 | target_pointer_width = "16", 19 | target_pointer_width = "32", 20 | target_pointer_width = "64", 21 | target_pointer_width = "128" 22 | ))] 23 | Arch16Bit = 2, 24 | /// 32-bit architecture 25 | #[cfg(any( 26 | target_pointer_width = "32", 27 | target_pointer_width = "64", 28 | target_pointer_width = "128" 29 | ))] 30 | Arch32Bit = 4, 31 | /// 64-bit architecture 32 | #[cfg(any(target_pointer_width = "64", target_pointer_width = "128"))] 33 | Arch64Bit = 8, 34 | /// 128-bit architecture 35 | #[cfg(target_pointer_width = "128")] 36 | Arch128Bit = 16, 37 | } 38 | 39 | impl Architecture { 40 | /// Create an Architecture matching that of the host process. 41 | #[must_use] 42 | pub fn from_native() -> Architecture { 43 | #[cfg(target_pointer_width = "8")] 44 | return Architecture::Arch8Bit; 45 | #[cfg(target_pointer_width = "16")] 46 | return Architecture::Arch16Bit; 47 | #[cfg(target_pointer_width = "32")] 48 | return Architecture::Arch32Bit; 49 | #[cfg(target_pointer_width = "64")] 50 | return Architecture::Arch64Bit; 51 | #[cfg(target_pointer_width = "128")] 52 | return Architecture::Arch128Bit; 53 | } 54 | 55 | /// Convert bytes read from memory into a pointer in the 56 | /// current architecture. 57 | /// 58 | /// # Panics 59 | /// If there are not enough bytes in the slice to make an integer of the sized indicated by 60 | /// `self`. 61 | #[must_use] 62 | pub fn pointer_from_ne_bytes(self, bytes: &[u8]) -> usize { 63 | match self { 64 | #[allow(clippy::cast_possible_truncation)] 65 | #[cfg(any( 66 | target_pointer_width = "8", 67 | target_pointer_width = "16", 68 | target_pointer_width = "32", 69 | target_pointer_width = "64", 70 | target_pointer_width = "128" 71 | ))] 72 | Architecture::Arch8Bit => u8::from_ne_bytes(bytes.try_into().unwrap()) as usize, 73 | #[allow(clippy::cast_possible_truncation)] 74 | #[cfg(any( 75 | target_pointer_width = "16", 76 | target_pointer_width = "32", 77 | target_pointer_width = "64", 78 | target_pointer_width = "128" 79 | ))] 80 | Architecture::Arch16Bit => u16::from_ne_bytes(bytes.try_into().unwrap()) as usize, 81 | #[allow(clippy::cast_possible_truncation)] 82 | #[cfg(any( 83 | target_pointer_width = "32", 84 | target_pointer_width = "64", 85 | target_pointer_width = "128" 86 | ))] 87 | Architecture::Arch32Bit => u32::from_ne_bytes(bytes.try_into().unwrap()) as usize, 88 | #[allow(clippy::cast_possible_truncation)] 89 | #[cfg(any(target_pointer_width = "64", target_pointer_width = "128"))] 90 | Architecture::Arch64Bit => u64::from_ne_bytes(bytes.try_into().unwrap()) as usize, 91 | #[allow(clippy::cast_possible_truncation)] 92 | #[cfg(target_pointer_width = "128")] 93 | Architecture::Arch128Bit => u128::from_ne_bytes(bytes.try_into().unwrap()) as usize, 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/data_member.rs: -------------------------------------------------------------------------------- 1 | use crate::{CopyAddress, Memory, ProcessHandle, PutAddress}; 2 | 3 | /// # Tools for working with memory of other programs 4 | /// This module provides functions for modifying the memory of a program from outside of the 5 | /// address space of that program. 6 | /// 7 | /// Examples: 8 | /// ```rust 9 | /// # use process_memory::{Memory, DataMember, Pid, TryIntoProcessHandle}; 10 | /// // We have a variable with some value 11 | /// let x = 4u32; 12 | /// println!("Original x-value: {}", x); 13 | /// 14 | /// // We need to make sure that we get a handle to a process, in this case, ourselves 15 | /// let handle = (std::process::id() as Pid).try_into_process_handle().unwrap(); 16 | /// // We make a `DataMember` that has an offset referring to its location in memory 17 | /// let member = DataMember::new_offset(handle, vec![&x as *const _ as usize]); 18 | /// // The memory refered to is now the same 19 | /// println!("Memory location: &x: {}, member: {}", &x as *const _ as usize, 20 | /// member.get_offset().unwrap()); 21 | /// assert_eq!(&x as *const _ as usize, member.get_offset().unwrap()); 22 | /// // The value of the member is the same as the variable 23 | /// println!("Member value: {}", unsafe { member.read().unwrap() }); 24 | /// assert_eq!(x, unsafe { member.read().unwrap() }); 25 | /// // We can write to and modify the value of the variable using the member 26 | /// member.write(&6u32).unwrap(); 27 | /// println!("New x-value: {}", x); 28 | /// assert_eq!(x, 6u32); 29 | /// ``` 30 | #[derive(Clone, Debug)] 31 | pub struct DataMember { 32 | offsets: Vec, 33 | process: ProcessHandle, 34 | _phantom: std::marker::PhantomData<*mut T>, 35 | } 36 | 37 | impl DataMember { 38 | /// Create a new `DataMember` from a [`ProcessHandle`]. You must remember to call 39 | /// [`try_into_process_handle`] on a [`Pid`], because the types may have the same backing type, 40 | /// resulting in errors when called with the wrong value. 41 | /// 42 | /// By default, there will be no offsets, leading to an error when attempting to call 43 | /// [`Memory::read`], so you will likely need to call [`Memory::set_offset`] before attempting 44 | /// any reads. 45 | /// 46 | /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle 47 | /// [`ProcessHandle`]: type.ProcessHandle.html 48 | /// [`Pid`]: type.Pid.html 49 | /// [`Memory::read`]: trait.Memory.html#tymethod.read 50 | /// [`Memory::set_offset`]: trait.Memory.html#tymethod.set_offset 51 | #[must_use] 52 | pub fn new(handle: ProcessHandle) -> Self { 53 | Self { 54 | offsets: Vec::new(), 55 | process: handle, 56 | _phantom: std::marker::PhantomData, 57 | } 58 | } 59 | 60 | /// Create a new `DataMember` from a [`ProcessHandle`] and some number of offsets. You must 61 | /// remember to call [`try_into_process_handle`] on a [`Pid`] as sometimes the `Pid` can have 62 | /// the same backing type as a [`ProcessHandle`], resulting in an error. 63 | /// 64 | /// [`try_into_process_handle`]: trait.TryIntoProcessHandle.html#tymethod.try_into_process_handle 65 | /// [`ProcessHandle`]: type.ProcessHandle.html 66 | /// [`Pid`]: type.Pid.html 67 | #[must_use] 68 | pub fn new_offset(handle: ProcessHandle, offsets: Vec) -> Self { 69 | Self { 70 | offsets, 71 | process: handle, 72 | _phantom: std::marker::PhantomData, 73 | } 74 | } 75 | } 76 | 77 | impl Memory for DataMember { 78 | fn set_offset(&mut self, new_offsets: Vec) { 79 | self.offsets = new_offsets; 80 | } 81 | 82 | fn get_offset(&self) -> std::io::Result { 83 | self.process.get_offset(&self.offsets) 84 | } 85 | 86 | unsafe fn read(&self) -> std::io::Result { 87 | let offset = self.process.get_offset(&self.offsets)?; 88 | // This can't be [0_u8;size_of::()] because no const generics. 89 | // It will be freed at the end of the function because no references are held to it. 90 | let mut buffer = vec![0_u8; std::mem::size_of::()]; 91 | self.process.copy_address(offset, &mut buffer)?; 92 | Ok(buffer.as_ptr().cast::().read_unaligned()) 93 | } 94 | 95 | fn write(&self, value: &T) -> std::io::Result<()> { 96 | use std::slice; 97 | let offset = self.process.get_offset(&self.offsets)?; 98 | let buffer: &[u8] = unsafe { 99 | slice::from_raw_parts((value as *const T).cast::(), std::mem::size_of::()) 100 | }; 101 | self.process.put_address(offset, buffer) 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod test { 107 | use super::*; 108 | use crate::TryIntoProcessHandle; 109 | #[test] 110 | fn modify_remote_i32() { 111 | let test = 4_i32; 112 | #[allow(clippy::cast_possible_wrap)] 113 | let handle = (std::process::id() as crate::Pid) 114 | .try_into_process_handle() 115 | .unwrap(); 116 | println!("Process Handle: {:?}", handle); 117 | let mut member = DataMember::::new(handle); 118 | member.set_offset(vec![std::ptr::addr_of!(test) as usize]); 119 | unsafe { 120 | // safety: the memory being pointed to is known to be a valid i32 as we control it 121 | assert_eq!(test, member.read().unwrap()); 122 | } 123 | member.write(&5_i32).unwrap(); 124 | assert_eq!(test, 5_i32); 125 | } 126 | #[test] 127 | fn modify_remote_i64() { 128 | let test = 3_i64; 129 | #[allow(clippy::cast_possible_wrap)] 130 | let handle = (std::process::id() as crate::Pid) 131 | .try_into_process_handle() 132 | .unwrap(); 133 | println!("Process Handle: {:?}", handle); 134 | let mut member = DataMember::::new(handle); 135 | member.set_offset(vec![std::ptr::addr_of!(test) as usize]); 136 | unsafe { 137 | // safety: the memory being pointed to is known to be a valid i64 as we control it 138 | assert_eq!(test, member.read().unwrap()); 139 | } 140 | member.write(&-1_i64).unwrap(); 141 | assert_eq!(test, -1); 142 | } 143 | #[test] 144 | fn modify_remote_usize() { 145 | let test = 0_usize; 146 | #[allow(clippy::cast_possible_wrap)] 147 | let handle = (std::process::id() as crate::Pid) 148 | .try_into_process_handle() 149 | .unwrap(); 150 | println!("Process Handle: {:?}", handle); 151 | let mut member = DataMember::::new(handle); 152 | member.set_offset(vec![std::ptr::addr_of!(test) as usize]); 153 | unsafe { 154 | // safety: the memory being pointed to is known to be a valid usize as we control it 155 | assert_eq!(test, member.read().unwrap()); 156 | } 157 | member.write(&0xffff).unwrap(); 158 | assert_eq!(test, 0xffff); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![deny(missing_docs)] 3 | #![deny(unused_results)] 4 | #![deny(unreachable_pub)] 5 | #![deny(missing_debug_implementations)] 6 | #![deny(rust_2018_idioms)] 7 | #![deny(bad_style)] 8 | #![deny(unused)] 9 | #![deny(clippy::pedantic)] 10 | 11 | mod architecture; 12 | mod data_member; 13 | mod local_member; 14 | 15 | pub use architecture::Architecture; 16 | pub use data_member::DataMember; 17 | pub use local_member::LocalMember; 18 | 19 | #[cfg(target_os = "linux")] 20 | #[path = "linux.rs"] 21 | mod platform; 22 | #[cfg(target_os = "macos")] 23 | #[path = "macos.rs"] 24 | mod platform; 25 | #[cfg(windows)] 26 | #[path = "windows.rs"] 27 | mod platform; 28 | 29 | /// A trait that defines that it is possible to copy some memory from something represented by a 30 | /// type into a buffer. 31 | pub trait CopyAddress { 32 | /// Copy an address into user-defined buffer. 33 | /// 34 | /// # Errors 35 | /// `std::io::Error` if an error occurs copying the address. 36 | fn copy_address(&self, addr: usize, buf: &mut [u8]) -> std::io::Result<()>; 37 | 38 | /// Get the actual memory location from a set of offsets. 39 | /// 40 | /// If [`copy_address`] and [`get_pointer_width`] are already defined, then 41 | /// we can provide a standard implementation that will work across all 42 | /// operating systems. 43 | /// 44 | /// # Errors 45 | /// `std::io::Error` if an error occurs copying the address. 46 | /// 47 | /// [`copy_address`]: #tymethod.copy_address 48 | /// [`get_pointer_width`]: #tymethod.get_pointer_width 49 | fn get_offset(&self, offsets: &[usize]) -> std::io::Result { 50 | // Look ma! No unsafes! 51 | let mut offset: usize = 0; 52 | let noffsets: usize = offsets.len(); 53 | let mut copy = vec![0_u8; self.get_pointer_width() as usize]; 54 | for next_offset in offsets.iter().take(noffsets - 1) { 55 | offset += next_offset; 56 | self.copy_address(offset, &mut copy)?; 57 | offset = self.get_pointer_width().pointer_from_ne_bytes(©); 58 | } 59 | 60 | offset += offsets[noffsets - 1]; 61 | Ok(offset) 62 | } 63 | 64 | /// Get the the pointer width of the underlying process. 65 | /// This is required for [`get_offset`] to work. 66 | /// 67 | /// # Performance 68 | /// Any implementation of this function should be marked with 69 | /// `#[inline(always)]` as this function is *very* commonly called and 70 | /// should be inlined. 71 | /// 72 | /// [`get_offset`]: #method.get_offset 73 | fn get_pointer_width(&self) -> Architecture; 74 | } 75 | 76 | /// A trait that defines that it is possible to put a buffer into the memory of something 77 | /// represented by a type. 78 | pub trait PutAddress { 79 | /// Put the data from a user-defined buffer at an address. 80 | /// 81 | /// # Errors 82 | /// `std::io::Error` if an error occurs copying the address. 83 | fn put_address(&self, addr: usize, buf: &[u8]) -> std::io::Result<()>; 84 | } 85 | 86 | /// A `Pid` is a "process id". Each different platform has a different method for uniquely 87 | /// identifying a process. You can see what the Rust standard library uses for your platform by 88 | /// looking at `std::process::id`. 89 | pub use platform::Pid; 90 | /// A `ProcessHandle` is a variable type that allows for access to functions that can manipulate 91 | /// other processes. On platforms other than Linux, this is typically a different type than 92 | /// [`Pid`], and thus it is a distinct type here. 93 | /// 94 | /// [`Pid`]: type.Pid.html 95 | pub use platform::ProcessHandle; 96 | 97 | /// A trait that attempts to turn some type into a [`ProcessHandle`] so memory can be either copied 98 | /// or placed into it. 99 | /// 100 | /// [`ProcessHandle`]: type.ProcessHandle.html 101 | pub trait TryIntoProcessHandle { 102 | /// Attempt to turn a type into a [`ProcessHandle`]. Whilst Linux provides the same type for 103 | /// [`Pid`]s and [`ProcessHandle`]s, Windows and macOS do not. As such, you need to ensure that 104 | /// `try_into_process_handle` is called on all [`Pid`]s to ensure cross-platform capabilities. 105 | /// 106 | /// # Errors 107 | /// Returns an error if the type cannot be turned into a [`ProcessHandle`] 108 | /// 109 | /// [`ProcessHandle`]: type.ProcessHandle.html 110 | /// [`Pid`]: type.Pid.html 111 | fn try_into_process_handle(&self) -> std::io::Result; 112 | } 113 | 114 | impl TryIntoProcessHandle for ProcessHandle { 115 | fn try_into_process_handle(&self) -> std::io::Result { 116 | Ok(*self) 117 | } 118 | } 119 | 120 | /// Additional functions on process handles 121 | pub trait ProcessHandleExt { 122 | /// Returns `true` if the [`ProcessHandle`] is not null, and `false` otherwise. 123 | fn check_handle(&self) -> bool; 124 | /// Return the null equivalent of a [`ProcessHandle`]. 125 | #[must_use] 126 | fn null_type() -> ProcessHandle; 127 | /// Set this handle to use some architecture 128 | #[must_use] 129 | fn set_arch(self, arch: Architecture) -> Self; 130 | } 131 | 132 | /// A trait that refers to and allows writing to a region of memory in a running program. 133 | pub trait Memory { 134 | /// Set the offsets to the location in memory. This is used for things such as multi-level 135 | /// pointers, such as a `Vec>` or a `Vec`. 136 | /// 137 | /// For those sorts of data structures, to access data you need to go via multiple pointers, so 138 | /// that if an inner region reallocates its size, the variable that is being modified will be 139 | /// correctly modified. 140 | fn set_offset(&mut self, new_offsets: Vec); 141 | 142 | /// Gets the actual total offset from the offsets given by [`Memory::set_offset`]. 143 | /// 144 | /// This function is safe because it should never internally allow for a null pointer 145 | /// deference, and instead should return a `std::io::Error` with a `std::io::ErrorKind` of 146 | /// `Other`. 147 | /// 148 | /// # Errors 149 | /// Returns an error if copying memory fails or if a null pointer dereference would 150 | /// otherwise occur. 151 | /// 152 | /// [`Memory::set_offset`]: trait.Memory.html#tymethod.set_offset 153 | fn get_offset(&self) -> std::io::Result; 154 | 155 | /// Reads the value of the pointer from the offsets given by [`Memory::set_offset`]. 156 | /// 157 | /// This function should never internally allow for a null pointer deference, and instead 158 | /// should return a `std::io::Error` with a `std::io::ErrorKind` of `Other`. 159 | /// 160 | /// # Safety 161 | /// This function is marked as unsafe as it may cause undefined behavior. 162 | /// 163 | /// The function will attempt to read a `T` from uncontrolled memory, and so may produce an 164 | /// invalid value (e.g. a value of `2` for a `bool`, which is [undefined]. The caller _must_ 165 | /// ensure that the data being read is valid for a `T`, or should get an equivalent integer 166 | /// representation and check the bit pattern themselves. 167 | /// 168 | /// # Errors 169 | /// Returns an error if copying memory fails or if a null pointer dereference would 170 | /// otherwise occur. 171 | /// 172 | /// [`Memory::set_offset`]: trait.Memory.html#tymethod.set_offset 173 | /// [undefined]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html 174 | unsafe fn read(&self) -> std::io::Result; 175 | 176 | /// Writes `value` to the pointer from the offsets given by [`Memory::set_offset`]. 177 | /// 178 | /// This function is safe because it should never internally allow for a null pointer 179 | /// deference, and instead should return a `std::io::Error` with a `std::io::ErrorKind` of 180 | /// `Other`. 181 | /// 182 | /// This function takes a reference instead of taking ownership so if the caller passes in a 183 | /// `String` or a `Vec`, it does not have to be cloned. 184 | /// 185 | /// # Errors 186 | /// Returns an error if copying memory fails or if a null pointer dereference would 187 | /// otherwise occur. 188 | /// 189 | /// [`Memory::set_offset`]: trait.Memory.html#tymethod.set_offset 190 | fn write(&self, value: &T) -> std::io::Result<()>; 191 | } 192 | 193 | /// Copy `length` bytes of memory at `addr` from `source`. 194 | /// 195 | /// This is just a convenient way to call [`CopyAddress::copy_address`] without 196 | /// having to provide your own buffer. 197 | /// 198 | /// # Errors 199 | /// Returns an error if copying memory fails 200 | pub fn copy_address(addr: usize, length: usize, source: &T) -> std::io::Result> 201 | where 202 | T: CopyAddress, 203 | { 204 | let mut copy = vec![0; length]; 205 | 206 | source.copy_address(addr, &mut copy)?; 207 | Ok(copy) 208 | } 209 | -------------------------------------------------------------------------------- /src/linux.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_void, iovec, pid_t, process_vm_readv, process_vm_writev}; 2 | use std::process::Child; 3 | 4 | use super::{Architecture, CopyAddress, ProcessHandleExt, PutAddress, TryIntoProcessHandle}; 5 | 6 | /// On Linux a `Pid` is just a `libc::pid_t`. 7 | pub type Pid = pid_t; 8 | /// On Linux a `ProcessHandle` is just a `libc::pid_t`. 9 | pub type ProcessHandle = (Pid, Architecture); 10 | 11 | impl ProcessHandleExt for ProcessHandle { 12 | #[must_use] 13 | fn check_handle(&self) -> bool { 14 | self.0 != 0 15 | } 16 | #[must_use] 17 | fn null_type() -> Self { 18 | (0, Architecture::from_native()) 19 | } 20 | #[must_use] 21 | fn set_arch(self, arch: Architecture) -> Self { 22 | (self.0, arch) 23 | } 24 | } 25 | 26 | /// A `Child` always has a pid, which is all we need on Linux. 27 | impl TryIntoProcessHandle for Child { 28 | fn try_into_process_handle(&self) -> std::io::Result { 29 | #[allow(clippy::cast_possible_wrap)] 30 | Ok((self.id() as Pid, Architecture::from_native())) 31 | } 32 | } 33 | 34 | impl TryIntoProcessHandle for Pid { 35 | fn try_into_process_handle(&self) -> std::io::Result { 36 | Ok((*self, Architecture::from_native())) 37 | } 38 | } 39 | 40 | impl CopyAddress for ProcessHandle { 41 | #[allow(clippy::inline_always)] 42 | #[inline(always)] 43 | fn get_pointer_width(&self) -> Architecture { 44 | self.1 45 | } 46 | 47 | fn copy_address(&self, addr: usize, buf: &mut [u8]) -> std::io::Result<()> { 48 | let local_iov = iovec { 49 | iov_base: buf.as_mut_ptr().cast::(), 50 | iov_len: buf.len(), 51 | }; 52 | let remote_iov = iovec { 53 | iov_base: addr as *mut c_void, 54 | iov_len: buf.len(), 55 | }; 56 | let result = unsafe { process_vm_readv(self.0, &local_iov, 1, &remote_iov, 1, 0) }; 57 | if result == -1 { 58 | Err(std::io::Error::last_os_error()) 59 | } else { 60 | Ok(()) 61 | } 62 | } 63 | } 64 | 65 | impl PutAddress for ProcessHandle { 66 | fn put_address(&self, addr: usize, buf: &[u8]) -> std::io::Result<()> { 67 | let local_iov = iovec { 68 | iov_base: buf.as_ptr() as *mut c_void, 69 | iov_len: buf.len(), 70 | }; 71 | let remote_iov = iovec { 72 | iov_base: addr as *mut c_void, 73 | iov_len: buf.len(), 74 | }; 75 | let result = unsafe { process_vm_writev(self.0, &local_iov, 1, &remote_iov, 1, 0) }; 76 | if result == -1 { 77 | Err(std::io::Error::last_os_error()) 78 | } else { 79 | Ok(()) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/local_member.rs: -------------------------------------------------------------------------------- 1 | use crate::Memory; 2 | 3 | /// This struct provides functions for modifying the memory of a program from within the address 4 | /// space of that program. This may be helpful for debug functions, or for an injected DLL. 5 | /// 6 | /// # Examples: 7 | /// ```rust 8 | /// # use process_memory::{Memory, LocalMember}; 9 | /// // We have a variable with some value 10 | /// let x = 4u32; 11 | /// 12 | /// // We make a `LocalMember` that has an offset referring to its location in memory 13 | /// let member = LocalMember::new_offset(vec![&x as *const _ as usize]); 14 | /// // The memory refered to is now the same 15 | /// assert_eq!(&x as *const _ as usize, member.get_offset().unwrap()); 16 | /// // The value of the member is the same as the variable 17 | /// assert_eq!(x, unsafe { member.read().unwrap() }); 18 | /// // We can write to and modify the value of the variable using the member 19 | /// member.write(&6u32).unwrap(); 20 | /// assert_eq!(x, 6u32); 21 | /// ``` 22 | /// 23 | /// # Safety 24 | /// 25 | /// These functions are technically ***not safe***. Do not attempt to read or write to any local 26 | /// memory that you do not know is correct. If you're trying to explore your entire address space 27 | /// or are testing to see if a pointer is allocated to you, use [`DataMember`] with your own PID. 28 | /// 29 | /// Unfortunately it's not possible to implement some traits safely (e.g. [`Memory`] on 30 | /// [`DataMember`] but implement it on other structures unsafely in Rust. 31 | /// 32 | /// The implemented functions try to stop you from shooting yourself in the foot by checking none 33 | /// of the pointers end up at the null pointer, but this does not guarantee that you won't be able 34 | /// to mess something up really badly in your program. 35 | /// 36 | /// [`DataMember`]: struct.DataMember.html 37 | #[derive(Clone, Debug, Default)] 38 | pub struct LocalMember { 39 | offsets: Vec, 40 | _phantom: std::marker::PhantomData<*mut T>, 41 | } 42 | 43 | impl LocalMember { 44 | /// Creates a new `LocalMember` with no offsets. Any calls to 45 | /// [`Memory::read`] will attempt to read from a null pointer reference. 46 | /// 47 | /// To set offsets, use [`Memory::set_offset`]offset), or create the `LocalMember` using 48 | /// [`new_offset`]. 49 | /// 50 | /// [`Memory::read`]: trait.Memory.html#tymethod.read 51 | /// [`Memory::set_offset`]: trait.Memory.html#tymethod.set_offset 52 | /// [`new_offset`]: struct.LocalMember.html#method.new_offset 53 | #[must_use] 54 | pub fn new() -> Self { 55 | Self { 56 | offsets: Vec::new(), 57 | _phantom: std::marker::PhantomData, 58 | } 59 | } 60 | 61 | /// Create a new `LocalMember` with a given set of offsets. 62 | #[must_use] 63 | pub fn new_offset(offsets: Vec) -> Self { 64 | Self { 65 | offsets, 66 | _phantom: std::marker::PhantomData, 67 | } 68 | } 69 | } 70 | 71 | impl Memory for LocalMember { 72 | fn set_offset(&mut self, new_offsets: Vec) { 73 | self.offsets = new_offsets; 74 | } 75 | 76 | fn get_offset(&self) -> std::io::Result { 77 | let mut offset = 0_usize; 78 | for i in 0..self.offsets.len() - 1 { 79 | offset = offset.wrapping_add(self.offsets[i]); 80 | if offset == 0 { 81 | return Err(std::io::Error::new( 82 | std::io::ErrorKind::Other, 83 | "Would be a null dereference!", 84 | )); 85 | } 86 | // We can't guarantee alignment, so we must use `read_unaligned()` 87 | // to ensure that its ok to read from, as `read()` requires that 88 | // our source pointer is properly aligned. 89 | unsafe { 90 | offset = (offset as *const usize).read_unaligned(); 91 | } 92 | } 93 | Ok(offset.wrapping_add(self.offsets[self.offsets.len() - 1])) 94 | } 95 | 96 | /// This will only return a error if one of the offsets gives a null pointer. or give a 97 | /// non-aligned read 98 | unsafe fn read(&self) -> std::io::Result { 99 | let offset = self.get_offset()? as *const T; 100 | // Read the value of the pointer. We can't guarantee alignment, so this 101 | // is `read_unaligned()` instead of `read()` 102 | let x: T = offset.read_unaligned(); 103 | Ok(x) 104 | } 105 | 106 | /// This will only return a error if one of the offsets gives a null pointer. 107 | fn write(&self, value: &T) -> std::io::Result<()> { 108 | use std::ptr::copy_nonoverlapping; 109 | 110 | let offset = self.get_offset()? as *mut T; 111 | unsafe { 112 | copy_nonoverlapping(value, offset, 1_usize); 113 | } 114 | Ok(()) 115 | } 116 | } 117 | 118 | #[cfg(test)] 119 | mod test { 120 | use super::*; 121 | #[test] 122 | fn modify_local_i32() { 123 | let test = 4_i32; 124 | let mut member = LocalMember::::new(); 125 | member.set_offset(vec![std::ptr::addr_of!(test) as usize]); 126 | unsafe { 127 | // safety: the memory being pointed to is known to be a valid i32 as we control it 128 | assert_eq!(test, member.read().unwrap()); 129 | } 130 | member.write(&5_i32).unwrap(); 131 | assert_eq!(test, 5_i32); 132 | } 133 | #[test] 134 | fn modify_local_i64() { 135 | let test = 3_i64; 136 | let mut member = LocalMember::::new(); 137 | member.set_offset(vec![std::ptr::addr_of!(test) as usize]); 138 | unsafe { 139 | // safety: the memory being pointed to is known to be a valid i64 as we control it 140 | assert_eq!(test, member.read().unwrap()); 141 | } 142 | member.write(&-1_i64).unwrap(); 143 | assert_eq!(test, -1); 144 | } 145 | #[test] 146 | fn modify_local_usize() { 147 | let test = 0_usize; 148 | let mut member = LocalMember::::new(); 149 | member.set_offset(vec![std::ptr::addr_of!(test) as usize]); 150 | unsafe { 151 | // safety: the memory being pointed to is known to be a valid usize as we control it 152 | assert_eq!(test, member.read().unwrap()); 153 | } 154 | member.write(&0xffff).unwrap(); 155 | assert_eq!(test, 0xffff); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/macos.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_int, pid_t}; 2 | use mach::kern_return::KERN_SUCCESS; 3 | use mach::port::{mach_port_name_t, MACH_PORT_NULL}; 4 | use std::process::Child; 5 | 6 | use super::{Architecture, CopyAddress, ProcessHandleExt, PutAddress, TryIntoProcessHandle}; 7 | 8 | /// On OS X a `Pid` is just a `libc::pid_t`. 9 | pub type Pid = pid_t; 10 | /// On OS X a `ProcessHandle` is a mach port. 11 | pub type ProcessHandle = (mach_port_name_t, Architecture); 12 | 13 | impl ProcessHandleExt for ProcessHandle { 14 | #[must_use] 15 | fn check_handle(&self) -> bool { 16 | self.0 != 0 17 | } 18 | #[must_use] 19 | fn null_type() -> ProcessHandle { 20 | (0, Architecture::from_native()) 21 | } 22 | #[must_use] 23 | fn set_arch(self, arch: Architecture) -> Self { 24 | (self.0, arch) 25 | } 26 | } 27 | 28 | /// A small wrapper around `task_for_pid`, which taskes a pid returns the mach port representing its task. 29 | fn task_for_pid(pid: Pid) -> std::io::Result { 30 | let mut task: mach_port_name_t = MACH_PORT_NULL; 31 | 32 | unsafe { 33 | let result = 34 | mach::traps::task_for_pid(mach::traps::mach_task_self(), pid as c_int, &mut task); 35 | if result != KERN_SUCCESS { 36 | return Err(std::io::Error::last_os_error()); 37 | } 38 | } 39 | 40 | Ok(task) 41 | } 42 | 43 | /// `Pid` can be turned into a `ProcessHandle` with `task_for_pid`. 44 | impl TryIntoProcessHandle for Pid { 45 | fn try_into_process_handle(&self) -> std::io::Result { 46 | Ok((task_for_pid(*self)?, Architecture::from_native())) 47 | } 48 | } 49 | 50 | /// This `TryIntoProcessHandle` impl simply calls the `TryIntoProcessHandle` impl for `Pid`. 51 | impl TryIntoProcessHandle for Child { 52 | fn try_into_process_handle(&self) -> std::io::Result { 53 | #[allow(clippy::cast_possible_wrap)] 54 | Pid::try_into_process_handle(&(self.id() as _)) 55 | } 56 | } 57 | 58 | /// Here we use `mach_vm_write` to write a buffer to some arbitrary address on a process. 59 | impl PutAddress for ProcessHandle { 60 | fn put_address(&self, addr: usize, buf: &[u8]) -> std::io::Result<()> { 61 | #[allow(clippy::cast_possible_truncation)] 62 | let result = unsafe { 63 | mach::vm::mach_vm_write(self.0, addr as _, buf.as_ptr() as _, buf.len() as _) 64 | }; 65 | if result != KERN_SUCCESS { 66 | return Err(std::io::Error::last_os_error()); 67 | } 68 | Ok(()) 69 | } 70 | } 71 | 72 | /// Use `vm_read_overwrite` to read memory from another process on OS X. 73 | /// 74 | /// We use `vm_read_overwrite` instead of `vm_read` because it can handle non-aligned reads and 75 | /// won't read an entire page. 76 | impl CopyAddress for ProcessHandle { 77 | #[allow(clippy::inline_always)] 78 | #[inline(always)] 79 | fn get_pointer_width(&self) -> Architecture { 80 | self.1 81 | } 82 | 83 | fn copy_address(&self, addr: usize, buf: &mut [u8]) -> std::io::Result<()> { 84 | let mut read_len: u64 = 0; 85 | let result = unsafe { 86 | mach::vm::mach_vm_read_overwrite( 87 | self.0, 88 | addr as _, 89 | buf.len() as _, 90 | buf.as_mut_ptr() as _, 91 | &mut read_len, 92 | ) 93 | }; 94 | 95 | if result != KERN_SUCCESS { 96 | return Err(std::io::Error::last_os_error()); 97 | } 98 | 99 | if read_len == buf.len() as _ { 100 | Ok(()) 101 | } else { 102 | Err(std::io::Error::new( 103 | std::io::ErrorKind::BrokenPipe, 104 | format!( 105 | "Mismatched read sizes for `vm_read_overwrite` (expected {}, got {})", 106 | buf.len(), 107 | read_len 108 | ), 109 | )) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | use std::os::windows::io::AsRawHandle; 3 | use std::process::Child; 4 | mod windows { 5 | pub(crate) use windows::Win32::{ 6 | Foundation::HANDLE, 7 | System::{ 8 | Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory}, 9 | Threading::{ 10 | OpenProcess, PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, 11 | PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, 12 | }, 13 | }, 14 | }; 15 | } 16 | 17 | use super::{Architecture, CopyAddress, ProcessHandleExt, PutAddress, TryIntoProcessHandle}; 18 | 19 | /// On Windows a `Pid` is a unsigned 32-bit integer. 20 | pub type Pid = u32; 21 | /// On Windows a `ProcessHandle` is a `HANDLE`. 22 | pub type ProcessHandle = (windows::HANDLE, Architecture); 23 | 24 | impl ProcessHandleExt for ProcessHandle { 25 | #[must_use] 26 | fn check_handle(&self) -> bool { 27 | self.0.is_invalid() 28 | } 29 | #[must_use] 30 | fn null_type() -> ProcessHandle { 31 | (windows::HANDLE::default(), Architecture::from_native()) 32 | } 33 | #[must_use] 34 | fn set_arch(self, arch: Architecture) -> Self { 35 | (self.0, arch) 36 | } 37 | } 38 | 39 | /// A `Pid` can be turned into a `ProcessHandle` with `OpenProcess`. 40 | impl TryIntoProcessHandle for Pid { 41 | fn try_into_process_handle(&self) -> std::io::Result { 42 | Ok(( 43 | unsafe { 44 | windows::OpenProcess( 45 | windows::PROCESS_CREATE_THREAD 46 | | windows::PROCESS_QUERY_INFORMATION 47 | | windows::PROCESS_VM_READ 48 | | windows::PROCESS_VM_WRITE 49 | | windows::PROCESS_VM_OPERATION, 50 | false, 51 | *self, 52 | ) 53 | }?, 54 | Architecture::from_native(), 55 | )) 56 | } 57 | } 58 | 59 | /// A `std::process::Child` has a `HANDLE` from calling `CreateProcess`. 60 | impl TryIntoProcessHandle for Child { 61 | fn try_into_process_handle(&self) -> std::io::Result { 62 | Ok(( 63 | windows::HANDLE(self.as_raw_handle() as isize), 64 | Architecture::from_native(), 65 | )) 66 | } 67 | } 68 | 69 | /// Use `ReadProcessMemory` to read memory from another process on Windows. 70 | impl CopyAddress for ProcessHandle { 71 | #[allow(clippy::inline_always)] 72 | #[inline(always)] 73 | fn get_pointer_width(&self) -> Architecture { 74 | self.1 75 | } 76 | 77 | #[allow(clippy::ptr_as_ptr)] 78 | fn copy_address(&self, addr: usize, buf: &mut [u8]) -> std::io::Result<()> { 79 | if buf.is_empty() { 80 | return Ok(()); 81 | } 82 | 83 | if unsafe { 84 | windows::ReadProcessMemory( 85 | self.0, 86 | addr as *const c_void, 87 | buf.as_mut_ptr() as *mut c_void, 88 | buf.len(), 89 | None, 90 | ) 91 | } == false 92 | { 93 | Err(std::io::Error::last_os_error()) 94 | } else { 95 | Ok(()) 96 | } 97 | } 98 | } 99 | 100 | /// Use `WriteProcessMemory` to write memory from another process on Windows. 101 | impl PutAddress for ProcessHandle { 102 | #[allow(clippy::ptr_as_ptr)] 103 | fn put_address(&self, addr: usize, buf: &[u8]) -> std::io::Result<()> { 104 | if buf.is_empty() { 105 | return Ok(()); 106 | } 107 | if unsafe { 108 | windows::WriteProcessMemory( 109 | self.0, 110 | addr as *const c_void, 111 | buf.as_ptr().cast(), 112 | buf.len(), 113 | None, 114 | ) 115 | } == false 116 | { 117 | Err(std::io::Error::last_os_error()) 118 | } else { 119 | Ok(()) 120 | } 121 | } 122 | } 123 | --------------------------------------------------------------------------------