Skip to main content

dll_syringe/process/memory/
buffer.rs

1use std::{
2    io,
3    marker::PhantomData,
4    mem::{self, ManuallyDrop, MaybeUninit},
5    ops::{Deref, DerefMut, RangeBounds},
6    os::windows::prelude::AsRawHandle,
7    ptr, slice,
8};
9
10use winapi::{
11    shared::minwindef::DWORD,
12    um::{
13        memoryapi::{ReadProcessMemory, VirtualAllocEx, VirtualFreeEx, WriteProcessMemory},
14        processthreadsapi::FlushInstructionCache,
15        sysinfoapi::GetSystemInfo,
16        winnt::{MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_EXECUTE_READWRITE, PAGE_READWRITE},
17    },
18};
19
20use crate::{
21    process::{BorrowedProcess, Process},
22    utils,
23};
24
25/// A owned buffer in the memory space of a (remote) process.
26#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "process-memory")))]
27#[derive(Debug)]
28pub struct ProcessMemoryBuffer<'a>(ProcessMemorySlice<'a>);
29
30impl<'a> Deref for ProcessMemoryBuffer<'a> {
31    type Target = ProcessMemorySlice<'a>;
32
33    fn deref(&self) -> &ProcessMemorySlice<'a> {
34        &self.0
35    }
36}
37impl<'a> DerefMut for ProcessMemoryBuffer<'a> {
38    fn deref_mut(&mut self) -> &mut ProcessMemorySlice<'a> {
39        &mut self.0
40    }
41}
42impl<'a> AsRef<ProcessMemorySlice<'a>> for ProcessMemoryBuffer<'a> {
43    fn as_ref(&self) -> &ProcessMemorySlice<'a> {
44        self.deref()
45    }
46}
47impl<'a> AsMut<ProcessMemorySlice<'a>> for ProcessMemoryBuffer<'a> {
48    fn as_mut(&mut self) -> &mut ProcessMemorySlice<'a> {
49        self.deref_mut()
50    }
51}
52
53impl<'a> ProcessMemoryBuffer<'a> {
54    /// Allocates a new buffer of the given length in the given process. Both data and code can be stored in the buffer.
55    pub fn allocate(process: BorrowedProcess<'a>, len: usize) -> Result<Self, io::Error> {
56        Self::allocate_code(process, len)
57    }
58    /// Allocates a new buffer with the size of a memory page in the given process.
59    pub fn allocate_page(process: BorrowedProcess<'a>) -> Result<Self, io::Error> {
60        Self::allocate_code(process, Self::os_page_size())
61    }
62    /// Allocates a new data buffer of the given length in the given process.
63    pub fn allocate_data(process: BorrowedProcess<'a>, len: usize) -> Result<Self, io::Error> {
64        Self::allocate_with_options(process, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
65    }
66    /// Allocates a new data buffer with the size of a memory page in the given process.
67    pub fn allocate_data_page(process: BorrowedProcess<'a>) -> Result<Self, io::Error> {
68        Self::allocate_data(process, Self::os_page_size())
69    }
70    /// Allocates a new codea buffer of the given length in the given process.
71    pub fn allocate_code(process: BorrowedProcess<'a>, len: usize) -> Result<Self, io::Error> {
72        Self::allocate_with_options(
73            process,
74            len,
75            MEM_COMMIT | MEM_RESERVE,
76            PAGE_EXECUTE_READWRITE,
77        )
78    }
79    /// Allocates a new code buffer with the size of a memory page in the given process.
80    pub fn allocate_code_page(process: BorrowedProcess<'a>) -> Result<Self, io::Error> {
81        Self::allocate_code(process, Self::os_page_size())
82    }
83    fn allocate_with_options(
84        process: BorrowedProcess<'a>,
85        len: usize,
86        allocation_type: DWORD,
87        protection: DWORD,
88    ) -> Result<Self, io::Error> {
89        let ptr = unsafe {
90            VirtualAllocEx(
91                process.as_raw_handle().cast(),
92                ptr::null_mut(),
93                len,
94                allocation_type,
95                protection,
96            )
97        };
98
99        if ptr.is_null() {
100            Err(io::Error::last_os_error())
101        } else {
102            Ok(unsafe { Self::from_raw_parts(ptr.cast(), len, process) })
103        }
104    }
105
106    /// Allocates a new buffer with enough space to store a value of type `T` in the given process.
107    pub fn allocate_for<T>(process: BorrowedProcess<'a>) -> Result<Self, io::Error> {
108        Self::allocate_data(process, mem::size_of::<T>())
109    }
110
111    /// Allocates a new buffer with enough space to store a value of type `T` in the given process.
112    pub fn allocate_and_write<T: ?Sized>(
113        process: BorrowedProcess<'a>,
114        s: &T,
115    ) -> Result<Self, io::Error> {
116        let buf = Self::allocate_data(process, mem::size_of_val(s))?;
117        buf.write_struct(0, s)?;
118        Ok(buf)
119    }
120
121    /// Constructs a new buffer from the given raw parts.
122    ///
123    /// # Safety
124    /// The caller must ensure that the designated region of memory
125    /// - is valid
126    /// - was allocated using [`VirtualAllocEx`]
127    /// - can be deallocated using [`VirtualFreeEx`]
128    /// - can be read using [`ReadProcessMemory`]
129    /// - can be written to using [`WriteProcessMemory`]
130    /// - will not be deallocated by other code
131    pub const unsafe fn from_raw_parts(
132        ptr: *mut u8,
133        len: usize,
134        process: BorrowedProcess<'a>,
135    ) -> Self {
136        Self(unsafe { ProcessMemorySlice::from_raw_parts(ptr, len, process) })
137    }
138
139    /// Constructs a new buffer from the given raw parts.
140    #[must_use]
141    pub fn into_raw_parts(self) -> (*mut u8, usize, BorrowedProcess<'a>) {
142        let parts = (self.ptr, self.len, self.process);
143        self.leak();
144        parts
145    }
146
147    /// Leaks the buffer and returns the underlying memory slice if the buffer is allocated in the current process.
148    pub fn into_dangling_local_slice(self) -> Result<&'static mut [u8], Self> {
149        if self.process.is_current() {
150            let slice = unsafe { slice::from_raw_parts_mut(self.ptr, self.len) };
151            self.leak();
152            Ok(slice)
153        } else {
154            Err(self)
155        }
156    }
157
158    /// Leaks the buffer and returns a [`ProcessMemorySlice`] spanning this buffer.
159    #[allow(clippy::must_use_candidate)]
160    pub fn leak(self) -> ProcessMemorySlice<'a> {
161        let this = ManuallyDrop::new(self);
162        this.0
163    }
164
165    /// Constructs a new slice spanning the whole buffer.
166    #[must_use]
167    pub fn as_slice(&self) -> &ProcessMemorySlice<'a> {
168        self.as_ref()
169    }
170
171    /// Constructs a new mutable slice spanning the whole buffer.
172    #[must_use]
173    pub fn as_mut_slice(&mut self) -> &mut ProcessMemorySlice<'a> {
174        self.as_mut()
175    }
176
177    /// Frees the buffer.
178    pub fn free(mut self) -> Result<(), (Self, io::Error)> {
179        unsafe { self._free() }.map_err(|e| (self, e))
180    }
181    unsafe fn _free(&mut self) -> Result<(), io::Error> {
182        let result = unsafe {
183            VirtualFreeEx(
184                self.process.as_raw_handle().cast(),
185                self.as_ptr().cast(),
186                0,
187                MEM_RELEASE,
188            )
189        };
190
191        if result != 0 || !self.process().is_alive() {
192            Ok(())
193        } else {
194            Err(io::Error::last_os_error())
195        }
196    }
197
198    /// Returns the memory page size of the operating system.
199    #[must_use]
200    pub fn os_page_size() -> usize {
201        let mut system_info = MaybeUninit::uninit();
202        unsafe { GetSystemInfo(system_info.as_mut_ptr()) };
203        unsafe { system_info.assume_init() }.dwPageSize as usize
204    }
205}
206
207impl Drop for ProcessMemoryBuffer<'_> {
208    fn drop(&mut self) {
209        let result = unsafe { self._free() };
210        debug_assert!(
211            result.is_ok(),
212            "Failed to free process memory buffer: {result:?}"
213        );
214    }
215}
216
217/// A unowned slice of a buffer in the memory space of a (remote) process.
218#[derive(Debug, Clone, Copy)]
219pub struct ProcessMemorySlice<'a> {
220    process: BorrowedProcess<'a>,
221    ptr: *mut u8,
222    len: usize,
223    data: PhantomData<&'a [u8]>,
224}
225
226impl<'a> ProcessMemorySlice<'a> {
227    /// Constructs a new slice from the given raw parts.
228    ///
229    /// # Safety
230    /// The caller must ensure that the designated region of memory
231    /// - is valid
232    /// - was allocated using [`VirtualAllocEx`]
233    /// - can be read using [`ReadProcessMemory`]
234    /// - can be written to using [`WriteProcessMemory`]
235    /// - will live as long as the slice is used
236    pub const unsafe fn from_raw_parts(
237        ptr: *mut u8,
238        len: usize,
239        process: BorrowedProcess<'a>,
240    ) -> Self {
241        Self {
242            ptr,
243            len,
244            process,
245            data: PhantomData,
246        }
247    }
248
249    /// Returns whether the memory is allocated in the current process.
250    #[must_use]
251    pub fn is_local(&self) -> bool {
252        self.process().is_current()
253    }
254
255    /// Returns whether the memory is allocated in another process.
256    #[must_use]
257    pub fn is_remote(&self) -> bool {
258        !self.is_local()
259    }
260
261    /// Returns the process the buffer is allocated in.
262    #[must_use]
263    pub const fn process(&self) -> BorrowedProcess<'a> {
264        self.process
265    }
266
267    /// Returns the length of the buffer.
268    #[must_use]
269    pub const fn len(&self) -> usize {
270        self.len
271    }
272
273    /// Returns whether the buffer is empty.
274    #[must_use]
275    pub const fn is_empty(&self) -> bool {
276        self.len() == 0
277    }
278
279    /// Copies the contents of this buffer starting from the given offset to the given local buffer.
280    ///
281    /// # Panics
282    /// This function will panic if the given offset plus the given buffer length exceeds this buffer's length.
283    pub fn read(&self, offset: usize, buf: &mut [u8]) -> Result<(), io::Error> {
284        assert!(offset + buf.len() <= self.len, "read out of bounds");
285
286        if self.is_local() {
287            unsafe {
288                ptr::copy(self.ptr.add(offset), buf.as_mut_ptr(), buf.len());
289            }
290            return Ok(());
291        }
292
293        let mut bytes_read = 0;
294        let result = unsafe {
295            ReadProcessMemory(
296                self.process.as_raw_handle().cast(),
297                self.ptr.add(offset).cast(),
298                buf.as_mut_ptr().cast(),
299                buf.len(),
300                &mut bytes_read,
301            )
302        };
303        if result == 0 {
304            Err(io::Error::last_os_error())
305        } else {
306            assert_eq!(bytes_read, buf.len());
307            Ok(())
308        }
309    }
310
311    /// Reads a value of type `T` from this buffer starting from the given offset.
312    ///
313    /// # Panics
314    /// This function will panic if the given offset plus the size of the value exceeds this buffer's length.
315    ///
316    /// # Safety
317    /// The caller must ensure that the designated region of memory contains a valid instance of type `T` at the given offset.
318    pub unsafe fn read_struct<T>(&self, offset: usize) -> Result<T, io::Error> {
319        let mut uninit_value = MaybeUninit::<T>::uninit();
320        self.read(offset, unsafe {
321            slice::from_raw_parts_mut(uninit_value.as_mut_ptr().cast(), mem::size_of::<T>())
322        })?;
323        Ok(unsafe { uninit_value.assume_init() })
324    }
325
326    /// Copies the contents of the given local buffer to this buffer at the given offset.
327    ///
328    /// # Panics
329    /// This function will panic if the given offset plus the size of the local buffer exceeds this buffer's length.
330    pub fn write(&self, offset: usize, buf: &[u8]) -> Result<(), io::Error> {
331        assert!(offset + buf.len() <= self.len, "write out of bounds");
332
333        if self.is_local() {
334            unsafe {
335                ptr::copy(buf.as_ptr(), self.ptr.add(offset), buf.len());
336            }
337            return Ok(());
338        }
339
340        let mut bytes_written = 0;
341        if buf.is_empty() {
342            // This works around a discrepancy between Wine and actual Windows.
343            // On Wine, a 0 sized write fails, on Windows this suceeds. Will file as bug soon.
344            return Ok(());
345        }
346
347        let result = unsafe {
348            WriteProcessMemory(
349                self.process.as_raw_handle().cast(),
350                self.ptr.add(offset).cast(),
351                buf.as_ptr().cast(),
352                buf.len(),
353                &mut bytes_written,
354            )
355        };
356        if result == 0 {
357            Err(io::Error::last_os_error())
358        } else {
359            assert_eq!(bytes_written, buf.len());
360            Ok(())
361        }
362    }
363
364    /// Writes a value of type `T` to this buffer at the given offset.
365    ///
366    /// # Panics
367    /// This function will panic if the given offset plus the given buffer length exceeds this buffer's length.
368    pub fn write_struct<T: ?Sized>(&self, offset: usize, s: &T) -> Result<(), io::Error> {
369        self.write(offset, unsafe {
370            slice::from_raw_parts(s as *const T as *const u8, mem::size_of_val(s))
371        })
372    }
373
374    /// Returns a pointer to the start of the buffer.
375    ///
376    /// # Note
377    /// The returned pointer is only valid in the target process.
378    #[must_use]
379    pub const fn as_ptr(&self) -> *mut u8 {
380        self.ptr
381    }
382
383    /// Returns a slice of this buffer.
384    #[must_use]
385    pub fn slice(&self, bounds: impl RangeBounds<usize>) -> Self {
386        let range = utils::range_from_bounds(self.ptr as usize, self.len, &bounds);
387        Self {
388            process: self.process,
389            ptr: range.start as *mut _,
390            len: range.len(),
391            data: PhantomData,
392        }
393    }
394
395    /// Constructs a new slice spanning the whole buffer.
396    #[must_use]
397    pub fn as_local_slice(&self) -> Option<&[u8]> {
398        if self.is_local() {
399            Some(unsafe { slice::from_raw_parts(self.ptr, self.len) })
400        } else {
401            None
402        }
403    }
404
405    /// Constructs a new mutable slice spanning the whole buffer.
406    #[must_use]
407    pub fn as_local_slice_mut(&mut self) -> Option<&mut [u8]> {
408        if self.is_local() {
409            Some(unsafe { slice::from_raw_parts_mut(self.ptr, self.len) })
410        } else {
411            None
412        }
413    }
414
415    /// Flushes the CPU instruction cache for the whole buffer.
416    /// This may be necesary if the buffer is used to store dynamically generated code. For details see [`FlushInstructionCache`].
417    pub fn flush_instruction_cache(&self) -> Result<(), io::Error> {
418        let result = unsafe {
419            FlushInstructionCache(
420                self.process.as_raw_handle().cast(),
421                self.as_ptr().cast(),
422                self.len,
423            )
424        };
425        if result == 0 {
426            Err(io::Error::last_os_error())
427        } else {
428            Ok(())
429        }
430    }
431}