Skip to main content

vmi_os_windows/arch/
amd64.rs

1use vmi_arch_amd64::{
2    Amd64, Cr3, ExceptionVector, Interrupt, InterruptType, PageTableEntry, PageTableLevel,
3    Registers,
4};
5use vmi_core::{
6    Architecture as _, Pa, Va, VmiCore, VmiError, VmiSession, VmiState, driver::VmiRead, os::NoOS,
7};
8
9use super::ArchAdapter;
10use crate::{WindowsImage, WindowsKernelInformation, WindowsOs};
11
12/// An extension trait for [`PageTableEntry`] that provides access to
13/// Windows-specific fields.
14pub trait WindowsPageTableEntry {
15    /// Returns whether the page is a prototype.
16    fn windows_prototype(self) -> bool;
17
18    /// Returns whether the page is in transition.
19    fn windows_transition(self) -> bool;
20}
21
22impl WindowsPageTableEntry for PageTableEntry {
23    fn windows_prototype(self) -> bool {
24        (self.0 >> 10) & 1 != 0
25    }
26
27    fn windows_transition(self) -> bool {
28        (self.0 >> 11) & 1 != 0
29    }
30}
31
32/// An extension trait for [`ExceptionVector`] that provides access to
33/// Windows-specific exception vectors.
34pub trait WindowsExceptionVector {
35    /// Asynchronous Procedure Call (APC) interrupt.
36    const Apc: Self;
37
38    /// Deferred Procedure Call (DPC) interrupt.
39    const Dpc: Self;
40}
41
42impl WindowsExceptionVector for ExceptionVector {
43    const Apc: Self = Self(31); // 0x1F
44    const Dpc: Self = Self(47); // 0x2F
45}
46
47/// An extension trait for [`Interrupt`] that provides access to
48/// Windows-specific interrupts.
49pub trait WindowsInterrupt {
50    /// Creates a new APC interrupt.
51    fn apc() -> Self;
52
53    /// Creates a new DPC interrupt.
54    fn dpc() -> Self;
55}
56
57impl WindowsInterrupt for Interrupt {
58    fn apc() -> Self {
59        Self {
60            vector: ExceptionVector::Apc,
61            typ: InterruptType::ExternalInterrupt,
62            error_code: 0xffff_ffff,
63            instruction_length: 0,
64            extra: 0,
65        }
66    }
67
68    fn dpc() -> Self {
69        Self {
70            vector: ExceptionVector::Dpc,
71            typ: InterruptType::ExternalInterrupt,
72            error_code: 0xffff_ffff,
73            instruction_length: 0,
74            extra: 0,
75        }
76    }
77}
78
79impl<Driver> ArchAdapter<Driver> for Amd64
80where
81    Driver: VmiRead<Architecture = Self>,
82{
83    fn find_kernel(
84        vmi: &VmiCore<Driver>,
85        registers: &Registers,
86    ) -> Result<Option<WindowsKernelInformation>, VmiError> {
87        /// Maximum backward search distance for the kernel image base.
88        const MAX_BACKWARD_SEARCH: u64 = 32 * 1024 * 1024;
89
90        let session = VmiSession::new(vmi, const { &NoOS(std::marker::PhantomData) });
91        let vmi = session.with_registers(registers);
92
93        // Align MSR_LSTAR to 4KB.
94        let lstar = registers.msr_lstar & Amd64::PAGE_MASK;
95
96        let mut data = [0u8; Amd64::PAGE_SIZE as usize];
97
98        for base_address in (lstar - MAX_BACKWARD_SEARCH..=lstar)
99            .rev()
100            .step_by(Amd64::PAGE_SIZE as usize)
101        {
102            let base_address = Va(base_address);
103
104            //
105            // Read next page.
106            // Ignore page faults.
107            //
108
109            match vmi.read(base_address, &mut data) {
110                Ok(()) => {}
111                Err(VmiError::Translation(_)) => continue,
112                Err(err) => return Err(err),
113            }
114
115            if &data[..2] != b"MZ" {
116                continue;
117            }
118
119            tracing::trace!(%base_address, "found MZ");
120
121            let image = WindowsImage::new_without_os(vmi, base_address);
122            match super::image_codeview(&image) {
123                Ok(Some(result)) => {
124                    let name = &result.codeview.name;
125
126                    if name.starts_with("nt") {
127                        tracing::debug!(%base_address, "found kernel image");
128                        return Ok(Some(result));
129                    }
130
131                    tracing::trace!(%name, "found non-kernel image");
132                }
133                Ok(None) => tracing::trace!("no codeview found"),
134                Err(err) => tracing::trace!(%err, "error parsing PE"),
135            };
136        }
137
138        tracing::trace!(
139            "no codeview found within {} MB",
140            MAX_BACKWARD_SEARCH / 1024 / 1024
141        );
142
143        Ok(None)
144    }
145
146    fn syscall_argument(vmi: VmiState<WindowsOs<Driver>>, index: u64) -> Result<u64, VmiError> {
147        let registers = vmi.registers();
148
149        match index {
150            0 => Ok(registers.r10),
151            1 => Ok(registers.rdx),
152            2 => Ok(registers.r8),
153            3 => Ok(registers.r9),
154            _ => {
155                let index = index + 1;
156                let stack = registers.rsp + index * size_of::<u64>() as u64;
157                vmi.read_u64(stack.into())
158            }
159        }
160    }
161
162    fn function_argument(vmi: VmiState<WindowsOs<Driver>>, index: u64) -> Result<u64, VmiError> {
163        let registers = vmi.registers();
164
165        if registers.cs.access.long_mode() {
166            function_argument_x64(vmi, index)
167        }
168        else {
169            function_argument_x86(vmi, index)
170        }
171    }
172
173    fn function_return_value(vmi: VmiState<WindowsOs<Driver>>) -> Result<u64, VmiError> {
174        let registers = vmi.registers();
175
176        Ok(registers.rax)
177    }
178
179    fn kernel_image_base(vmi: VmiState<WindowsOs<Driver>>) -> Result<Va, VmiError> {
180        vmi.underlying_os()
181            .kernel_image_base
182            .get_or_try_init(|| {
183                let KiSystemCall64 = vmi.underlying_os().symbols.KiSystemCall64;
184
185                let registers = vmi.registers();
186                Ok(Va(registers.msr_lstar - KiSystemCall64))
187            })
188            .copied()
189    }
190
191    fn is_page_present_or_transition(
192        vmi: VmiState<WindowsOs<Driver>>,
193        address: Va,
194    ) -> Result<bool, VmiError> {
195        let registers = vmi.registers();
196
197        let translation = Amd64::translation(vmi.core(), address, registers.cr3.into());
198        if let Some(entry) = translation.entries().last()
199            && entry.level == PageTableLevel::Pt
200        {
201            if entry.entry.present() {
202                // The address is valid if the page is present.
203                return Ok(true);
204            }
205            else if entry.entry.windows_transition() && !entry.entry.windows_prototype() {
206                // The Transition bit being 1 indicates that the page is in a transitional
207                // state. This means the page is not currently in the process's working
208                // set, but it's still resident in physical memory.
209                //
210                // If the process tries to access this page, it can be quickly brought
211                // back into the working set without needing to read from disk. This is
212                // sometimes called a "soft page fault" or "transition fault".
213                //
214                // This state is part of Windows' memory management optimization.
215                // It allows the system to keep pages in memory that might be needed
216                // again soon, without consuming the working set quota of processes.
217                return Ok(true);
218            }
219        }
220
221        Ok(false)
222    }
223
224    fn current_kpcr(vmi: VmiState<WindowsOs<Driver>>) -> Va {
225        let registers = vmi.registers();
226
227        if registers.cs.selector.request_privilege_level() != 0
228            || (registers.gs.base & (1 << 47)) == 0
229        {
230            registers.shadow_gs.into()
231        }
232        else {
233            registers.gs.base.into()
234        }
235    }
236
237    fn dtb_to_root(value: u64) -> Pa {
238        Pa::from(Cr3(value))
239    }
240}
241
242/*
243
244fn find_kernel_slow<Driver>(
245    vmi: VmiState<NoOS<Driver>>,
246) -> Result<Option<WindowsKernelInformation>, VmiError>
247where
248    Driver: VmiRead<Architecture = Amd64>,
249{
250    tracing::trace!("performing slow kernel search");
251
252    let info = vmi.info()?;
253
254    for gfn in 0..info.max_gfn.0 {
255        let gfn = Gfn(gfn);
256
257        let data = match vmi.read_page(gfn) {
258            Ok(page) => page,
259            Err(_) => continue,
260        };
261
262        if &data[..2] != b"MZ" {
263            continue;
264        }
265
266        let image = WindowsImage::new_from_pa(vmi, Amd64::pa_from_gfn(gfn));
267        let result = match image_codeview(&image) {
268            Ok(Some(result)) => result,
269            _ => continue,
270        };
271
272        if !result.codeview.path.starts_with("nt") {
273            continue;
274        }
275
276        return Ok(Some(result));
277    }
278
279    Ok(None)
280}
281
282*/
283
284fn function_argument_x86<Driver>(
285    vmi: VmiState<WindowsOs<Driver>>,
286    index: u64,
287) -> Result<u64, VmiError>
288where
289    Driver: VmiRead<Architecture = Amd64>,
290{
291    let registers = vmi.registers();
292
293    let index = index + 1;
294    let stack = registers.rsp + index * size_of::<u32>() as u64;
295    Ok(vmi.read_u32(stack.into())? as u64)
296}
297
298fn function_argument_x64<Driver>(
299    vmi: VmiState<WindowsOs<Driver>>,
300    index: u64,
301) -> Result<u64, VmiError>
302where
303    Driver: VmiRead<Architecture = Amd64>,
304{
305    let registers = vmi.registers();
306
307    match index {
308        0 => Ok(registers.rcx),
309        1 => Ok(registers.rdx),
310        2 => Ok(registers.r8),
311        3 => Ok(registers.r9),
312        _ => {
313            let index = index + 1;
314            let stack = registers.rsp + index * size_of::<u64>() as u64;
315            vmi.read_u64(stack.into())
316        }
317    }
318}