Skip to main content

vmi_os_windows/comps/object/
process.rs

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