wraith/km_client/
process.rs

1//! Process operations through kernel driver
2
3use super::driver::DriverHandle;
4use super::ioctl;
5use super::{ClientError, ClientResult};
6
7use std::mem::MaybeUninit;
8use std::ptr;
9
10/// process memory operations via kernel driver
11pub struct ProcessOps<'a> {
12    handle: &'a DriverHandle,
13    pid: u32,
14}
15
16impl<'a> ProcessOps<'a> {
17    /// create new process operations
18    pub(crate) fn new(handle: &'a DriverHandle, pid: u32) -> ClientResult<Self> {
19        Ok(Self { handle, pid })
20    }
21
22    /// get process ID
23    pub fn pid(&self) -> u32 {
24        self.pid
25    }
26
27    /// read value from process memory
28    pub fn read<T: Copy>(&self, address: u64) -> ClientResult<T> {
29        let request = ioctl::ReadMemoryRequest {
30            process_id: self.pid,
31            address,
32            size: std::mem::size_of::<T>() as u32,
33        };
34
35        let mut buffer = vec![0u8; std::mem::size_of::<T>()];
36
37        let bytes = self.handle.ioctl_raw(
38            ioctl::codes::READ_MEMORY.code(),
39            unsafe {
40                std::slice::from_raw_parts(
41                    &request as *const _ as *const u8,
42                    std::mem::size_of::<ioctl::ReadMemoryRequest>(),
43                )
44            },
45            &mut buffer,
46        )?;
47
48        if bytes as usize != std::mem::size_of::<T>() {
49            return Err(ClientError::InvalidResponse {
50                expected: std::mem::size_of::<T>(),
51                received: bytes as usize,
52            });
53        }
54
55        Ok(unsafe { ptr::read(buffer.as_ptr() as *const T) })
56    }
57
58    /// read bytes from process memory
59    pub fn read_bytes(&self, address: u64, size: usize) -> ClientResult<Vec<u8>> {
60        let request = ioctl::ReadMemoryRequest {
61            process_id: self.pid,
62            address,
63            size: size as u32,
64        };
65
66        let mut buffer = vec![0u8; size];
67
68        let bytes = self.handle.ioctl_raw(
69            ioctl::codes::READ_MEMORY.code(),
70            unsafe {
71                std::slice::from_raw_parts(
72                    &request as *const _ as *const u8,
73                    std::mem::size_of::<ioctl::ReadMemoryRequest>(),
74                )
75            },
76            &mut buffer,
77        )?;
78
79        buffer.truncate(bytes as usize);
80        Ok(buffer)
81    }
82
83    /// write value to process memory
84    pub fn write<T: Copy>(&self, address: u64, value: &T) -> ClientResult<()> {
85        let header_size = std::mem::size_of::<ioctl::WriteMemoryRequest>();
86        let data_size = std::mem::size_of::<T>();
87        let mut buffer = vec![0u8; header_size + data_size];
88
89        let request = ioctl::WriteMemoryRequest {
90            process_id: self.pid,
91            address,
92            size: data_size as u32,
93        };
94
95        unsafe {
96            ptr::copy_nonoverlapping(
97                &request as *const _ as *const u8,
98                buffer.as_mut_ptr(),
99                header_size,
100            );
101            ptr::copy_nonoverlapping(
102                value as *const T as *const u8,
103                buffer.as_mut_ptr().add(header_size),
104                data_size,
105            );
106        }
107
108        let _ = self.handle.ioctl_raw(ioctl::codes::WRITE_MEMORY.code(), &buffer, &mut [])?;
109        Ok(())
110    }
111
112    /// write bytes to process memory
113    pub fn write_bytes(&self, address: u64, data: &[u8]) -> ClientResult<()> {
114        let header_size = std::mem::size_of::<ioctl::WriteMemoryRequest>();
115        let mut buffer = vec![0u8; header_size + data.len()];
116
117        let request = ioctl::WriteMemoryRequest {
118            process_id: self.pid,
119            address,
120            size: data.len() as u32,
121        };
122
123        unsafe {
124            ptr::copy_nonoverlapping(
125                &request as *const _ as *const u8,
126                buffer.as_mut_ptr(),
127                header_size,
128            );
129            ptr::copy_nonoverlapping(
130                data.as_ptr(),
131                buffer.as_mut_ptr().add(header_size),
132                data.len(),
133            );
134        }
135
136        let _ = self.handle.ioctl_raw(ioctl::codes::WRITE_MEMORY.code(), &buffer, &mut [])?;
137        Ok(())
138    }
139
140    /// get module base address
141    pub fn get_module_base(&self, module_name: &str) -> ClientResult<u64> {
142        let name_bytes = module_name.as_bytes();
143        let header_size = std::mem::size_of::<ioctl::GetModuleBaseRequest>();
144        let total_size = header_size + name_bytes.len();
145
146        let mut input = vec![0u8; total_size];
147        let request = ioctl::GetModuleBaseRequest {
148            process_id: self.pid,
149            module_name_offset: header_size as u32,
150            module_name_length: name_bytes.len() as u32,
151        };
152
153        unsafe {
154            ptr::copy_nonoverlapping(
155                &request as *const _ as *const u8,
156                input.as_mut_ptr(),
157                header_size,
158            );
159            ptr::copy_nonoverlapping(
160                name_bytes.as_ptr(),
161                input.as_mut_ptr().add(header_size),
162                name_bytes.len(),
163            );
164        }
165
166        let mut response = MaybeUninit::<ioctl::GetModuleBaseResponse>::uninit();
167
168        let bytes = self.handle.ioctl(
169            ioctl::codes::GET_MODULE_BASE.code(),
170            Some(&input[..]),
171            Some(unsafe { response.assume_init_mut() }),
172        )?;
173
174        if bytes as usize != std::mem::size_of::<ioctl::GetModuleBaseResponse>() {
175            return Err(ClientError::InvalidResponse {
176                expected: std::mem::size_of::<ioctl::GetModuleBaseResponse>(),
177                received: bytes as usize,
178            });
179        }
180
181        Ok(unsafe { response.assume_init() }.base_address)
182    }
183
184    /// allocate virtual memory
185    pub fn allocate(&self, size: u64, protection: u32) -> ClientResult<u64> {
186        let request = ioctl::AllocateMemoryRequest {
187            process_id: self.pid,
188            size,
189            protection,
190            preferred_address: 0,
191        };
192
193        let mut response = MaybeUninit::<ioctl::AllocateMemoryResponse>::uninit();
194
195        let bytes = self.handle.ioctl(
196            ioctl::codes::ALLOCATE_MEMORY.code(),
197            Some(&request),
198            Some(unsafe { response.assume_init_mut() }),
199        )?;
200
201        if bytes as usize != std::mem::size_of::<ioctl::AllocateMemoryResponse>() {
202            return Err(ClientError::InvalidResponse {
203                expected: std::mem::size_of::<ioctl::AllocateMemoryResponse>(),
204                received: bytes as usize,
205            });
206        }
207
208        Ok(unsafe { response.assume_init() }.allocated_address)
209    }
210
211    /// allocate memory at preferred address
212    pub fn allocate_at(&self, address: u64, size: u64, protection: u32) -> ClientResult<u64> {
213        let request = ioctl::AllocateMemoryRequest {
214            process_id: self.pid,
215            size,
216            protection,
217            preferred_address: address,
218        };
219
220        let mut response = MaybeUninit::<ioctl::AllocateMemoryResponse>::uninit();
221
222        let bytes = self.handle.ioctl(
223            ioctl::codes::ALLOCATE_MEMORY.code(),
224            Some(&request),
225            Some(unsafe { response.assume_init_mut() }),
226        )?;
227
228        if bytes as usize != std::mem::size_of::<ioctl::AllocateMemoryResponse>() {
229            return Err(ClientError::InvalidResponse {
230                expected: std::mem::size_of::<ioctl::AllocateMemoryResponse>(),
231                received: bytes as usize,
232            });
233        }
234
235        Ok(unsafe { response.assume_init() }.allocated_address)
236    }
237
238    /// free allocated memory
239    pub fn free(&self, address: u64) -> ClientResult<()> {
240        let request = ioctl::FreeMemoryRequest {
241            process_id: self.pid,
242            address,
243        };
244
245        let _ = self.handle.ioctl(
246            ioctl::codes::FREE_MEMORY.code(),
247            Some(&request),
248            None::<&mut ()>,
249        )?;
250        Ok(())
251    }
252
253    /// change memory protection
254    pub fn protect(&self, address: u64, size: u64, protection: u32) -> ClientResult<u32> {
255        let request = ioctl::ProtectMemoryRequest {
256            process_id: self.pid,
257            address,
258            size,
259            new_protection: protection,
260        };
261
262        let mut response = MaybeUninit::<ioctl::ProtectMemoryResponse>::uninit();
263
264        let bytes = self.handle.ioctl(
265            ioctl::codes::PROTECT_MEMORY.code(),
266            Some(&request),
267            Some(unsafe { response.assume_init_mut() }),
268        )?;
269
270        if bytes as usize != std::mem::size_of::<ioctl::ProtectMemoryResponse>() {
271            return Err(ClientError::InvalidResponse {
272                expected: std::mem::size_of::<ioctl::ProtectMemoryResponse>(),
273                received: bytes as usize,
274            });
275        }
276
277        Ok(unsafe { response.assume_init() }.old_protection)
278    }
279
280    /// read null-terminated string
281    pub fn read_string(&self, address: u64, max_len: usize) -> ClientResult<String> {
282        let bytes = self.read_bytes(address, max_len)?;
283        let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
284        String::from_utf8_lossy(&bytes[..len]).into_owned().pipe(Ok)
285    }
286
287    /// read null-terminated wide string
288    pub fn read_wstring(&self, address: u64, max_chars: usize) -> ClientResult<String> {
289        let bytes = self.read_bytes(address, max_chars * 2)?;
290        let chars: Vec<u16> = bytes
291            .chunks_exact(2)
292            .map(|c| u16::from_le_bytes([c[0], c[1]]))
293            .take_while(|&c| c != 0)
294            .collect();
295        String::from_utf16_lossy(&chars).pipe(Ok)
296    }
297}
298
299/// extension trait for pipe operator
300trait Pipe: Sized {
301    fn pipe<F, R>(self, f: F) -> R
302    where
303        F: FnOnce(Self) -> R,
304    {
305        f(self)
306    }
307}
308
309impl<T> Pipe for T {}