dll_syringe/process/memory/
buffer.rs1use 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#[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 pub fn allocate(process: BorrowedProcess<'a>, len: usize) -> Result<Self, io::Error> {
56 Self::allocate_code(process, len)
57 }
58 pub fn allocate_page(process: BorrowedProcess<'a>) -> Result<Self, io::Error> {
60 Self::allocate_code(process, Self::os_page_size())
61 }
62 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 pub fn allocate_data_page(process: BorrowedProcess<'a>) -> Result<Self, io::Error> {
68 Self::allocate_data(process, Self::os_page_size())
69 }
70 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 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 pub fn allocate_for<T>(process: BorrowedProcess<'a>) -> Result<Self, io::Error> {
108 Self::allocate_data(process, mem::size_of::<T>())
109 }
110
111 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 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 #[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 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 #[allow(clippy::must_use_candidate)]
160 pub fn leak(self) -> ProcessMemorySlice<'a> {
161 let this = ManuallyDrop::new(self);
162 this.0
163 }
164
165 #[must_use]
167 pub fn as_slice(&self) -> &ProcessMemorySlice<'a> {
168 self.as_ref()
169 }
170
171 #[must_use]
173 pub fn as_mut_slice(&mut self) -> &mut ProcessMemorySlice<'a> {
174 self.as_mut()
175 }
176
177 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 #[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#[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 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 #[must_use]
251 pub fn is_local(&self) -> bool {
252 self.process().is_current()
253 }
254
255 #[must_use]
257 pub fn is_remote(&self) -> bool {
258 !self.is_local()
259 }
260
261 #[must_use]
263 pub const fn process(&self) -> BorrowedProcess<'a> {
264 self.process
265 }
266
267 #[must_use]
269 pub const fn len(&self) -> usize {
270 self.len
271 }
272
273 #[must_use]
275 pub const fn is_empty(&self) -> bool {
276 self.len() == 0
277 }
278
279 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 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 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 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 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 #[must_use]
379 pub const fn as_ptr(&self) -> *mut u8 {
380 self.ptr
381 }
382
383 #[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 #[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 #[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 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}