vmi_os_windows/comps/object/
process.rs

1use vmi_arch_amd64::Cr3;
2use vmi_core::{
3    Architecture, Pa, Va, VmiDriver, VmiError, VmiState, VmiVa,
4    os::{ProcessId, ProcessObject, ThreadObject, VmiOsImageArchitecture, VmiOsProcess},
5};
6
7use super::{
8    super::{
9        WindowsHandleTable, WindowsPeb, WindowsRegion, WindowsSession, macros::impl_offsets,
10        peb::WindowsWow64Kind,
11    },
12    WindowsObject, WindowsThread,
13};
14use crate::{
15    ArchAdapter, ListEntryIterator, OffsetsExt, TreeNodeIterator, WindowsOs,
16    offsets::{v1, v2},
17};
18
19/// A Windows process.
20///
21/// A process in Windows is represented by the `_EPROCESS` structure,
22/// which contains metadata about its execution state, memory layout,
23/// and handles.
24///
25/// # Implementation Details
26///
27/// Corresponds to `_EPROCESS`.
28pub struct WindowsProcess<'a, Driver>
29where
30    Driver: VmiDriver,
31    Driver::Architecture: Architecture + ArchAdapter<Driver>,
32{
33    /// The VMI state.
34    vmi: VmiState<'a, Driver, WindowsOs<Driver>>,
35
36    /// The virtual address of the `_EPROCESS` structure.
37    va: Va,
38}
39
40impl<'a, Driver> From<WindowsProcess<'a, Driver>> for WindowsObject<'a, Driver>
41where
42    Driver: VmiDriver,
43    Driver::Architecture: Architecture + ArchAdapter<Driver>,
44{
45    fn from(value: WindowsProcess<'a, Driver>) -> Self {
46        Self::new(value.vmi, value.va)
47    }
48}
49
50impl<Driver> VmiVa for WindowsProcess<'_, Driver>
51where
52    Driver: VmiDriver,
53    Driver::Architecture: Architecture + ArchAdapter<Driver>,
54{
55    fn va(&self) -> Va {
56        self.va
57    }
58}
59
60impl<'a, Driver> WindowsProcess<'a, Driver>
61where
62    Driver: VmiDriver,
63    Driver::Architecture: Architecture + ArchAdapter<Driver>,
64{
65    impl_offsets!();
66
67    /// Creates a new Windows process.
68    pub fn new(vmi: VmiState<'a, Driver, WindowsOs<Driver>>, process: ProcessObject) -> Self {
69        Self { vmi, va: process.0 }
70    }
71
72    /// Returns the process environment block (PEB).
73    ///
74    /// # Implementation Details
75    ///
76    /// The function first reads the `_EPROCESS.WoW64Process` field to determine
77    /// if the process is a 32-bit process. If the field is `NULL`, the process
78    /// is 64-bit. Otherwise, the function reads the `_EWOW64PROCESS.Peb` field
79    /// to get the 32-bit PEB.
80    pub fn peb(&self) -> Result<Option<WindowsPeb<'a, Driver>>, VmiError> {
81        let offsets = self.offsets();
82        let EPROCESS = &offsets._EPROCESS;
83
84        let root = self.translation_root()?;
85
86        let wow64 = self
87            .vmi
88            .read_va_native(self.va + EPROCESS.WoW64Process.offset())?;
89
90        if wow64.is_null() {
91            let peb64 = self.vmi.read_va_native(self.va + EPROCESS.Peb.offset())?;
92
93            if peb64.is_null() {
94                return Ok(None);
95            }
96
97            Ok(Some(WindowsPeb::new(
98                self.vmi,
99                peb64,
100                root,
101                WindowsWow64Kind::Native,
102            )))
103        }
104        else {
105            let peb32 = match &offsets.ext {
106                Some(OffsetsExt::V1(_)) => wow64,
107                Some(OffsetsExt::V2(v2)) => self
108                    .vmi
109                    .read_va_native(wow64 + v2._EWOW64PROCESS.Peb.offset())?,
110                None => panic!("OffsetsExt not set"),
111            };
112
113            if peb32.is_null() {
114                return Ok(None);
115            }
116
117            Ok(Some(WindowsPeb::new(
118                self.vmi,
119                peb32,
120                root,
121                WindowsWow64Kind::X86,
122            )))
123        }
124    }
125
126    /// Returns the session of the process.
127    pub fn session(&self) -> Result<Option<WindowsSession<'a, Driver>>, VmiError> {
128        let offsets = self.offsets();
129        let EPROCESS = &offsets._EPROCESS;
130
131        let session = self
132            .vmi
133            .read_va_native(self.va + EPROCESS.Session.offset())?;
134
135        if session.is_null() {
136            return Ok(None);
137        }
138
139        Ok(Some(WindowsSession::new(self.vmi, session)))
140    }
141
142    /// Returns the handle table of the process.
143    ///
144    /// # Implementation Details
145    ///
146    /// Corresponds to `_EPROCESS.ObjectTable`.
147    pub fn handle_table(&self) -> Result<Option<WindowsHandleTable<'a, Driver>>, VmiError> {
148        let offsets = self.offsets();
149        let EPROCESS = &offsets._EPROCESS;
150
151        let handle_table = self
152            .vmi
153            .read_va_native(self.va + EPROCESS.ObjectTable.offset())?;
154
155        if handle_table.is_null() {
156            return Ok(None);
157        }
158
159        Ok(Some(WindowsHandleTable::new(self.vmi, handle_table)))
160    }
161
162    /// Returns the root of the virtual address descriptor (VAD) tree.
163    ///
164    /// # Implementation Details
165    ///
166    /// Corresponds to `_EPROCESS.VadRoot->BalancedRoot` for Windows 7 and
167    /// `_EPROCESS.VadRoot->Root` for Windows 8.1 and later.
168    pub fn vad_root(&self) -> Result<Option<WindowsRegion<'a, Driver>>, VmiError> {
169        let node = match &self.offsets().ext() {
170            Some(OffsetsExt::V1(offsets)) => self.vad_root_v1(offsets)?,
171            Some(OffsetsExt::V2(offsets)) => self.vad_root_v2(offsets)?,
172            None => panic!("OffsetsExt not set"),
173        };
174
175        if node.is_null() {
176            return Ok(None);
177        }
178
179        Ok(Some(WindowsRegion::new(self.vmi, node)))
180    }
181
182    fn vad_root_v1(&self, offsets_ext: &v1::Offsets) -> Result<Va, VmiError> {
183        let offsets = self.offsets();
184        let EPROCESS = &offsets._EPROCESS;
185        let MM_AVL_TABLE = &offsets_ext._MM_AVL_TABLE;
186
187        // The `_MM_AVL_TABLE::BalancedRoot` field is of `_MMADDRESS_NODE` type,
188        // which represents the root.
189        let vad_root = self.va + EPROCESS.VadRoot.offset() + MM_AVL_TABLE.BalancedRoot.offset();
190
191        Ok(vad_root)
192    }
193
194    fn vad_root_v2(&self, offsets_ext: &v2::Offsets) -> Result<Va, VmiError> {
195        let offsets = self.offsets();
196        let EPROCESS = &offsets._EPROCESS;
197        let RTL_AVL_TREE = &offsets_ext._RTL_AVL_TREE;
198
199        // The `RTL_AVL_TREE::Root` field is of pointer type (`_RTL_BALANCED_NODE*`),
200        // thus we need to dereference it to get the actual node.
201        let vad_root = self
202            .vmi
203            .read_va_native(self.va + EPROCESS.VadRoot.offset() + RTL_AVL_TREE.Root.offset())?;
204
205        Ok(vad_root)
206    }
207
208    /// Returns the VAD hint node.
209    ///
210    /// The VAD hint is an optimization used by Windows to speed up VAD lookups.
211    /// This method returns the address of the hint node in the VAD tree.
212    ///
213    /// # Implementation Details
214    ///
215    /// Corresponds to `_EPROCESS.VadRoot->NodeHint` for Windows 7 and
216    /// `_EPROCESS.VadRoot->Hint` for Windows 8.1 and later.
217    pub fn vad_hint(&self) -> Result<Option<WindowsRegion<'a, Driver>>, VmiError> {
218        let node = match &self.offsets().ext() {
219            Some(OffsetsExt::V1(offsets)) => self.vad_hint_v1(offsets)?,
220            Some(OffsetsExt::V2(offsets)) => self.vad_hint_v2(offsets)?,
221            None => panic!("OffsetsExt not set"),
222        };
223
224        if node.is_null() {
225            return Ok(None);
226        }
227
228        Ok(Some(WindowsRegion::new(self.vmi, node)))
229    }
230
231    fn vad_hint_v1(&self, offsets_ext: &v1::Offsets) -> Result<Va, VmiError> {
232        let offsets = self.offsets();
233        let EPROCESS = &offsets._EPROCESS;
234        let MM_AVL_TABLE = &offsets_ext._MM_AVL_TABLE;
235
236        self.vmi
237            .read_va_native(self.va + EPROCESS.VadRoot.offset() + MM_AVL_TABLE.NodeHint.offset())
238    }
239
240    fn vad_hint_v2(&self, _offsets_ext: &v2::Offsets) -> Result<Va, VmiError> {
241        let offsets = self.offsets();
242        let EPROCESS = &offsets._EPROCESS;
243
244        let VadHint = EPROCESS
245            .VadHint
246            .expect("VadHint is not present in common offsets");
247
248        self.vmi.read_va_native(self.va + VadHint.offset())
249    }
250}
251
252impl<'a, Driver> VmiOsProcess<'a, Driver> for WindowsProcess<'a, Driver>
253where
254    Driver: VmiDriver,
255    Driver::Architecture: Architecture + ArchAdapter<Driver>,
256{
257    type Os = WindowsOs<Driver>;
258
259    /// Returns the process ID.
260    ///
261    /// # Implementation Details
262    ///
263    /// Corresponds to `_EPROCESS.UniqueProcessId`.
264    fn id(&self) -> Result<ProcessId, VmiError> {
265        let offsets = self.offsets();
266        let EPROCESS = &offsets._EPROCESS;
267
268        let result = self
269            .vmi
270            .read_u32(self.va + EPROCESS.UniqueProcessId.offset())?;
271
272        Ok(ProcessId(result))
273    }
274
275    /// Returns the process object.
276    fn object(&self) -> Result<ProcessObject, VmiError> {
277        Ok(ProcessObject(self.va))
278    }
279
280    /// Returns the name of the process.
281    ///
282    /// # Implementation Details
283    ///
284    /// Corresponds to `_EPROCESS.ImageFileName`.
285    fn name(&self) -> Result<String, VmiError> {
286        let offsets = self.offsets();
287        let EPROCESS = &offsets._EPROCESS;
288
289        self.vmi
290            .read_string(self.va + EPROCESS.ImageFileName.offset())
291    }
292
293    /// Returns the parent process ID.
294    ///
295    /// # Implementation Details
296    ///
297    /// Corresponds to `_EPROCESS.InheritedFromUniqueProcessId`.
298    fn parent_id(&self) -> Result<ProcessId, VmiError> {
299        let offsets = self.offsets();
300        let EPROCESS = &offsets._EPROCESS;
301
302        let result = self
303            .vmi
304            .read_u32(self.va + EPROCESS.InheritedFromUniqueProcessId.offset())?;
305
306        Ok(ProcessId(result))
307    }
308
309    /// Returns the architecture of the process.
310    ///
311    /// # Implementation Details
312    ///
313    /// The function reads the `_EPROCESS.WoW64Process` field to determine if the
314    /// process is a 32-bit process. If the field is `NULL`, the process is 64-bit.
315    /// Otherwise, the process is 32-bit.
316    fn architecture(&self) -> Result<VmiOsImageArchitecture, VmiError> {
317        let offsets = self.offsets();
318        let EPROCESS = &offsets._EPROCESS;
319
320        let wow64process = self
321            .vmi
322            .read_va_native(self.va + EPROCESS.WoW64Process.offset())?;
323
324        if wow64process.is_null() {
325            Ok(VmiOsImageArchitecture::Amd64)
326        }
327        else {
328            Ok(VmiOsImageArchitecture::X86)
329        }
330    }
331
332    /// Returns the process's page table translation root.
333    ///
334    /// # Implementation Details
335    ///
336    /// Corresponds to `_KPROCESS.DirectoryTableBase`.
337    fn translation_root(&self) -> Result<Pa, VmiError> {
338        let offsets = self.offsets();
339        let KPROCESS = &offsets._KPROCESS;
340
341        // let current_process = self.vmi.os().current_process()?.object()?;
342        //
343        // if self.va == current_process.0 {
344        //     return Ok(self.vmi.translation_root(self.va));
345        // }
346
347        let root = Cr3(self
348            .vmi
349            .read_va_native(self.va + KPROCESS.DirectoryTableBase.offset())?
350            .0);
351
352        Ok(root.into())
353    }
354
355    /// Returns the user-mode page table translation root.
356    ///
357    /// If KPTI is disabled, this function will return the same value as
358    /// [`translation_root`](Self::translation_root).
359    ///
360    /// # Implementation Details
361    ///
362    /// Corresponds to `_KPROCESS.UserDirectoryTableBase`.
363    fn user_translation_root(&self) -> Result<Pa, VmiError> {
364        let offsets = self.offsets();
365        let KPROCESS = &offsets._KPROCESS;
366        let UserDirectoryTableBase = match &KPROCESS.UserDirectoryTableBase {
367            Some(UserDirectoryTableBase) => UserDirectoryTableBase,
368            None => return self.translation_root(),
369        };
370
371        let root = Cr3(self
372            .vmi
373            .read_va_native(self.va + UserDirectoryTableBase.offset())?
374            .0);
375
376        if root.0 < Driver::Architecture::PAGE_SIZE {
377            return self.translation_root();
378        }
379
380        Ok(root.into())
381    }
382
383    /// Returns the base address of the process image.
384    ///
385    /// # Implementation Details
386    ///
387    /// Corresponds to `_EPROCESS.SectionBaseAddress`.
388    fn image_base(&self) -> Result<Va, VmiError> {
389        let offsets = self.offsets();
390        let EPROCESS = &offsets._EPROCESS;
391
392        self.vmi
393            .read_va_native(self.va + EPROCESS.SectionBaseAddress.offset())
394    }
395
396    /// Returns an iterator over the process's memory regions (VADs).
397    ///
398    /// # Implementation Details
399    ///
400    /// The function iterates over the VAD tree of the process.
401    fn regions(
402        &self,
403    ) -> Result<impl Iterator<Item = Result<WindowsRegion<'a, Driver>, VmiError>>, VmiError> {
404        let iterator = match self.vad_root()? {
405            Some(vad_root) => TreeNodeIterator::new(self.vmi, vad_root.va()),
406            None => TreeNodeIterator::empty(self.vmi),
407        };
408
409        Ok(iterator.map(move |result| result.map(|vad| WindowsRegion::new(self.vmi, vad))))
410    }
411
412    /// Finds the memory region (VAD) containing the given address.
413    ///
414    /// This method efficiently searches the VAD tree to find the VAD node that
415    /// corresponds to the given virtual address within the process's address
416    /// space.
417    ///
418    /// Returns the matching VAD if found, or `None` if the address is not
419    /// within any VAD.
420    ///
421    /// # Implementation Details
422    ///
423    /// The functionality is similar to the Windows kernel's internal
424    /// `MiLocateAddress()` function.
425    fn find_region(&self, address: Va) -> Result<Option<WindowsRegion<'a, Driver>>, VmiError> {
426        let vad = match self.vad_hint()? {
427            Some(vad) => vad,
428            None => return Ok(None),
429        };
430
431        let vpn = address.0 >> 12;
432
433        if vpn >= vad.starting_vpn()? && vpn <= vad.ending_vpn()? {
434            return Ok(Some(vad));
435        }
436
437        let mut next = self.vad_root()?;
438        while let Some(vad) = next {
439            if vpn < vad.starting_vpn()? {
440                next = vad.left_child()?;
441            }
442            else if vpn > vad.ending_vpn()? {
443                next = vad.right_child()?;
444            }
445            else {
446                return Ok(Some(vad));
447            }
448        }
449
450        Ok(None)
451    }
452
453    /// Returns an iterator over the threads in the process.
454    ///
455    /// # Notes
456    ///
457    /// Both `_EPROCESS` and `_KPROCESS` structures contain the same list
458    /// of threads.
459    ///
460    /// # Implementation Details
461    ///
462    /// Corresponds to `_EPROCESS.ThreadListHead`.
463    fn threads(
464        &self,
465    ) -> Result<
466        impl Iterator<Item = Result<<Self::Os as vmi_core::VmiOs<Driver>>::Thread<'a>, VmiError>>,
467        VmiError,
468    > {
469        let offsets = self.offsets();
470        let EPROCESS = &offsets._EPROCESS;
471        let ETHREAD = &offsets._ETHREAD;
472
473        Ok(ListEntryIterator::new(
474            self.vmi,
475            self.va + EPROCESS.ThreadListHead.offset(),
476            ETHREAD.ThreadListEntry.offset(),
477        )
478        .map(move |result| result.map(|entry| WindowsThread::new(self.vmi, ThreadObject(entry)))))
479    }
480
481    /// Checks whether the given virtual address is valid in the process.
482    ///
483    /// This method checks if page-faulting on the address would result in
484    /// a successful access.
485    fn is_valid_address(&self, address: Va) -> Result<Option<bool>, VmiError> {
486        //
487        // So, the logic is roughly as follows:
488        // - Translate the address and try to find the page table entry.
489        //   - If the page table entry is found:
490        //     - If the page is present, the address is valid.
491        //     - If the page is in transition AND not a prototype, the address is valid.
492        // - Find the VAD for the address.
493        //   - If the VAD is not found, the address is invalid.
494        // - If the VadType is VadImageMap, the address is valid.
495        //   - If the VadType is not VadImageMap, we don't care (VadAwe, physical
496        //     memory, ...).
497        // - If the PrivateMemory bit is not set, the address is invalid.
498        // - If the MemCommit bit is not set, the address is invalid.
499        //
500        // References:
501        // - MmAccessFault
502        // - MiDispatchFault
503        // - MiQueryAddressState
504        // - MiCheckVirtualAddress
505        //
506
507        if Driver::Architecture::is_page_present_or_transition(self.vmi, address)? {
508            return Ok(Some(true));
509        }
510
511        let vad = match self.find_region(address)? {
512            Some(vad) => vad,
513            None => return Ok(Some(false)),
514        };
515
516        const MM_ZERO_ACCESS: u8 = 0; // this value is not used.
517        const MM_DECOMMIT: u8 = 0x10; // NO_ACCESS, Guard page
518        const MM_NOACCESS: u8 = 0x18; // NO_ACCESS, Guard_page, nocache.
519
520        const VadImageMap: u8 = 2;
521
522        if matches!(
523            vad.vad_protection()?,
524            MM_ZERO_ACCESS | MM_DECOMMIT | MM_NOACCESS
525        ) {
526            return Ok(Some(false));
527        }
528
529        Ok(Some(
530            // Private memory must be committed.
531            (vad.private_memory()? && vad.mem_commit()?) ||
532
533            // Non-private memory must be mapped from an image.
534            // Note that this isn't actually correct, because
535            // some parts of the image might not be committed,
536            // or they can have different protection than the VAD.
537            //
538            // However, figuring out the correct protection would
539            // be quite complex, so we just assume that the image
540            // is always committed and has the same protection as
541            // the VAD.
542            (!vad.private_memory()? && vad.vad_type()? == VadImageMap),
543        ))
544    }
545}