Skip to main content

vmi_os_windows/comps/
handle_table.rs

1use once_cell::unsync::OnceCell;
2use vmi_core::{Registers as _, Va, VmiError, VmiState, VmiVa, driver::VmiRead};
3
4use super::WindowsHandleTableEntry;
5use crate::{ArchAdapter, HandleTableEntryIterator, OffsetsExt, WindowsOs, offset};
6
7/// A Windows handle table.
8///
9/// A handle table in Windows tracks handles to kernel objects
10/// for a specific process, allowing access control and management.
11///
12/// # Implementation Details
13///
14/// Corresponds to `_HANDLE_TABLE`.
15pub struct WindowsHandleTable<'a, Driver>
16where
17    Driver: VmiRead,
18    Driver::Architecture: ArchAdapter<Driver>,
19{
20    /// The VMI state.
21    vmi: VmiState<'a, WindowsOs<Driver>>,
22
23    /// Address of the handle table.
24    va: Va,
25
26    /// Corresponds to `_HANDLE_TABLE.TableCode`.
27    table_code: OnceCell<u64>,
28
29    /// Corresponds to `_HANDLE_TABLE.NextHandleNeedingPool`.
30    next_handle_needing_pool: OnceCell<u64>,
31}
32
33impl<Driver> VmiVa for WindowsHandleTable<'_, Driver>
34where
35    Driver: VmiRead,
36    Driver::Architecture: ArchAdapter<Driver>,
37{
38    fn va(&self) -> Va {
39        self.va
40    }
41}
42
43impl<'a, Driver> WindowsHandleTable<'a, Driver>
44where
45    Driver: VmiRead,
46    Driver::Architecture: ArchAdapter<Driver>,
47{
48    /// Creates a new Windows module object.
49    pub fn new(vmi: VmiState<'a, WindowsOs<Driver>>, va: Va) -> Self {
50        Self {
51            vmi,
52            va,
53            table_code: OnceCell::new(),
54            next_handle_needing_pool: OnceCell::new(),
55        }
56    }
57
58    /// Returns the table code of the handle table.
59    ///
60    /// # Notes
61    ///
62    /// This value is cached after the first read.
63    ///
64    /// # Implementation Details
65    ///
66    /// Corresponds to `_HANDLE_TABLE.TableCode`.
67    pub fn table_code(&self) -> Result<u64, VmiError> {
68        self.table_code
69            .get_or_try_init(|| {
70                let HANDLE_TABLE = offset!(self.vmi, _HANDLE_TABLE);
71
72                self.vmi.read_field(self.va, &HANDLE_TABLE.TableCode)
73            })
74            .copied()
75    }
76
77    /// Returns the next handle needing pool.
78    ///
79    /// This value tracks the next handle slot that requires additional pool
80    /// allocation.
81    ///
82    /// # Notes
83    ///
84    /// This value is cached after the first read.
85    ///
86    /// # Implementation Details
87    ///
88    /// Corresponds to `_HANDLE_TABLE.NextHandleNeedingPool`.
89    pub fn next_handle_needing_pool(&self) -> Result<u64, VmiError> {
90        self.next_handle_needing_pool
91            .get_or_try_init(|| {
92                let HANDLE_TABLE = offset!(self.vmi, _HANDLE_TABLE);
93
94                self.vmi
95                    .read_field(self.va, &HANDLE_TABLE.NextHandleNeedingPool)
96            })
97            .copied()
98    }
99
100    /// Iterates over all handle table entries.
101    ///
102    /// Returns an iterator over all handle table entries that have a valid
103    /// object pointer. The iterator yields a tuple containing the handle
104    /// value and the handle table entry.
105    ///
106    /// # Implementation Details
107    ///
108    /// The functionality is similar to the Windows kernel's internal
109    /// `ExpSnapShotHandleTables()` function.
110    pub fn iter(
111        &self,
112    ) -> Result<
113        impl Iterator<Item = Result<(u64, WindowsHandleTableEntry<'a, Driver>), VmiError>>
114        + use<'a, Driver>,
115        VmiError,
116    > {
117        Ok(HandleTableEntryIterator::new(
118            self.vmi,
119            self.table_code()?,
120            self.next_handle_needing_pool()?,
121        ))
122    }
123
124    /// Performs a lookup in the handle table to find the address of a handle
125    /// table entry.
126    ///
127    /// Implements the multi-level handle table lookup algorithm used by
128    /// Windows. Returns the virtual address of the handle table entry.
129    ///
130    /// # Implementation Details
131    ///
132    /// The functionality is similar to the Windows kernel's internal
133    /// `ExpLookupHandleTableEntry()` function.
134    pub fn lookup(
135        &self,
136        handle: u64,
137    ) -> Result<Option<WindowsHandleTableEntry<'a, Driver>>, VmiError> {
138        lookup_handle_entry(
139            self.vmi,
140            self.table_code()?,
141            self.next_handle_needing_pool()?,
142            handle,
143        )
144    }
145}
146
147/// Looks up a handle table entry given snapshots of `_HANDLE_TABLE.TableCode`
148/// and `_HANDLE_TABLE.NextHandleNeedingPool`. Factored out so both
149/// [`WindowsHandleTable::lookup`] and [`HandleTableEntryIterator`] can share
150/// the multi-level lookup algorithm without the iterator having to borrow
151/// from [`WindowsHandleTable`].
152pub(crate) fn lookup_handle_entry<'a, Driver>(
153    vmi: VmiState<'a, WindowsOs<Driver>>,
154    table_code: u64,
155    next_handle_needing_pool: u64,
156    handle: u64,
157) -> Result<Option<WindowsHandleTableEntry<'a, Driver>>, VmiError>
158where
159    Driver: VmiRead,
160    Driver::Architecture: ArchAdapter<Driver>,
161{
162    const PAGE_SIZE: u64 = 4096;
163    const LEVEL_CODE_MASK: u64 = 3;
164    const HANDLE_VALUE_INC: u64 = 4;
165
166    let sizeof_handle_table_entry = match vmi.underlying_os().offsets.ext() {
167        Some(OffsetsExt::V1(offsets)) => offsets._HANDLE_TABLE_ENTRY.len() as u64,
168        Some(OffsetsExt::V2(offsets)) => offsets._HANDLE_TABLE_ENTRY.len() as u64,
169        None => unimplemented!(),
170    };
171
172    let address_width = vmi.registers().address_width() as u64;
173    let lowlevel_count = PAGE_SIZE / sizeof_handle_table_entry; // TABLE_PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY)
174    let midlevel_count = PAGE_SIZE / address_width; // PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY)
175
176    // The 2 least significant bits of a handle are available to the
177    // application and are ignored by the system.
178    let mut index = handle & !0b11;
179
180    // See if this can be a valid handle given the table levels.
181    if index >= next_handle_needing_pool {
182        return Ok(None);
183    }
184
185    let level = table_code & LEVEL_CODE_MASK;
186    let table = Va(table_code - level);
187
188    let entry = match level {
189        0 => table + index * (sizeof_handle_table_entry / HANDLE_VALUE_INC),
190
191        1 => {
192            let table2 = table;
193            let i = index % (lowlevel_count * HANDLE_VALUE_INC);
194
195            index -= i;
196            let j = index / (lowlevel_count * HANDLE_VALUE_INC);
197
198            let table1 = vmi.read_va_native(table2 + j * address_width)?;
199
200            table1 + i * (sizeof_handle_table_entry / HANDLE_VALUE_INC)
201        }
202
203        2 => {
204            let table3 = table;
205            let i = index % (lowlevel_count * HANDLE_VALUE_INC);
206
207            index -= i;
208            let mut k = index / (lowlevel_count * HANDLE_VALUE_INC);
209
210            let j = k % midlevel_count;
211            k -= j;
212            k /= midlevel_count;
213
214            let table2 = vmi.read_va_native(table3 + k * address_width)?;
215            let table1 = vmi.read_va_native(table2 + j * address_width)?;
216
217            table1 + i * (sizeof_handle_table_entry / HANDLE_VALUE_INC)
218        }
219
220        _ => unreachable!(),
221    };
222
223    Ok(Some(WindowsHandleTableEntry::new(vmi, entry)))
224}