Skip to main content

vmi_os_windows/comps/object/
directory.rs

1use vmi_core::{Registers as _, Va, VmiError, VmiState, VmiVa, driver::VmiRead};
2
3use super::{FromWindowsObject, WindowsObject, WindowsObjectTypeKind};
4use crate::{ArchAdapter, DirectoryObjectIterator, WindowsOs, offset};
5
6/// A Windows directory object.
7///
8/// A directory object is a kernel-managed container that stores named objects
9/// such as events, mutexes, symbolic links, and device objects.
10///
11/// # Implementation Details
12///
13/// Corresponds to `_OBJECT_DIRECTORY`.
14pub struct WindowsDirectoryObject<'a, Driver>
15where
16    Driver: VmiRead,
17    Driver::Architecture: ArchAdapter<Driver>,
18{
19    /// The VMI state.
20    vmi: VmiState<'a, WindowsOs<Driver>>,
21
22    /// Address of the `_OBJECT_DIRECTORY` structure.
23    va: Va,
24}
25
26impl<'a, Driver> From<WindowsDirectoryObject<'a, Driver>> for WindowsObject<'a, Driver>
27where
28    Driver: VmiRead,
29    Driver::Architecture: ArchAdapter<Driver>,
30{
31    fn from(value: WindowsDirectoryObject<'a, Driver>) -> Self {
32        Self::new(value.vmi, value.va)
33    }
34}
35
36impl<'a, Driver> FromWindowsObject<'a, Driver> for WindowsDirectoryObject<'a, Driver>
37where
38    Driver: VmiRead,
39    Driver::Architecture: ArchAdapter<Driver>,
40{
41    fn from_object(object: WindowsObject<'a, Driver>) -> Result<Option<Self>, VmiError> {
42        match object.type_kind()? {
43            Some(WindowsObjectTypeKind::Directory) => Ok(Some(Self::new(object.vmi, object.va))),
44            _ => Ok(None),
45        }
46    }
47}
48
49impl<Driver> VmiVa for WindowsDirectoryObject<'_, Driver>
50where
51    Driver: VmiRead,
52    Driver::Architecture: ArchAdapter<Driver>,
53{
54    fn va(&self) -> Va {
55        self.va
56    }
57}
58
59impl<'a, Driver> WindowsDirectoryObject<'a, Driver>
60where
61    Driver: VmiRead,
62    Driver::Architecture: ArchAdapter<Driver>,
63{
64    /// Creates a new Windows directory object.
65    pub fn new(vmi: VmiState<'a, WindowsOs<Driver>>, va: Va) -> Self {
66        Self { vmi, va }
67    }
68
69    /// Iterates over the objects in the directory.
70    pub fn iter(
71        &self,
72    ) -> Result<
73        impl Iterator<Item = Result<WindowsObject<'a, Driver>, VmiError>> + use<'a, Driver>,
74        VmiError,
75    > {
76        Ok(DirectoryObjectIterator::new(self.vmi, self.va))
77    }
78
79    /// Resolves a relative path to a descendant object.
80    ///
81    /// Splits `path` on `\\` and descends one component at a time. Empty
82    /// segments are ignored. Name comparison is ASCII-case-insensitive.
83    /// Each intermediate component must resolve to a `Directory` object.
84    ///
85    /// Returns `Ok(None)` if a component does not exist or an intermediate
86    /// is some other object type. The final component may be any type. An
87    /// empty path returns this directory.
88    ///
89    /// Does not follow `SymbolicLink` objects.
90    pub fn lookup(
91        &self,
92        path: impl AsRef<str>,
93    ) -> Result<Option<WindowsObject<'a, Driver>>, VmiError> {
94        let path = path.as_ref();
95        let mut components = path.split('\\').filter(|component| !component.is_empty());
96
97        let first = match components.next() {
98            Some(first) => first,
99            None => return Ok(Some(WindowsObject::new(self.vmi, self.va))),
100        };
101
102        let mut directory = WindowsDirectoryObject::new(self.vmi, self.va);
103        let mut next = first;
104        for component in components {
105            let object = match directory.child(next)? {
106                Some(object) => object,
107                None => return Ok(None),
108            };
109
110            directory = match WindowsDirectoryObject::from_object(object)? {
111                Some(dir) => dir,
112                None => return Ok(None),
113            };
114
115            next = component;
116        }
117
118        directory.child(next)
119    }
120
121    /// Returns the direct entry with the given name, if any.
122    ///
123    /// `name` is treated as a single component. It is not split on `\\`,
124    /// so `child("Device\\HarddiskVolume4")` will never match a real entry -
125    /// use [`lookup`] for path traversal.
126    ///
127    /// Walks every hash bucket and matches names with ASCII-case-insensitive
128    /// comparison.
129    ///
130    /// Per-bucket read errors do not abort the search. A paged-out bucket
131    /// head or chain-link page would otherwise mask matches in other buckets.
132    /// Errors are skipped.
133    ///
134    /// [`lookup`]: Self::lookup
135    pub fn child(
136        &self,
137        name: impl AsRef<str>,
138    ) -> Result<Option<WindowsObject<'a, Driver>>, VmiError> {
139        const NUMBER_HASH_BUCKETS: u64 = 37;
140
141        let OBJECT_DIRECTORY = offset!(self.vmi, _OBJECT_DIRECTORY);
142
143        let name = name.as_ref();
144        let address_width = self.vmi.registers().address_width() as u64;
145
146        for index in 0..NUMBER_HASH_BUCKETS {
147            let bucket = self.va + OBJECT_DIRECTORY.HashBuckets.offset() + index * address_width;
148
149            match self.lookup_in_bucket(bucket, name) {
150                Ok(Some(object)) => return Ok(Some(object)),
151                Ok(None) => {}
152                Err(err) => {
153                    tracing::trace!(%err, index, "skipping directory bucket after read error");
154                }
155            }
156        }
157
158        Ok(None)
159    }
160
161    /// Walks one hash bucket linked list looking for `name`.
162    fn lookup_in_bucket(
163        &self,
164        bucket: Va,
165        name: &str,
166    ) -> Result<Option<WindowsObject<'a, Driver>>, VmiError> {
167        let OBJECT_DIRECTORY_ENTRY = offset!(self.vmi, _OBJECT_DIRECTORY_ENTRY);
168
169        let mut entry = self.vmi.read_va_native(bucket)?;
170        while !entry.is_null() {
171            let object = self
172                .vmi
173                .read_va_native(entry + OBJECT_DIRECTORY_ENTRY.Object.offset())?;
174
175            let object = WindowsObject::new(self.vmi, object);
176
177            match object.name() {
178                Ok(Some(entry_name)) => {
179                    if entry_name.eq_ignore_ascii_case(name) {
180                        return Ok(Some(object));
181                    }
182                }
183                Ok(None) => {}
184                Err(err) => {
185                    tracing::trace!(%err, "skipping directory entry with unreadable name");
186                }
187            }
188
189            entry = self
190                .vmi
191                .read_va_native(entry + OBJECT_DIRECTORY_ENTRY.ChainLink.offset())?;
192        }
193
194        Ok(None)
195    }
196}