wraith/km/
process.rs

1//! Kernel-mode process operations
2
3use core::ffi::c_void;
4use core::ptr::NonNull;
5use alloc::vec::Vec;
6
7use super::error::{status, KmError, KmResult, NtStatus};
8use super::memory::{AccessMode, Mdl, LockOperation};
9
10/// EPROCESS structure (opaque)
11pub struct Eprocess {
12    raw: NonNull<c_void>,
13}
14
15impl Eprocess {
16    /// lookup EPROCESS by process ID
17    pub fn lookup(process_id: u32) -> KmResult<Self> {
18        let mut eprocess: *mut c_void = core::ptr::null_mut();
19
20        // SAFETY: kernel API call
21        let status = unsafe {
22            PsLookupProcessByProcessId(process_id as *mut c_void, &mut eprocess)
23        };
24
25        if !status::nt_success(status) {
26            return Err(KmError::ProcessOperationFailed {
27                pid: process_id,
28                reason: "PsLookupProcessByProcessId failed",
29            });
30        }
31
32        NonNull::new(eprocess)
33            .map(|raw| Self { raw })
34            .ok_or(KmError::ProcessOperationFailed {
35                pid: process_id,
36                reason: "process not found",
37            })
38    }
39
40    /// get raw EPROCESS pointer
41    pub fn as_raw(&self) -> *mut c_void {
42        self.raw.as_ptr()
43    }
44
45    /// dereference (must call when done)
46    pub fn dereference(&self) {
47        // SAFETY: valid EPROCESS
48        unsafe {
49            ObDereferenceObject(self.raw.as_ptr());
50        }
51    }
52
53    /// get process ID
54    pub fn process_id(&self) -> u32 {
55        // SAFETY: valid EPROCESS
56        unsafe { PsGetProcessId(self.raw.as_ptr()) as u32 }
57    }
58
59    /// get process CR3 (directory table base)
60    pub fn cr3(&self) -> u64 {
61        // SAFETY: valid EPROCESS - this reads DirectoryTableBase field
62        // offset varies by Windows version, using documented API
63        unsafe { PsGetProcessCr3(self.raw.as_ptr()) }
64    }
65
66    /// get process image file name (up to 15 chars)
67    pub fn image_file_name(&self) -> [u8; 15] {
68        let mut name = [0u8; 15];
69        // SAFETY: valid EPROCESS
70        let ptr = unsafe { PsGetProcessImageFileName(self.raw.as_ptr()) };
71        if !ptr.is_null() {
72            unsafe {
73                for (i, byte) in name.iter_mut().enumerate() {
74                    let b = *ptr.add(i);
75                    if b == 0 {
76                        break;
77                    }
78                    *byte = b;
79                }
80            }
81        }
82        name
83    }
84
85    /// check if process is terminating
86    pub fn is_terminating(&self) -> bool {
87        // SAFETY: valid EPROCESS
88        unsafe { PsGetProcessExitStatus(self.raw.as_ptr()) != 0x103 } // STATUS_PENDING
89    }
90
91    /// get process PEB (usermode)
92    pub fn peb(&self) -> *mut c_void {
93        // SAFETY: valid EPROCESS
94        unsafe { PsGetProcessPeb(self.raw.as_ptr()) }
95    }
96
97    /// get process WoW64 PEB (32-bit PEB on 64-bit Windows)
98    pub fn wow64_peb(&self) -> *mut c_void {
99        // SAFETY: valid EPROCESS
100        unsafe { PsGetProcessWow64Process(self.raw.as_ptr()) }
101    }
102}
103
104impl Drop for Eprocess {
105    fn drop(&mut self) {
106        self.dereference();
107    }
108}
109
110/// process access flags for opening
111#[repr(u32)]
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum ProcessAccess {
114    Terminate = 0x0001,
115    CreateThread = 0x0002,
116    VmOperation = 0x0008,
117    VmRead = 0x0010,
118    VmWrite = 0x0020,
119    DupHandle = 0x0040,
120    QueryInformation = 0x0400,
121    SetInformation = 0x0200,
122    All = 0x001F0FFF,
123}
124
125/// kernel process wrapper for memory operations
126pub struct KmProcess {
127    eprocess: Eprocess,
128    apc_state: ApcState,
129    attached: bool,
130}
131
132/// APC state for process attachment
133#[repr(C)]
134struct ApcState {
135    apc_list_head: [[*mut c_void; 2]; 2],
136    process: *mut c_void,
137    kernel_apc_in_progress: u8,
138    kernel_apc_pending: u8,
139    user_apc_pending: u8,
140}
141
142impl Default for ApcState {
143    fn default() -> Self {
144        Self {
145            apc_list_head: [[core::ptr::null_mut(); 2]; 2],
146            process: core::ptr::null_mut(),
147            kernel_apc_in_progress: 0,
148            kernel_apc_pending: 0,
149            user_apc_pending: 0,
150        }
151    }
152}
153
154impl KmProcess {
155    /// open process by ID
156    pub fn open(process_id: u32) -> KmResult<Self> {
157        let eprocess = Eprocess::lookup(process_id)?;
158        Ok(Self {
159            eprocess,
160            apc_state: ApcState::default(),
161            attached: false,
162        })
163    }
164
165    /// get EPROCESS
166    pub fn eprocess(&self) -> &Eprocess {
167        &self.eprocess
168    }
169
170    /// get process ID
171    pub fn id(&self) -> u32 {
172        self.eprocess.process_id()
173    }
174
175    /// attach to process address space
176    pub fn attach(&mut self) -> KmResult<()> {
177        if self.attached {
178            return Ok(());
179        }
180
181        // SAFETY: valid EPROCESS
182        unsafe {
183            KeStackAttachProcess(self.eprocess.as_raw(), &mut self.apc_state as *mut _ as *mut _);
184        }
185
186        self.attached = true;
187        Ok(())
188    }
189
190    /// detach from process address space
191    pub fn detach(&mut self) {
192        if self.attached {
193            // SAFETY: we are attached
194            unsafe {
195                KeUnstackDetachProcess(&mut self.apc_state as *mut _ as *mut _);
196            }
197            self.attached = false;
198        }
199    }
200
201    /// read memory from process
202    pub fn read<T: Copy>(&mut self, address: u64) -> KmResult<T> {
203        let mut value = core::mem::MaybeUninit::<T>::uninit();
204
205        self.read_bytes(
206            address,
207            unsafe { core::slice::from_raw_parts_mut(value.as_mut_ptr() as *mut u8, core::mem::size_of::<T>()) },
208        )?;
209
210        Ok(unsafe { value.assume_init() })
211    }
212
213    /// read bytes from process
214    pub fn read_bytes(&mut self, address: u64, buffer: &mut [u8]) -> KmResult<usize> {
215        if buffer.is_empty() {
216            return Ok(0);
217        }
218
219        self.attach()?;
220
221        let mut bytes_read = 0usize;
222
223        // SAFETY: we're attached to the process
224        let status = unsafe {
225            MmCopyVirtualMemory(
226                self.eprocess.as_raw(),
227                address as *const c_void,
228                PsGetCurrentProcess(),
229                buffer.as_mut_ptr() as *mut c_void,
230                buffer.len(),
231                AccessMode::KernelMode as u8,
232                &mut bytes_read,
233            )
234        };
235
236        self.detach();
237
238        if !status::nt_success(status) {
239            return Err(KmError::ProcessOperationFailed {
240                pid: self.id(),
241                reason: "MmCopyVirtualMemory read failed",
242            });
243        }
244
245        Ok(bytes_read)
246    }
247
248    /// write memory to process
249    pub fn write<T: Copy>(&mut self, address: u64, value: &T) -> KmResult<()> {
250        let bytes = unsafe {
251            core::slice::from_raw_parts(value as *const T as *const u8, core::mem::size_of::<T>())
252        };
253        self.write_bytes(address, bytes)?;
254        Ok(())
255    }
256
257    /// write bytes to process
258    pub fn write_bytes(&mut self, address: u64, buffer: &[u8]) -> KmResult<usize> {
259        if buffer.is_empty() {
260            return Ok(0);
261        }
262
263        self.attach()?;
264
265        let mut bytes_written = 0usize;
266
267        // SAFETY: we're attached to the process
268        let status = unsafe {
269            MmCopyVirtualMemory(
270                PsGetCurrentProcess(),
271                buffer.as_ptr() as *const c_void,
272                self.eprocess.as_raw(),
273                address as *mut c_void,
274                buffer.len(),
275                AccessMode::KernelMode as u8,
276                &mut bytes_written,
277            )
278        };
279
280        self.detach();
281
282        if !status::nt_success(status) {
283            return Err(KmError::ProcessOperationFailed {
284                pid: self.id(),
285                reason: "MmCopyVirtualMemory write failed",
286            });
287        }
288
289        Ok(bytes_written)
290    }
291
292    /// allocate virtual memory in process
293    pub fn allocate(
294        &mut self,
295        size: usize,
296        protection: u32,
297        preferred_address: Option<u64>,
298    ) -> KmResult<u64> {
299        let mut base_address = preferred_address.unwrap_or(0) as *mut c_void;
300        let mut region_size = size;
301
302        let process_handle = self.open_handle(ProcessAccess::VmOperation as u32)?;
303
304        // SAFETY: valid handle
305        let status = unsafe {
306            ZwAllocateVirtualMemory(
307                process_handle,
308                &mut base_address,
309                0,
310                &mut region_size,
311                0x3000, // MEM_COMMIT | MEM_RESERVE
312                protection,
313            )
314        };
315
316        unsafe { ZwClose(process_handle) };
317
318        if !status::nt_success(status) {
319            return Err(KmError::ProcessOperationFailed {
320                pid: self.id(),
321                reason: "ZwAllocateVirtualMemory failed",
322            });
323        }
324
325        Ok(base_address as u64)
326    }
327
328    /// free virtual memory in process
329    pub fn free(&mut self, address: u64) -> KmResult<()> {
330        let mut base_address = address as *mut c_void;
331        let mut region_size = 0usize;
332
333        let process_handle = self.open_handle(ProcessAccess::VmOperation as u32)?;
334
335        // SAFETY: valid handle
336        let status = unsafe {
337            ZwFreeVirtualMemory(
338                process_handle,
339                &mut base_address,
340                &mut region_size,
341                0x8000, // MEM_RELEASE
342            )
343        };
344
345        unsafe { ZwClose(process_handle) };
346
347        if !status::nt_success(status) {
348            return Err(KmError::ProcessOperationFailed {
349                pid: self.id(),
350                reason: "ZwFreeVirtualMemory failed",
351            });
352        }
353
354        Ok(())
355    }
356
357    /// change memory protection
358    pub fn protect(&mut self, address: u64, size: usize, protection: u32) -> KmResult<u32> {
359        let mut base_address = address as *mut c_void;
360        let mut region_size = size;
361        let mut old_protection = 0u32;
362
363        let process_handle = self.open_handle(ProcessAccess::VmOperation as u32)?;
364
365        // SAFETY: valid handle
366        let status = unsafe {
367            ZwProtectVirtualMemory(
368                process_handle,
369                &mut base_address,
370                &mut region_size,
371                protection,
372                &mut old_protection,
373            )
374        };
375
376        unsafe { ZwClose(process_handle) };
377
378        if !status::nt_success(status) {
379            return Err(KmError::ProcessOperationFailed {
380                pid: self.id(),
381                reason: "ZwProtectVirtualMemory failed",
382            });
383        }
384
385        Ok(old_protection)
386    }
387
388    /// get module base address by name
389    pub fn get_module_base(&mut self, module_name: &[u16]) -> KmResult<u64> {
390        self.attach()?;
391
392        // read PEB
393        let peb = self.eprocess.peb();
394        if peb.is_null() {
395            self.detach();
396            return Err(KmError::ProcessOperationFailed {
397                pid: self.id(),
398                reason: "PEB is null",
399            });
400        }
401
402        // read PEB_LDR_DATA pointer (offset 0x18 on x64)
403        let ldr_offset = if cfg!(target_arch = "x86_64") { 0x18 } else { 0x0C };
404        let ldr_ptr = unsafe {
405            *(peb.cast::<u8>().add(ldr_offset) as *const *const c_void)
406        };
407
408        if ldr_ptr.is_null() {
409            self.detach();
410            return Err(KmError::ProcessOperationFailed {
411                pid: self.id(),
412                reason: "PEB_LDR_DATA is null",
413            });
414        }
415
416        // InLoadOrderModuleList offset (0x10 on x64)
417        let list_offset = if cfg!(target_arch = "x86_64") { 0x10 } else { 0x0C };
418        let head = unsafe { ldr_ptr.cast::<u8>().add(list_offset) as *const ListEntry };
419        let mut current = unsafe { (*head).flink };
420
421        while current != head as *mut _ {
422            // get module entry (LDR_DATA_TABLE_ENTRY)
423            // BaseDllName is at offset 0x58 on x64
424            let name_offset = if cfg!(target_arch = "x86_64") { 0x58 } else { 0x2C };
425            let name_ptr = unsafe { current.cast::<u8>().add(name_offset) as *const UnicodeStringKernel };
426
427            let name = unsafe { &*name_ptr };
428            if !name.buffer.is_null() && name.length > 0 {
429                let name_slice = unsafe {
430                    core::slice::from_raw_parts(name.buffer, (name.length / 2) as usize)
431                };
432
433                // case-insensitive comparison
434                if name_slice.len() == module_name.len() {
435                    let matches = name_slice.iter().zip(module_name.iter())
436                        .all(|(a, b)| to_lower_u16(*a) == to_lower_u16(*b));
437
438                    if matches {
439                        // DllBase is at offset 0x30 on x64
440                        let base_offset = if cfg!(target_arch = "x86_64") { 0x30 } else { 0x18 };
441                        let base = unsafe {
442                            *(current.cast::<u8>().add(base_offset) as *const u64)
443                        };
444                        self.detach();
445                        return Ok(base);
446                    }
447                }
448            }
449
450            current = unsafe { (*current).flink };
451        }
452
453        self.detach();
454        Err(KmError::ProcessOperationFailed {
455            pid: self.id(),
456            reason: "module not found",
457        })
458    }
459
460    /// open handle to this process
461    fn open_handle(&self, access: u32) -> KmResult<*mut c_void> {
462        let mut handle: *mut c_void = core::ptr::null_mut();
463
464        // SAFETY: valid EPROCESS
465        let status = unsafe {
466            ObOpenObjectByPointer(
467                self.eprocess.as_raw(),
468                0x200, // OBJ_KERNEL_HANDLE
469                core::ptr::null_mut(),
470                access,
471                core::ptr::null_mut(), // PsProcessType
472                AccessMode::KernelMode as u8,
473                &mut handle,
474            )
475        };
476
477        if !status::nt_success(status) {
478            return Err(KmError::ProcessOperationFailed {
479                pid: self.id(),
480                reason: "ObOpenObjectByPointer failed",
481            });
482        }
483
484        Ok(handle)
485    }
486}
487
488impl Drop for KmProcess {
489    fn drop(&mut self) {
490        self.detach();
491    }
492}
493
494/// list entry structure
495#[repr(C)]
496struct ListEntry {
497    flink: *mut ListEntry,
498    blink: *mut ListEntry,
499}
500
501/// unicode string for kernel
502#[repr(C)]
503struct UnicodeStringKernel {
504    length: u16,
505    maximum_length: u16,
506    buffer: *const u16,
507}
508
509// process functions
510extern "system" {
511    fn PsLookupProcessByProcessId(ProcessId: *mut c_void, Process: *mut *mut c_void) -> NtStatus;
512    fn PsGetProcessId(Process: *mut c_void) -> usize;
513    fn PsGetProcessCr3(Process: *mut c_void) -> u64;
514    fn PsGetProcessImageFileName(Process: *mut c_void) -> *const u8;
515    fn PsGetProcessExitStatus(Process: *mut c_void) -> NtStatus;
516    fn PsGetProcessPeb(Process: *mut c_void) -> *mut c_void;
517    fn PsGetProcessWow64Process(Process: *mut c_void) -> *mut c_void;
518    fn PsGetCurrentProcess() -> *mut c_void;
519
520    fn ObDereferenceObject(Object: *mut c_void);
521    fn ObOpenObjectByPointer(
522        Object: *mut c_void,
523        HandleAttributes: u32,
524        PassedAccessState: *mut c_void,
525        DesiredAccess: u32,
526        ObjectType: *mut c_void,
527        AccessMode: u8,
528        Handle: *mut *mut c_void,
529    ) -> NtStatus;
530
531    fn KeStackAttachProcess(Process: *mut c_void, ApcState: *mut c_void);
532    fn KeUnstackDetachProcess(ApcState: *mut c_void);
533
534    fn MmCopyVirtualMemory(
535        SourceProcess: *mut c_void,
536        SourceAddress: *const c_void,
537        TargetProcess: *mut c_void,
538        TargetAddress: *mut c_void,
539        BufferSize: usize,
540        PreviousMode: u8,
541        ReturnSize: *mut usize,
542    ) -> NtStatus;
543
544    fn ZwAllocateVirtualMemory(
545        ProcessHandle: *mut c_void,
546        BaseAddress: *mut *mut c_void,
547        ZeroBits: usize,
548        RegionSize: *mut usize,
549        AllocationType: u32,
550        Protect: u32,
551    ) -> NtStatus;
552
553    fn ZwFreeVirtualMemory(
554        ProcessHandle: *mut c_void,
555        BaseAddress: *mut *mut c_void,
556        RegionSize: *mut usize,
557        FreeType: u32,
558    ) -> NtStatus;
559
560    fn ZwProtectVirtualMemory(
561        ProcessHandle: *mut c_void,
562        BaseAddress: *mut *mut c_void,
563        RegionSize: *mut usize,
564        NewProtect: u32,
565        OldProtect: *mut u32,
566    ) -> NtStatus;
567
568    fn ZwClose(Handle: *mut c_void) -> NtStatus;
569}
570
571/// convert u16 to lowercase (ASCII only)
572#[inline]
573fn to_lower_u16(c: u16) -> u16 {
574    if c >= b'A' as u16 && c <= b'Z' as u16 {
575        c + 32
576    } else {
577        c
578    }
579}