process_memory/data_member.rs
1use 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)]
31pub struct DataMember<T> {
32 offsets: Vec<usize>,
33 process: ProcessHandle,
34 _phantom: std::marker::PhantomData<*mut T>,
35}
36
37impl<T: Sized + Copy> DataMember<T> {
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<usize>) -> Self {
69 Self {
70 offsets,
71 process: handle,
72 _phantom: std::marker::PhantomData,
73 }
74 }
75}
76
77impl<T: Sized + Copy> Memory<T> for DataMember<T> {
78 fn set_offset(&mut self, new_offsets: Vec<usize>) {
79 self.offsets = new_offsets;
80 }
81
82 fn get_offset(&self) -> std::io::Result<usize> {
83 self.process.get_offset(&self.offsets)
84 }
85
86 unsafe fn read(&self) -> std::io::Result<T> {
87 let offset = self.process.get_offset(&self.offsets)?;
88 // This can't be [0_u8;size_of::<T>()] 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::<T>()];
91 self.process.copy_address(offset, &mut buffer)?;
92 Ok(buffer.as_ptr().cast::<T>().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::<u8>(), std::mem::size_of::<T>())
100 };
101 self.process.put_address(offset, buffer)
102 }
103}
104
105#[cfg(test)]
106mod 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::<i32>::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::<i64>::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::<usize>::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}