Skip to main content

VmiState

Struct VmiState 

Source
pub struct VmiState<'a, Os>
where Os: VmiOs,
{ /* private fields */ }
Expand description

A VMI state.

The state combines access to a VmiSession with Architecture::Registers to provide unified access to VMI operations in the context of a specific virtual machine state.

Implementations§

Source§

impl<'a, Os> VmiState<'a, Os>
where Os: VmiOs,

Source

pub fn new( session: &'a VmiSession<'a, Os>, registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Os>

Available on crate features injector and utils only.

Creates a new VMI state.

Source

pub fn with_registers( &'a self, registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Os>

Available on crate features injector and utils only.

Creates a new VMI state with the specified registers.

Source

pub fn without_os(&self) -> VmiState<'a, NoOS<<Os as VmiOs>::Driver>>

Available on crate features injector and utils only.

Creates a new VMI state without an OS-specific implementation.

Source

pub fn session(&self) -> &VmiSession<'a, Os>

Available on crate features injector and utils only.

Returns the VMI session.

Source

pub fn registers( &self, ) -> &'a <<Os as VmiOs>::Architecture as Architecture>::Registers

Available on crate features injector and utils only.

Returns the CPU registers associated with the current event.

Source

pub fn os(&self) -> VmiOsState<'a, Os>

Available on crate features injector and utils only.

Returns a wrapper providing access to OS-specific operations.

Examples found in repository?
examples/windows-dump.rs (line 103)
102fn enumerate_kernel_modules(vmi: &VmiState<WindowsOs<Driver>>) -> Result<(), VmiError> {
103    for module in vmi.os().modules()? {
104        let module = module?;
105
106        let module_va = module.va();
107        let base_address = module.base_address()?; // `KLDR_DATA_TABLE_ENTRY.DllBase`
108        let size = module.size()?; // `KLDR_DATA_TABLE_ENTRY.SizeOfImage`
109        let name = module.name()?; // `KLDR_DATA_TABLE_ENTRY.BaseDllName`
110        let full_name = match module.full_name() {
111            // `KLDR_DATA_TABLE_ENTRY.FullDllName`
112            Ok(full_name) => full_name,
113            Err(err) => handle_error(err)?,
114        };
115
116        println!("Module @ {module_va}");
117        println!("    Base Address: {base_address}");
118        println!("    Size: {size}");
119        println!("    Name: {name}");
120        println!("    Full Name: {full_name}");
121    }
122
123    Ok(())
124}
125
126// Enumerate entries in a `_OBJECT_DIRECTORY`.
127fn enumerate_directory_object(
128    directory_object: &WindowsDirectoryObject<Driver>,
129    level: usize,
130) -> Result<(), VmiError> {
131    for object in directory_object.iter()? {
132        // Print the indentation.
133        for _ in 0..level {
134            print!("    ");
135        }
136
137        // Retrieve the `_OBJECT_DIRECTORY_ENTRY.Object`.
138        let object = match object {
139            Ok(object) => object,
140            Err(err) => {
141                println!("{}", handle_error(err)?);
142                continue;
143            }
144        };
145
146        let object_va = object.va();
147
148        // Determine the object type.
149        let type_kind = match object.type_kind() {
150            Ok(Some(typ)) => format!("{typ:?}"),
151            Ok(None) => String::from("<unknown>"),
152            Err(err) => handle_error(err)?,
153        };
154
155        print!("{type_kind}: ");
156
157        // Retrieve the full name of the object.
158        let name = match object.full_path() {
159            Ok(Some(name)) => name,
160            Ok(None) => String::from("<unnamed>"),
161            Err(err) => handle_error(err)?,
162        };
163
164        println!("{name} (Object: {object_va})");
165
166        // If the entry is a directory, recursively enumerate it.
167        if let Ok(Some(next)) = object.as_directory() {
168            enumerate_directory_object(&next, level + 1)?;
169        }
170    }
171
172    Ok(())
173}
174
175// Enumerate entries in a `_HANDLE_TABLE`.
176fn enumerate_handle_table(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
177    const OBJ_PROTECT_CLOSE: u32 = 0x00000001;
178    const OBJ_INHERIT: u32 = 0x00000002;
179    const OBJ_AUDIT_OBJECT_CLOSE: u32 = 0x00000004;
180
181    static LABEL_PROTECTED: [&str; 2] = ["", " (Protected)"];
182    static LABEL_INHERIT: [&str; 2] = ["", " (Inherit)"];
183    static LABEL_AUDIT: [&str; 2] = ["", " (Audit)"];
184
185    // Get the handle table from `_EPROCESS.ObjectTable`.
186    let handle_table = match process.handle_table() {
187        Ok(Some(handle_table)) => handle_table,
188        Ok(None) => {
189            println!("        (No handle table)");
190            return Ok(());
191        }
192        Err(err) => {
193            tracing::error!(%err, "Failed to get handle table");
194            return Ok(());
195        }
196    };
197
198    // Iterate over `_HANDLE_TABLE_ENTRY` items.
199    for handle_entry in handle_table.iter()? {
200        let (handle, entry) = match handle_entry {
201            Ok(entry) => entry,
202            Err(err) => {
203                println!("Failed to get handle entry: {}", handle_error(err)?);
204                continue;
205            }
206        };
207
208        let attributes = match entry.attributes() {
209            Ok(attributes) => attributes,
210            Err(err) => {
211                println!("Failed to get attributes: {}", handle_error(err)?);
212                continue;
213            }
214        };
215
216        let granted_access = match entry.granted_access() {
217            Ok(granted_access) => granted_access,
218            Err(err) => {
219                println!("Failed to get granted access: {}", handle_error(err)?);
220                continue;
221            }
222        };
223
224        let object = match entry.object() {
225            Ok(Some(object)) => object,
226            Ok(None) => {
227                // [`WindowsHandleTable::iter`] should only return entries with
228                // valid objects, so this should not happen.
229                println!("<NULL>");
230                continue;
231            }
232            Err(err) => {
233                println!("Failed to get object: {}", handle_error(err)?);
234                continue;
235            }
236        };
237
238        let type_name = match object.type_name() {
239            Ok(type_name) => type_name,
240            Err(err) => handle_error(err)?,
241        };
242
243        let full_path = match object.full_path() {
244            Ok(Some(path)) => path,
245            Ok(None) => String::from("<no-path>"),
246            Err(err) => handle_error(err)?,
247        };
248
249        println!(
250            "        {:04x}: Object: {:x} GrantedAccess: {:08x}{}{}{} Entry: {}",
251            handle,
252            object.va().0,
253            granted_access,
254            LABEL_PROTECTED[((attributes & OBJ_PROTECT_CLOSE) != 0) as usize],
255            LABEL_INHERIT[((attributes & OBJ_INHERIT) != 0) as usize],
256            LABEL_AUDIT[((attributes & OBJ_AUDIT_OBJECT_CLOSE) != 0) as usize],
257            entry.va(),
258        );
259
260        println!("            Type: {type_name}, Path: {full_path}");
261    }
262
263    Ok(())
264}
265
266// Enumerate VADs in a process.
267fn enumerate_regions(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
268    for region in process.regions()? {
269        let region = region?;
270
271        let region_va = region.va();
272        let start = region.start()?;
273        let end = region.end()?;
274        let protection = region.protection()?;
275        let kind = region.kind()?;
276
277        print!("        Region @ {region_va}: {start}-{end} {protection:?}");
278
279        match &kind {
280            VmiOsRegionKind::Private => println!(" Private"),
281            VmiOsRegionKind::MappedImage(mapped) => {
282                let path = match mapped.path() {
283                    Ok(Some(path)) => path,
284                    Ok(None) => String::from("<Pagefile>"),
285                    Err(err) => handle_error(err)?,
286                };
287
288                println!(" Mapped (Exe): {path}");
289            }
290            VmiOsRegionKind::MappedData(mapped) => {
291                let path = match mapped.path() {
292                    Ok(Some(path)) => path,
293                    Ok(None) => String::from("<Pagefile>"),
294                    Err(err) => handle_error(err)?,
295                };
296
297                println!(" Mapped: {path}");
298            }
299        }
300    }
301
302    Ok(())
303}
304
305// Enumerate threads in a process.
306fn enumerate_threads(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
307    for thread in process.threads()? {
308        let thread = thread?;
309
310        let tid = thread.id()?;
311        let object = thread.object()?;
312
313        println!("        Thread @ {object}, TID: {tid}");
314    }
315
316    Ok(())
317}
318
319// Print process information in a `_PEB.ProcessParameters`.
320fn print_process_parameters(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
321    let peb = match process.peb() {
322        Ok(Some(peb)) => peb,
323        Ok(None) => {
324            println!("        (No PEB)");
325            return Ok(());
326        }
327        Err(err) => {
328            println!("Failed to get PEB: {}", handle_error(err)?);
329            return Ok(());
330        }
331    };
332
333    let current_directory = match peb.current_directory() {
334        Ok(current_directory) => current_directory,
335        Err(err) => handle_error(err)?,
336    };
337
338    let dll_path = match peb.dll_path() {
339        Ok(dll_path) => dll_path,
340        Err(err) => handle_error(err)?,
341    };
342
343    let image_path_name = match peb.image_path_name() {
344        Ok(image_path_name) => image_path_name,
345        Err(err) => handle_error(err)?,
346    };
347
348    let command_line = match peb.command_line() {
349        Ok(command_line) => command_line,
350        Err(err) => handle_error(err)?,
351    };
352
353    println!("        Current Directory:    {current_directory}");
354    println!("        DLL Path:             {dll_path}");
355    println!("        Image Path Name:      {image_path_name}");
356    println!("        Command Line:         {command_line}");
357
358    Ok(())
359}
360
361// Enumerate processes in the system.
362fn enumerate_processes(vmi: &VmiState<WindowsOs<Driver>>) -> Result<(), VmiError> {
363    for process in vmi.os().processes()? {
364        let process = process?;
365
366        let pid = process.id()?; // `_EPROCESS.UniqueProcessId`
367        let object = process.object()?; // `_EPROCESS` pointer
368        let name = process.name()?; // `_EPROCESS.ImageFileName`
369        let session = process.session()?; // `_EPROCESS.Session`
370
371        println!("Process @ {object}, PID: {pid}");
372        println!("    Name: {name}");
373        if let Some(session) = session {
374            println!("    Session: {}", session.id()?); // `_MM_SESSION_SPACE.SessionId`
375        }
376
377        println!("    Threads:");
378        enumerate_threads(&process)?;
379
380        println!("    Regions:");
381        enumerate_regions(&process)?;
382
383        println!("    PEB:");
384        print_process_parameters(&process)?;
385
386        println!("    Handles:");
387        enumerate_handle_table(&process)?;
388    }
389
390    Ok(())
391}
392
393fn main() -> Result<(), Box<dyn std::error::Error>> {
394    tracing_subscriber::fmt()
395        .with_max_level(tracing::Level::DEBUG)
396        .with_ansi(false)
397        .init();
398
399    // First argument is the path to the dump file.
400    let args = std::env::args().collect::<Vec<_>>();
401    if args.len() != 2 {
402        eprintln!("Usage: {} <dump-file>", args[0]);
403        std::process::exit(1);
404    }
405
406    let dump_file = &args[1];
407
408    // Setup VMI.
409    let driver = Driver::new(dump_file)?;
410    let core = VmiCore::new(driver)?;
411
412    let registers = core.registers(VcpuId(0))?;
413
414    // Try to find the kernel information.
415    // This is necessary in order to load the profile.
416    let kernel_info = WindowsOs::find_kernel(&core, &registers)?.expect("kernel information");
417    tracing::info!(?kernel_info, "Kernel information");
418
419    // Load the profile.
420    // The profile contains offsets to kernel functions and data structures.
421    let isr = IsrCache::new("cache")?;
422    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
423    let profile = entry.profile()?;
424
425    // Create the VMI session.
426    tracing::info!("Creating VMI session");
427    let os = WindowsOs::<Driver>::with_kernel_base(&profile, kernel_info.base_address)?;
428    let session = VmiSession::new(&core, &os);
429
430    let vmi = session.with_registers(&registers);
431    let root_directory = vmi.os().object_root_directory()?;
432
433    println!("Kernel Modules:");
434    println!("=================================================");
435    enumerate_kernel_modules(&vmi)?;
436
437    println!("Object Tree (root directory: {}):", root_directory.va());
438    println!("=================================================");
439    enumerate_directory_object(&root_directory, 0)?;
440
441    println!("Processes:");
442    println!("=================================================");
443    enumerate_processes(&vmi)?;
444
445    Ok(())
446}
More examples
Hide additional examples
examples/windows-recipe-messagebox.rs (line 76)
65fn main() -> Result<(), Error> {
66    let session = common::create_vmi_session()?;
67
68    let explorer_pid = {
69        // This block is used to drop the pause guard after the PID is found.
70        // If the `session.handle()` would be called with the VM paused, no
71        // events would be triggered.
72        let paused = session.pause_guard()?;
73
74        let vmi = paused.state();
75
76        let explorer = match vmi.os().find_process("explorer.exe")? {
77            Some(explorer) => explorer,
78            None => {
79                tracing::error!("explorer.exe not found");
80                return Ok(());
81            }
82        };
83
84        tracing::info!(
85            pid = %explorer.id()?,
86            object = %explorer.object()?,
87            "found explorer.exe"
88        );
89
90        explorer.id()?
91    };
92
93    session.handle(|session| {
94        UserInjectorHandler::new(
95            session,
96            recipe_factory(MessageBox::new(
97                "Hello, World!",
98                "This is a message box from the VMI!",
99            )),
100        )?
101        .with_pid(explorer_pid)
102    })?;
103
104    Ok(())
105}
examples/windows-recipe-writefile.rs (line 216)
205fn main() -> Result<(), Error> {
206    let session = common::create_vmi_session()?;
207
208    let explorer_pid = {
209        // This block is used to drop the pause guard after the PID is found.
210        // If the `session.handle()` would be called with the VM paused, no
211        // events would be triggered.
212        let paused = session.pause_guard()?;
213
214        let vmi = paused.state();
215
216        let explorer = match vmi.os().find_process("explorer.exe")? {
217            Some(explorer) => explorer,
218            None => {
219                tracing::error!("explorer.exe not found");
220                return Ok(());
221            }
222        };
223
224        tracing::info!(
225            pid = %explorer.id()?,
226            object = %explorer.object()?,
227            "found explorer.exe"
228        );
229
230        explorer.id()?
231    };
232
233    session.handle(|session| {
234        UserInjectorHandler::new(
235            session,
236            recipe_factory(GuestFile::new(
237                "C:\\Users\\John\\Desktop\\test.txt",
238                "Hello, World!".as_bytes(),
239            )),
240        )?
241        .with_pid(explorer_pid)
242    })?;
243
244    Ok(())
245}
examples/windows-recipe-writefile-advanced.rs (line 314)
303fn main() -> Result<(), Error> {
304    let session = common::create_vmi_session()?;
305
306    let explorer_pid = {
307        // This block is used to drop the pause guard after the PID is found.
308        // If the `session.handle()` would be called with the VM paused, no
309        // events would be triggered.
310        let paused = session.pause_guard()?;
311
312        let vmi = paused.state();
313
314        let explorer = match vmi.os().find_process("explorer.exe")? {
315            Some(explorer) => explorer,
316            None => {
317                tracing::error!("explorer.exe not found");
318                return Ok(());
319            }
320        };
321
322        tracing::info!(
323            pid = %explorer.id()?,
324            object = %explorer.object()?,
325            "found explorer.exe"
326        );
327
328        explorer.id()?
329    };
330
331    let mut content = Vec::new();
332    for c in 'A'..='Z' {
333        content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
334    }
335
336    session.handle(|session| {
337        UserInjectorHandler::new(
338            session,
339            recipe_factory(GuestFile::new(
340                "C:\\Users\\John\\Desktop\\test.txt",
341                content,
342            )),
343        )?
344        .with_pid(explorer_pid)
345    })?;
346
347    Ok(())
348}
examples/basic-process-list.rs (line 57)
13fn main() -> Result<(), Error> {
14    // Setup VMI.
15    let driver = VmiXenDriver::<Amd64>::try_from_env()?
16        .context("invalid VMI_XEN_DOMAIN environment variable")?;
17    let core = VmiCore::new(driver)?;
18
19    // Try to find the kernel information.
20    // This is necessary in order to load the profile.
21    let kernel_info = {
22        // Pause the VM to get consistent state.
23        let _pause_guard = core.pause_guard()?;
24
25        // Get the register state for the first vCPU.
26        let registers = core.registers(VcpuId(0))?;
27
28        // On AMD64 architecture, the kernel is usually found using the
29        // `MSR_LSTAR` register, which contains the address of the system call
30        // handler. This register is set by the operating system during boot
31        // and is left unchanged (unless some rootkits are involved).
32        //
33        // Therefore, we can take an arbitrary registers at any point in time
34        // (as long as the OS has booted and the page tables are set up) and
35        // use them to find the kernel.
36        WindowsOs::find_kernel(&core, &registers)?.expect("kernel information")
37    };
38
39    // Load the profile.
40    // The profile contains offsets to kernel functions and data structures.
41    let isr = IsrCache::new("cache")?;
42    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
43    let profile = entry.profile()?;
44
45    // Create the VMI session.
46    tracing::info!("Creating VMI session");
47    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
48    let session = VmiSession::new(&core, &os);
49
50    // Pause the VM again to get consistent state.
51    let paused = session.pause_guard()?;
52
53    // Create a new `VmiState` with the boot CPU registers.
54    let vmi = paused.state();
55
56    // Get the list of processes and print them.
57    for process in vmi.os().processes()? {
58        let process = process?;
59
60        println!(
61            "{} [{}] {} (root @ {})",
62            process.object()?,
63            process.id()?,
64            process.name()?,
65            process.translation_root()?
66        );
67    }
68
69    Ok(())
70}
examples/windows-breakpoint-manager.rs (line 91)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
Source

pub fn access_context(&self, address: Va) -> AccessContext

Available on crate features injector and utils only.

Creates an address context for a given virtual address.

Source

pub fn address_context(&self, address: Va) -> AddressContext

Available on crate features injector and utils only.

Creates an address context for a given virtual address.

Source

pub fn translation_root(&self, va: Va) -> Pa

Available on crate features injector and utils only.

Returns the physical address of the root of the current page table hierarchy for a given virtual address.

Source§

impl<'a, Os> VmiState<'a, Os>
where Os: VmiOs, <Os as VmiOs>::Driver: VmiRead,

Source

pub fn return_address(&self) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Returns the return address from the current stack frame.

Source

pub fn return_from_function( &self, value: u64, ) -> Result<<<<Os as VmiOs>::Architecture as Architecture>::Registers as Registers>::GpRegisters, VmiError>

Available on crate features injector and utils only.

Builds the general-purpose registers that make the current function return value to its caller without executing its body.

Examples found in repository?
examples/windows-reactor/netio.rs (line 553)
523pub fn KfdIsLayerEmpty<Driver>(
524    vmi: &VmiContext<WindowsOs<Driver>>,
525) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
526where
527    Driver: VmiRead,
528    Driver::Architecture: ArchAdapter<Driver>,
529{
530    //
531    // BOOLEAN
532    // NTAPI
533    // KfdIsLayerEmpty (
534    //     _In_ UINT16 layerId
535    //     );
536    //
537
538    let layerId = FwpsLayer(vmi.os().function_argument(0)? as u16);
539
540    if !matches!(
541        layerId,
542        FwpsLayer::ALE_AUTH_CONNECT_V4
543            | FwpsLayer::ALE_AUTH_CONNECT_V6
544            | FwpsLayer::ALE_FLOW_ESTABLISHED_V4
545            | FwpsLayer::ALE_FLOW_ESTABLISHED_V6
546    ) {
547        tracing::trace!(?layerId, "passing through");
548        return Ok(Action::default());
549    }
550
551    tracing::trace!(?layerId, "overriding");
552
553    let registers = vmi.return_from_function(0)?; // Return FALSE
554
555    Ok(Action::Response(
556        VmiEventResponse::default().with_registers(registers),
557    ))
558}
Source

pub fn translate_address(&self, va: Va) -> Result<Pa, VmiError>

Available on crate features injector and utils only.

Translates a virtual address to a physical address.

Source

pub fn read(&self, address: Va, buffer: &mut [u8]) -> Result<(), VmiError>

Available on crate features injector and utils only.

Reads memory from the virtual machine.

Examples found in repository?
examples/windows-reactor/ncrypt.rs (line 94)
63pub fn SslGenerateSessionKeys<Driver>(
64    vmi: &VmiContext<WindowsOs<Driver>>,
65) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
66where
67    Driver: VmiRead,
68    Driver::Architecture: ArchAdapter<Driver>,
69{
70    //
71    // SECURITY_STATUS
72    // WINAPI
73    // SslGenerateSessionKeys(
74    //     _In_ NCRYPT_PROV_HANDLE hSslProvider,
75    //     _In_ NCRYPT_KEY_HANDLE hMasterKey,
76    //     _Out_ NCRYPT_KEY_HANDLE *phReadKey,
77    //     _Out_ NCRYPT_KEY_HANDLE *phWriteKey,
78    //     _In_ PNCryptBufferDesc pParameterList,
79    //     _In_ DWORD dwFlags
80    //     );
81    //
82
83    let hMasterKey = vmi.os().function_argument(1)?;
84    let pParameterList = vmi.os().function_argument(4)?;
85
86    let mut client_random = vec![0u8; 32];
87
88    let parameter_list = vmi.read_struct::<NCryptBufferDesc>(Va(pParameterList))?;
89    for i in 0..parameter_list.cBuffers {
90        let offset = (i as u64) * size_of::<NCryptBuffer>() as u64;
91        let buffer = vmi.read_struct::<NCryptBuffer>(Va(parameter_list.pBuffers + offset))?;
92
93        if buffer.BufferType == NCRYPTBUFFER_SSL_CLIENT_RANDOM {
94            vmi.read(Va(buffer.pvBuffer), &mut client_random)?;
95            break;
96        }
97    }
98
99    let master_key = vmi.read_struct::<NCRYPT_SSL_KEY>(Va(hMasterKey))?;
100    let subkey = vmi.read_struct::<SSL_MASTER_KEY>(Va(master_key.hSubKey))?;
101
102    tracing::info!(
103        client_random = hex::encode(client_random),
104        secret = hex::encode(subkey.rgbMasterKey),
105    );
106
107    Ok(Action::default())
108}
Source

pub fn read_u8(&self, address: Va) -> Result<u8, VmiError>

Available on crate features injector and utils only.

Reads a single byte from the virtual machine.

Source

pub fn read_u16(&self, address: Va) -> Result<u16, VmiError>

Available on crate features injector and utils only.

Reads a 16-bit unsigned integer from the virtual machine.

Source

pub fn read_u32(&self, address: Va) -> Result<u32, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit unsigned integer from the virtual machine.

Examples found in repository?
examples/windows-recipe-writefile.rs (line 188)
96fn recipe_factory<Driver>(data: GuestFile) -> Recipe<WindowsOs<Driver>, GuestFile>
97where
98    Driver: VmiFullDriver<Architecture = Amd64>,
99{
100    recipe![
101        Recipe::<WindowsOs<Driver>>::new(data),
102        //
103        // Step 1:
104        // - Create a file
105        //
106        {
107            tracing::info!(
108                target_path = data![target_path],
109                "step 1: kernel32!CreateFileA()"
110            );
111
112            const GENERIC_WRITE: u64 = 0x40000000;
113            const CREATE_ALWAYS: u64 = 2;
114            const FILE_ATTRIBUTE_NORMAL: u64 = 0x80;
115
116            inject! {
117                kernel32!CreateFileA(
118                    &data![target_path],        // lpFileName
119                    GENERIC_WRITE,              // dwDesiredAccess
120                    0,                          // dwShareMode
121                    0,                          // lpSecurityAttributes
122                    CREATE_ALWAYS,              // dwCreationDisposition
123                    FILE_ATTRIBUTE_NORMAL,      // dwFlagsAndAttributes
124                    0                           // hTemplateFile
125                )
126            }
127        },
128        //
129        // Step 2:
130        // - Verify the file handle
131        // - Write the content to the file
132        //
133        {
134            let return_value = registers!().rax;
135
136            const INVALID_HANDLE_VALUE: u64 = 0xffff_ffff_ffff_ffff;
137
138            if return_value == INVALID_HANDLE_VALUE {
139                tracing::error!(
140                    return_value = %Hex(return_value),
141                    "step 2: kernel32!CreateFileA() failed"
142                );
143
144                return Ok(RecipeControlFlow::Break);
145            }
146
147            tracing::info!(
148                handle = %Hex(data![handle]),
149                "step 2: kernel32!WriteFile()"
150            );
151
152            // Save the handle.
153            data![handle] = return_value;
154
155            // Allocate a value on the stack to store the output parameter.
156            data![bytes_written_ptr] = copy_to_stack!(0u64)?;
157
158            inject! {
159                kernel32!WriteFile(
160                    data![handle],              // hFile
161                    data![content],             // lpBuffer
162                    data![content].len(),       // nNumberOfBytesToWrite
163                    data![bytes_written_ptr],   // lpNumberOfBytesWritten
164                    0                           // lpOverlapped
165                )
166            }
167        },
168        //
169        // Step 3:
170        // - Verify that the `WriteFile()` call succeeded
171        // - Close the file handle
172        //
173        {
174            let return_value = registers!().rax;
175
176            // Check if the `WriteFile()` call failed.
177            if return_value == 0 {
178                tracing::error!(
179                    return_value = %Hex(return_value),
180                    "step 3: kernel32!WriteFile() failed"
181                );
182
183                // Don't exit, we want to close the handle.
184                // return Ok(RecipeControlFlow::Break);
185            }
186
187            // Read the number of bytes written.
188            let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
189            tracing::info!(number_of_bytes_written, "step 3: kernel32!WriteFile()");
190
191            tracing::info!(
192                handle = %Hex(data![handle]),
193                "step 3: kernel32!CloseHandle()"
194            );
195
196            inject! {
197                kernel32!CloseHandle(
198                    data![handle]               // hObject
199                )
200            }
201        },
202    ]
203}
More examples
Hide additional examples
examples/windows-recipe-writefile-advanced.rs (line 235)
139pub fn recipe_factory<Driver>(data: GuestFile) -> Recipe<WindowsOs<Driver>, GuestFile>
140where
141    Driver: VmiFullDriver<Architecture = Amd64>,
142{
143    recipe![
144        Recipe::<WindowsOs<Driver>>::new(data),
145        //
146        // Step 1:
147        // - Create a file.
148        //
149        {
150            tracing::info!(
151                target_path = data![target_path],
152                "step 1: kernel32!CreateFileA()"
153            );
154
155            const GENERIC_WRITE: u64 = 0x40000000;
156            const CREATE_ALWAYS: u64 = 2;
157            const FILE_ATTRIBUTE_NORMAL: u64 = 0x80;
158
159            inject! {
160                kernel32!CreateFileA(
161                    &data![target_path],        // lpFileName
162                    GENERIC_WRITE,              // dwDesiredAccess
163                    0,                          // dwShareMode
164                    0,                          // lpSecurityAttributes
165                    CREATE_ALWAYS,              // dwCreationDisposition
166                    FILE_ATTRIBUTE_NORMAL,      // dwFlagsAndAttributes
167                    0                           // hTemplateFile
168                )
169            }
170        },
171        //
172        // Step 2:
173        // - Verify the file handle
174        // - Write the first chunk to the file
175        //
176        {
177            let return_value = registers!().rax;
178
179            const INVALID_HANDLE_VALUE: u64 = 0xffff_ffff_ffff_ffff;
180
181            if return_value == INVALID_HANDLE_VALUE {
182                tracing::error!(
183                    return_value = %Hex(return_value),
184                    "step 2: kernel32!CreateFileA() failed"
185                );
186
187                return Ok(RecipeControlFlow::Break);
188            }
189
190            tracing::info!(
191                handle = %Hex(data![handle]),
192                "step 2: kernel32!WriteFile()"
193            );
194
195            // Save the handle.
196            data![handle] = return_value;
197
198            // Allocate a value on the stack to store the output parameter.
199            data![bytes_written_ptr] = copy_to_stack!(0u64)?;
200
201            // Get the first chunk of content.
202            let content = &data![content];
203            let chunk_size = usize::min(content.len(), data![chunk_size]);
204            let chunk = content[..chunk_size].to_vec();
205
206            inject! {
207                kernel32!WriteFile(
208                    data![handle],              // hFile
209                    chunk,                      // lpBuffer
210                    chunk.len() as u64,         // nNumberOfBytesToWrite
211                    data![bytes_written_ptr],   // lpNumberOfBytesWritten
212                    0                           // lpOverlapped
213                )
214            }
215        },
216        //
217        // Step 3:
218        // - Verify that the `WriteFile()` call succeeded
219        // - Write the next chunk to the file
220        // - Repeat this step until all content is written
221        //
222        {
223            let return_value = registers!().rax;
224
225            if return_value == 0 {
226                tracing::error!(
227                    return_value = %Hex(return_value),
228                    "step 3: kernel32!WriteFile() failed"
229                );
230
231                return Ok(RecipeControlFlow::Break);
232            }
233
234            // Read the number of bytes written and update the total.
235            let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
236            data![bytes_written_total] += number_of_bytes_written;
237
238            let bytes_written_total = data![bytes_written_total];
239            let content = &data![content];
240
241            // If all content is written, move to the next step.
242            if bytes_written_total >= content.len() as u32 {
243                return Ok(RecipeControlFlow::Continue);
244            }
245
246            // Get the next chunk of content.
247            let remaining = content.len() - bytes_written_total as usize;
248            let chunk_size = usize::min(remaining, data![chunk_size]);
249            let chunk = &content[bytes_written_total as usize..];
250            let chunk = chunk[..chunk_size].to_vec();
251
252            // Allocate a value on the stack to store the output parameter.
253            data![bytes_written_ptr] = copy_to_stack!(0u64)?;
254
255            inject! {
256                kernel32!WriteFile(
257                    data![handle],              // hFile
258                    chunk,                      // lpBuffer
259                    chunk.len() as u64,         // nNumberOfBytesToWrite
260                    data![bytes_written_ptr],   // lpNumberOfBytesWritten
261                    0                           // lpOverlapped
262                )
263            }?;
264
265            Ok(RecipeControlFlow::Repeat)
266        },
267        //
268        // Step 4:
269        // - Verify that the last `WriteFile()` call succeeded
270        // - Close the file handle
271        //
272        {
273            let return_value = registers!().rax;
274
275            if return_value == 0 {
276                tracing::error!(
277                    return_value = %Hex(return_value),
278                    "step 4: kernel32!WriteFile() failed"
279                );
280
281                // Don't exit, we want to close the handle.
282                // return Ok(RecipeControlFlow::Break);
283            }
284
285            // Read the number of bytes written.
286            let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
287            tracing::info!(number_of_bytes_written, "step 4: kernel32!WriteFile()");
288
289            tracing::info!(
290                handle = %Hex(data![handle]),
291                "step 4: kernel32!CloseHandle()"
292            );
293
294            inject! {
295                kernel32!CloseHandle(
296                    data![handle]               // hObject
297                )
298            }
299        },
300    ]
301}
Source

pub fn read_u64(&self, address: Va) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit unsigned integer from the virtual machine.

Source

pub fn read_uint(&self, address: Va, size: usize) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an unsigned integer of the specified size from the virtual machine.

This method reads an unsigned integer of the specified size (in bytes) from the virtual machine. Note that the size must be 1, 2, 4, or 8.

The result is returned as a u64 to accommodate the widest possible integer size.

Source

pub fn read_field( &self, base_address: Va, field: &Field, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a field of a structure from the virtual machine.

This method reads a field from the virtual machine. The field is defined by the provided Field structure, which specifies the offset and size of the field within the memory region.

The result is returned as a u64 to accommodate the widest possible integer size.

Source

pub fn read_address(&self, address: Va) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an address-sized unsigned integer from the virtual machine.

Source

pub fn read_address_native(&self, address: Va) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an address-sized unsigned integer from the virtual machine.

Source

pub fn read_address32(&self, address: Va) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit address from the virtual machine.

Source

pub fn read_address64(&self, address: Va) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit address from the virtual machine.

Source

pub fn read_va(&self, address: Va) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a virtual address from the virtual machine.

Source

pub fn read_va_native(&self, address: Va) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a virtual address from the virtual machine.

Source

pub fn read_va32(&self, address: Va) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit virtual address from the virtual machine.

Source

pub fn read_va64(&self, address: Va) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit virtual address from the virtual machine.

Source

pub fn read_string_bytes_limited( &self, address: Va, limit: usize, ) -> Result<Vec<u8>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string of bytes from the virtual machine with a specified limit.

Source

pub fn read_string_bytes(&self, address: Va) -> Result<Vec<u8>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string of bytes from the virtual machine.

Source

pub fn read_string_utf16_bytes_limited( &self, address: Va, limit: usize, ) -> Result<Vec<u16>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.

Source

pub fn read_string_utf16_bytes(&self, address: Va) -> Result<Vec<u16>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine.

Source

pub fn read_string_limited( &self, address: Va, limit: usize, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string from the virtual machine with a specified limit.

Source

pub fn read_string(&self, address: Va) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string from the virtual machine.

Source

pub fn read_string_utf16_limited( &self, address: Va, limit: usize, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.

Source

pub fn read_string_utf16(&self, address: Va) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine.

Source

pub fn read_struct<T>(&self, address: Va) -> Result<T, VmiError>
where T: IntoBytes + FromBytes,

Available on crate features injector and utils only.

Reads a struct from the virtual machine.

Examples found in repository?
examples/windows-reactor/ncrypt.rs (line 88)
63pub fn SslGenerateSessionKeys<Driver>(
64    vmi: &VmiContext<WindowsOs<Driver>>,
65) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
66where
67    Driver: VmiRead,
68    Driver::Architecture: ArchAdapter<Driver>,
69{
70    //
71    // SECURITY_STATUS
72    // WINAPI
73    // SslGenerateSessionKeys(
74    //     _In_ NCRYPT_PROV_HANDLE hSslProvider,
75    //     _In_ NCRYPT_KEY_HANDLE hMasterKey,
76    //     _Out_ NCRYPT_KEY_HANDLE *phReadKey,
77    //     _Out_ NCRYPT_KEY_HANDLE *phWriteKey,
78    //     _In_ PNCryptBufferDesc pParameterList,
79    //     _In_ DWORD dwFlags
80    //     );
81    //
82
83    let hMasterKey = vmi.os().function_argument(1)?;
84    let pParameterList = vmi.os().function_argument(4)?;
85
86    let mut client_random = vec![0u8; 32];
87
88    let parameter_list = vmi.read_struct::<NCryptBufferDesc>(Va(pParameterList))?;
89    for i in 0..parameter_list.cBuffers {
90        let offset = (i as u64) * size_of::<NCryptBuffer>() as u64;
91        let buffer = vmi.read_struct::<NCryptBuffer>(Va(parameter_list.pBuffers + offset))?;
92
93        if buffer.BufferType == NCRYPTBUFFER_SSL_CLIENT_RANDOM {
94            vmi.read(Va(buffer.pvBuffer), &mut client_random)?;
95            break;
96        }
97    }
98
99    let master_key = vmi.read_struct::<NCRYPT_SSL_KEY>(Va(hMasterKey))?;
100    let subkey = vmi.read_struct::<SSL_MASTER_KEY>(Va(master_key.hSubKey))?;
101
102    tracing::info!(
103        client_random = hex::encode(client_random),
104        secret = hex::encode(subkey.rgbMasterKey),
105    );
106
107    Ok(Action::default())
108}
More examples
Hide additional examples
examples/windows-reactor/netio.rs (line 383)
348pub fn KfdClassify<Driver>(
349    vmi: &VmiContext<WindowsOs<Driver>>,
350) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
351where
352    Driver: VmiRead,
353    Driver::Architecture: ArchAdapter<Driver>,
354{
355    //
356    // PVOID
357    // NTAPI
358    // KfdClassify (
359    //     _In_ UINT16 layerId,
360    //     _In_ const FWPS_INCOMING_VALUES* inFixedValues,
361    //     _In_ const FWPS_INCOMING_METADATA_VALUES* inContext,
362    //     _In_ PVOID packet,
363    //     _In_ const FWPP_SHIM_PROVIDER_CONTEXT* shimProvContext,
364    //     _Inout_ FWPS_CLASSIFY_OUT* classifyOut
365    //     );
366    //
367
368    let layerId = FwpsLayer(vmi.os().function_argument(0)? as u16);
369    let inFixedValues = Va(vmi.os().function_argument(1)?);
370    let inContext = Va(vmi.os().function_argument(2)?);
371
372    let (
373        protocol_index,
374        local_address_index,
375        local_port_index,
376        remote_address_index,
377        remote_port_index,
378    ) = match layerId.network_5tuple_indexes() {
379        Some(indexes) => indexes,
380        None => return Ok(Action::default()),
381    };
382
383    let incoming_values = vmi.read_struct::<FWPS_INCOMING_VALUES0>(inFixedValues)?;
384    let incoming = Va(incoming_values.incomingValue);
385
386    const SIZEOF_VALUE: u64 = size_of::<FWPS_INCOMING_VALUE0>() as u64;
387
388    //
389    // Protocol.
390    //
391
392    let protocol =
393        vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + protocol_index * SIZEOF_VALUE)?;
394
395    if protocol.value.ty != FwpDataType::UINT8 {
396        tracing::debug!(
397            protocol_type = ?protocol.value.ty,
398            expected = ?FwpDataType::UINT8,
399            "unexpected protocol type"
400        );
401        return Ok(Action::default());
402    }
403
404    //
405    // Local Address.
406    //
407
408    let local_address =
409        vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + local_address_index * SIZEOF_VALUE)?;
410
411    if local_address.value.ty != FwpDataType::UINT32 {
412        tracing::debug!(
413            local_address_type = ?local_address.value.ty,
414            expected = ?FwpDataType::UINT32,
415            "unexpected local address type"
416        );
417        return Ok(Action::default());
418    }
419
420    //
421    // Local Port.
422    //
423
424    let local_port =
425        vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + local_port_index * SIZEOF_VALUE)?;
426
427    if local_port.value.ty != FwpDataType::UINT16 {
428        tracing::debug!(
429            local_port_type = ?local_port.value.ty,
430            expected = ?FwpDataType::UINT16,
431            "unexpected local port type"
432        );
433        return Ok(Action::default());
434    }
435
436    //
437    // Remote Address.
438    //
439
440    let remote_address =
441        vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + remote_address_index * SIZEOF_VALUE)?;
442
443    if remote_address.value.ty != FwpDataType::UINT32 {
444        tracing::debug!(
445            remote_address_type = ?remote_address.value.ty,
446            expected = ?FwpDataType::UINT32,
447            "unexpected remote address type"
448        );
449        return Ok(Action::default());
450    }
451
452    //
453    // Remote Port.
454    //
455
456    let remote_port =
457        vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + remote_port_index * SIZEOF_VALUE)?;
458
459    if remote_port.value.ty != FwpDataType::UINT16 {
460        tracing::debug!(
461            remote_port_type = ?remote_port.value.ty,
462            expected = ?FwpDataType::UINT16,
463            "unexpected remote port type"
464        );
465        return Ok(Action::default());
466    }
467
468    let protocol = IpProtocol(protocol.value.data as u8);
469    let local_address = local_address.value.data as u32;
470    let local_port = local_port.value.data as u16;
471    let remote_address = remote_address.value.data as u32;
472    let remote_port = remote_port.value.data as u16;
473
474    let local_ip = IpAddr::from(local_address.to_be_bytes());
475    let remote_ip = IpAddr::from(remote_address.to_be_bytes());
476
477    // Fetch the most valuable information that can't be obtained
478    // from the pcap: the process ID that initiated the connection.
479    let context = vmi.read_struct::<FWPS_INCOMING_METADATA_VALUES0>(inContext)?;
480    let metadata_values = FwpsMetadataFields::from_bits_retain(context.currentMetadataValues);
481    let pid = if metadata_values.contains(FwpsMetadataFields::PROCESS_ID) {
482        Some(context.processId)
483    }
484    else {
485        None
486    };
487
488    tracing::info!(
489        ?protocol,
490        %local_ip,
491        local_port,
492        %remote_ip,
493        remote_port,
494        pid,
495    );
496
497    Ok(Action::default())
498}
Source

pub fn read_in( &self, ctx: impl Into<AccessContext>, buffer: &mut [u8], ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Reads memory from the virtual machine.

Source

pub fn read_u8_in(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>

Available on crate features injector and utils only.

Reads a single byte from the virtual machine.

Source

pub fn read_u16_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u16, VmiError>

Available on crate features injector and utils only.

Reads a 16-bit unsigned integer from the virtual machine.

Source

pub fn read_u32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u32, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit unsigned integer from the virtual machine.

Source

pub fn read_u64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit unsigned integer from the virtual machine.

Source

pub fn read_uint_in( &self, ctx: impl Into<AccessContext>, size: usize, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an unsigned integer of the specified size from the virtual machine.

This method reads an unsigned integer of the specified size (in bytes) from the virtual machine. Note that the size must be 1, 2, 4, or 8.

The result is returned as a u64 to accommodate the widest possible integer size.

Source

pub fn read_field_in( &self, ctx: impl Into<AccessContext>, field: &Field, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a field of a structure from the virtual machine.

This method reads a field from the virtual machine. The field is defined by the provided Field structure, which specifies the offset and size of the field within the memory region.

The result is returned as a u64 to accommodate the widest possible integer size.

Source

pub fn read_address_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an address-sized unsigned integer from the virtual machine.

Source

pub fn read_address_native_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an address-sized unsigned integer from the virtual machine.

Source

pub fn read_address32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit address from the virtual machine.

Source

pub fn read_address64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit address from the virtual machine.

Source

pub fn read_va_in(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a virtual address from the virtual machine.

Source

pub fn read_va_native_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a virtual address from the virtual machine.

Source

pub fn read_va32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit virtual address from the virtual machine.

Source

pub fn read_va64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit virtual address from the virtual machine.

Source

pub fn read_string_bytes_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u8>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string of bytes from the virtual machine with a specified limit.

Source

pub fn read_string_bytes_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u8>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string of bytes from the virtual machine.

Source

pub fn read_string_utf16_bytes_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u16>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.

Source

pub fn read_string_utf16_bytes_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u16>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine.

Source

pub fn read_string_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string from the virtual machine with a specified limit.

Source

pub fn read_string_in( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string from the virtual machine.

Source

pub fn read_string_utf16_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.

Source

pub fn read_string_utf16_in( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine.

Source

pub fn read_struct_in<T>( &self, ctx: impl Into<AccessContext>, ) -> Result<T, VmiError>
where T: IntoBytes + FromBytes,

Available on crate features injector and utils only.

Reads a struct from the virtual machine.

Source§

impl<'a, Os> VmiState<'a, Os>
where Os: VmiOs, <Os as VmiOs>::Driver: VmiRead + VmiWrite,

Source

pub fn write(&self, address: Va, buffer: &[u8]) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes memory to the virtual machine.

Source

pub fn write_in( &self, ctx: impl Into<AccessContext>, buffer: &[u8], ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes memory to the virtual machine.

Source

pub fn write_u8(&self, address: Va, value: u8) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a single byte to the virtual machine.

Source

pub fn write_u16(&self, address: Va, value: u16) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a 16-bit unsigned integer to the virtual machine.

Source

pub fn write_u32(&self, address: Va, value: u32) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a 32-bit unsigned integer to the virtual machine.

Source

pub fn write_u64(&self, address: Va, value: u64) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a 64-bit unsigned integer to the virtual machine.

Source

pub fn write_struct<T>(&self, address: Va, value: T) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a struct to the virtual machine.

Source§

impl<'a, Os> VmiState<'a, Os>
where Os: VmiOs, <Os as VmiOs>::Driver: VmiSetRegisters,

Source

pub fn set_registers( &self, vcpu: VcpuId, registers: <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Sets the registers of a virtual CPU.

Methods from Deref<Target = VmiSession<'a, Os>>§

Source

pub fn with_registers( &'a self, registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Os>

Available on crate features injector and utils only.

Creates a new VMI state with the specified registers.

Examples found in repository?
examples/windows-dump.rs (line 430)
393fn main() -> Result<(), Box<dyn std::error::Error>> {
394    tracing_subscriber::fmt()
395        .with_max_level(tracing::Level::DEBUG)
396        .with_ansi(false)
397        .init();
398
399    // First argument is the path to the dump file.
400    let args = std::env::args().collect::<Vec<_>>();
401    if args.len() != 2 {
402        eprintln!("Usage: {} <dump-file>", args[0]);
403        std::process::exit(1);
404    }
405
406    let dump_file = &args[1];
407
408    // Setup VMI.
409    let driver = Driver::new(dump_file)?;
410    let core = VmiCore::new(driver)?;
411
412    let registers = core.registers(VcpuId(0))?;
413
414    // Try to find the kernel information.
415    // This is necessary in order to load the profile.
416    let kernel_info = WindowsOs::find_kernel(&core, &registers)?.expect("kernel information");
417    tracing::info!(?kernel_info, "Kernel information");
418
419    // Load the profile.
420    // The profile contains offsets to kernel functions and data structures.
421    let isr = IsrCache::new("cache")?;
422    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
423    let profile = entry.profile()?;
424
425    // Create the VMI session.
426    tracing::info!("Creating VMI session");
427    let os = WindowsOs::<Driver>::with_kernel_base(&profile, kernel_info.base_address)?;
428    let session = VmiSession::new(&core, &os);
429
430    let vmi = session.with_registers(&registers);
431    let root_directory = vmi.os().object_root_directory()?;
432
433    println!("Kernel Modules:");
434    println!("=================================================");
435    enumerate_kernel_modules(&vmi)?;
436
437    println!("Object Tree (root directory: {}):", root_directory.va());
438    println!("=================================================");
439    enumerate_directory_object(&root_directory, 0)?;
440
441    println!("Processes:");
442    println!("=================================================");
443    enumerate_processes(&vmi)?;
444
445    Ok(())
446}
More examples
Hide additional examples
examples/windows-breakpoint-manager.rs (line 89)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
Source

pub fn without_os(&self) -> VmiSession<'a, NoOS<<Os as VmiOs>::Driver>>

Available on crate features injector and utils only.

Creates a new VMI session without an OS-specific implementation.

Source

pub fn core(&self) -> &'a VmiCore<<Os as VmiOs>::Driver>

Available on crate features injector and utils only.

Returns the VMI core.

Source

pub fn underlying_os(&self) -> &'a Os

Available on crate features injector and utils only.

Returns the underlying OS-specific implementation.

Source

pub fn wait_for_event( &self, timeout: Duration, handler: &mut impl VmiHandler<Os>, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Waits for an event to occur and processes it with the provided handler.

This method blocks until an event occurs or the specified timeout is reached. When an event occurs, it is passed to the provided callback function for processing.

Source

pub fn handle<Handler>( &self, handler_factory: impl FnOnce(&VmiSession<'_, Os>) -> Result<Handler, VmiError>, ) -> Result<Option<<Handler as VmiHandler<Os>>::Output>, VmiError>
where Handler: VmiHandler<Os>,

Available on crate features injector and utils only.

Enters the main event handling loop that processes VMI events until finished.

Examples found in repository?
examples/windows-recipe-messagebox.rs (lines 93-102)
65fn main() -> Result<(), Error> {
66    let session = common::create_vmi_session()?;
67
68    let explorer_pid = {
69        // This block is used to drop the pause guard after the PID is found.
70        // If the `session.handle()` would be called with the VM paused, no
71        // events would be triggered.
72        let paused = session.pause_guard()?;
73
74        let vmi = paused.state();
75
76        let explorer = match vmi.os().find_process("explorer.exe")? {
77            Some(explorer) => explorer,
78            None => {
79                tracing::error!("explorer.exe not found");
80                return Ok(());
81            }
82        };
83
84        tracing::info!(
85            pid = %explorer.id()?,
86            object = %explorer.object()?,
87            "found explorer.exe"
88        );
89
90        explorer.id()?
91    };
92
93    session.handle(|session| {
94        UserInjectorHandler::new(
95            session,
96            recipe_factory(MessageBox::new(
97                "Hello, World!",
98                "This is a message box from the VMI!",
99            )),
100        )?
101        .with_pid(explorer_pid)
102    })?;
103
104    Ok(())
105}
More examples
Hide additional examples
examples/windows-recipe-writefile.rs (lines 233-242)
205fn main() -> Result<(), Error> {
206    let session = common::create_vmi_session()?;
207
208    let explorer_pid = {
209        // This block is used to drop the pause guard after the PID is found.
210        // If the `session.handle()` would be called with the VM paused, no
211        // events would be triggered.
212        let paused = session.pause_guard()?;
213
214        let vmi = paused.state();
215
216        let explorer = match vmi.os().find_process("explorer.exe")? {
217            Some(explorer) => explorer,
218            None => {
219                tracing::error!("explorer.exe not found");
220                return Ok(());
221            }
222        };
223
224        tracing::info!(
225            pid = %explorer.id()?,
226            object = %explorer.object()?,
227            "found explorer.exe"
228        );
229
230        explorer.id()?
231    };
232
233    session.handle(|session| {
234        UserInjectorHandler::new(
235            session,
236            recipe_factory(GuestFile::new(
237                "C:\\Users\\John\\Desktop\\test.txt",
238                "Hello, World!".as_bytes(),
239            )),
240        )?
241        .with_pid(explorer_pid)
242    })?;
243
244    Ok(())
245}
examples/windows-recipe-writefile-advanced.rs (lines 336-345)
303fn main() -> Result<(), Error> {
304    let session = common::create_vmi_session()?;
305
306    let explorer_pid = {
307        // This block is used to drop the pause guard after the PID is found.
308        // If the `session.handle()` would be called with the VM paused, no
309        // events would be triggered.
310        let paused = session.pause_guard()?;
311
312        let vmi = paused.state();
313
314        let explorer = match vmi.os().find_process("explorer.exe")? {
315            Some(explorer) => explorer,
316            None => {
317                tracing::error!("explorer.exe not found");
318                return Ok(());
319            }
320        };
321
322        tracing::info!(
323            pid = %explorer.id()?,
324            object = %explorer.object()?,
325            "found explorer.exe"
326        );
327
328        explorer.id()?
329    };
330
331    let mut content = Vec::new();
332    for c in 'A'..='Z' {
333        content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
334    }
335
336    session.handle(|session| {
337        UserInjectorHandler::new(
338            session,
339            recipe_factory(GuestFile::new(
340                "C:\\Users\\John\\Desktop\\test.txt",
341                content,
342            )),
343        )?
344        .with_pid(explorer_pid)
345    })?;
346
347    Ok(())
348}
examples/windows-breakpoint-manager.rs (line 556)
520fn main() -> Result<(), Error> {
521    tracing_subscriber::fmt()
522        .with_max_level(tracing::Level::DEBUG)
523        .init();
524
525    // Setup VMI.
526    let driver = VmiXenDriver::<Amd64>::try_from_env()?
527        .context("invalid VMI_XEN_DOMAIN environment variable")?;
528    let core = VmiCore::new(driver)?;
529
530    // Try to find the kernel information.
531    // This is necessary in order to load the profile.
532    let kernel_info = {
533        let _pause_guard = core.pause_guard()?;
534        let regs = core.registers(0.into())?;
535
536        WindowsOs::find_kernel(&core, &regs)?.expect("kernel information")
537    };
538
539    // Load the profile.
540    // The profile contains offsets to kernel functions and data structures.
541    let isr = IsrCache::new("cache")?;
542    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
543    let profile = entry.profile()?;
544
545    // Create the VMI session.
546    tracing::info!("Creating VMI session");
547    let terminate_flag = Arc::new(AtomicBool::new(false));
548    signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
549    signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
550    signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
551    signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
552
553    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
554    let session = VmiSession::new(&core, &os);
555
556    session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
557
558    Ok(())
559}
examples/windows-reactor/main.rs (lines 353-355)
244fn main() -> Result<(), Error> {
245    let filter = EnvFilter::default()
246        .add_directive(tracing::Level::DEBUG.into())
247        .add_directive("reqwest=warn".parse()?)
248        .add_directive("rustls=warn".parse()?);
249
250    tracing_subscriber::fmt()
251        .with_env_filter(filter)
252        .with_target(false)
253        .init();
254
255    // Setup VMI.
256    let driver = VmiXenDriver::<Amd64>::try_from_env()?
257        .context("invalid VMI_XEN_DOMAIN environment variable")?;
258    let core = VmiCore::new(driver)?;
259
260    // Try to find the kernel information.
261    // This is necessary in order to load the profile.
262    let kernel_info = {
263        let _pause_guard = core.pause_guard()?;
264        let registers = core.registers(VcpuId(0))?;
265
266        WindowsOs::find_kernel(&core, &registers)?.context("cannot find kernel information")?
267    };
268
269    // Load the kernel profile.
270    // The profile contains offsets to kernel functions and data structures.
271    tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
272    let isr = IsrCache::new("cache")?;
273    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
274    let profile = entry.profile()?;
275
276    // Create the VMI session.
277    tracing::info!("creating VMI session");
278    let terminate_flag = Arc::new(AtomicBool::new(false));
279    signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
280    signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
281    signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
282    signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
283
284    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
285    let session = VmiSession::new(&core, &os);
286
287    let handler = NetIo::default();
288
289    //
290    // The following `let ncrypt_* = ...` lines demonstrate how to manually
291    // resolve a module, load its profile (symbols) and add it to the resolver
292    // via `with_module(_in_process)`.
293    //
294    // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
295    // will automatically resolve modules if they are not explicitly added.
296    //
297    // Manually resolving modules can be useful in cases where you want to deal
298    // with the resolved information (base address, profile) in other places.
299    //
300
301    let ncrypt_resolved = {
302        let paused = session.pause_guard()?;
303        let vmi = paused.state();
304
305        // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
306        // would also work, but this demonstrates how to use a custom predicate.
307        //
308        // Also, `match_lsass` is more strict, because it specifically looks
309        // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
310        // positives or potential malicious processes).
311        vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
312            .context("ncrypt.dll not found in lsass.exe")?
313    };
314
315    let ncrypt_entry = isr
316        .entry_from_codeview(ncrypt_resolved.debug_signature)
317        .context("cannot find symbols for ncrypt.dll")?;
318
319    let ncrypt_profile = ncrypt_entry
320        .profile()
321        .context("cannot load profile for ncrypt.dll")?;
322
323    // The `SymbolCache` holds the resolved `isr::Entry` items.
324    let mut cache = SymbolCache::default();
325    let modules = ModuleResolver::default()
326        // `with_kernel` MUST be called if `Event` variants reference kernel
327        // symbols - like `NtWriteFile` in this example.
328        //
329        // This is because the "kernel" module is always optional.
330        .with_kernel(kernel_info.base_address, profile)
331        .with_module_in_process(
332            Module::NcryptDll,
333            ncrypt_resolved.process,
334            ncrypt_resolved.image_base,
335            ncrypt_profile,
336        )
337        // This will automatically resolve the `netio.sys` module and load
338        // its profile.
339        //
340        // Note that if we hadn't called `with_module_in_process` for
341        // `ncrypt.dll`, it would also be automatically resolved here.
342        .resolve(&session, &isr, &mut cache)?;
343
344    // Finally, we collect the events according to the resolved information
345    // and the metadata.
346    //
347    // For example, if some module/event is marked as `optional` and the
348    // resolver fails to resolve it, then it will simply not be included
349    // in the `events`.
350    let events = modules.into_events()?;
351
352    // And we're ready to create the reactor!
353    session.handle(|session| {
354        Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
355    })?;
356
357    Ok(())
358}
Source

pub fn handle_with_timeout<Handler>( &self, timeout: Duration, handler_factory: impl FnOnce(&VmiSession<'_, Os>) -> Result<Handler, VmiError>, ) -> Result<Option<<Handler as VmiHandler<Os>>::Output>, VmiError>
where Handler: VmiHandler<Os>,

Available on crate features injector and utils only.

Enters the main event handling loop that processes VMI events until finished, with a timeout for each event.

Source

pub fn pause_guard(&self) -> Result<VmiSessionPauseGuard<'_, Os>, VmiError>

Available on crate features injector and utils only.

Pauses the virtual machine, snapshots the boot CPU registers, and returns a guard that resumes the VM when dropped.

Examples found in repository?
examples/windows-recipe-messagebox.rs (line 72)
65fn main() -> Result<(), Error> {
66    let session = common::create_vmi_session()?;
67
68    let explorer_pid = {
69        // This block is used to drop the pause guard after the PID is found.
70        // If the `session.handle()` would be called with the VM paused, no
71        // events would be triggered.
72        let paused = session.pause_guard()?;
73
74        let vmi = paused.state();
75
76        let explorer = match vmi.os().find_process("explorer.exe")? {
77            Some(explorer) => explorer,
78            None => {
79                tracing::error!("explorer.exe not found");
80                return Ok(());
81            }
82        };
83
84        tracing::info!(
85            pid = %explorer.id()?,
86            object = %explorer.object()?,
87            "found explorer.exe"
88        );
89
90        explorer.id()?
91    };
92
93    session.handle(|session| {
94        UserInjectorHandler::new(
95            session,
96            recipe_factory(MessageBox::new(
97                "Hello, World!",
98                "This is a message box from the VMI!",
99            )),
100        )?
101        .with_pid(explorer_pid)
102    })?;
103
104    Ok(())
105}
More examples
Hide additional examples
examples/windows-recipe-writefile.rs (line 212)
205fn main() -> Result<(), Error> {
206    let session = common::create_vmi_session()?;
207
208    let explorer_pid = {
209        // This block is used to drop the pause guard after the PID is found.
210        // If the `session.handle()` would be called with the VM paused, no
211        // events would be triggered.
212        let paused = session.pause_guard()?;
213
214        let vmi = paused.state();
215
216        let explorer = match vmi.os().find_process("explorer.exe")? {
217            Some(explorer) => explorer,
218            None => {
219                tracing::error!("explorer.exe not found");
220                return Ok(());
221            }
222        };
223
224        tracing::info!(
225            pid = %explorer.id()?,
226            object = %explorer.object()?,
227            "found explorer.exe"
228        );
229
230        explorer.id()?
231    };
232
233    session.handle(|session| {
234        UserInjectorHandler::new(
235            session,
236            recipe_factory(GuestFile::new(
237                "C:\\Users\\John\\Desktop\\test.txt",
238                "Hello, World!".as_bytes(),
239            )),
240        )?
241        .with_pid(explorer_pid)
242    })?;
243
244    Ok(())
245}
examples/windows-recipe-writefile-advanced.rs (line 310)
303fn main() -> Result<(), Error> {
304    let session = common::create_vmi_session()?;
305
306    let explorer_pid = {
307        // This block is used to drop the pause guard after the PID is found.
308        // If the `session.handle()` would be called with the VM paused, no
309        // events would be triggered.
310        let paused = session.pause_guard()?;
311
312        let vmi = paused.state();
313
314        let explorer = match vmi.os().find_process("explorer.exe")? {
315            Some(explorer) => explorer,
316            None => {
317                tracing::error!("explorer.exe not found");
318                return Ok(());
319            }
320        };
321
322        tracing::info!(
323            pid = %explorer.id()?,
324            object = %explorer.object()?,
325            "found explorer.exe"
326        );
327
328        explorer.id()?
329    };
330
331    let mut content = Vec::new();
332    for c in 'A'..='Z' {
333        content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
334    }
335
336    session.handle(|session| {
337        UserInjectorHandler::new(
338            session,
339            recipe_factory(GuestFile::new(
340                "C:\\Users\\John\\Desktop\\test.txt",
341                content,
342            )),
343        )?
344        .with_pid(explorer_pid)
345    })?;
346
347    Ok(())
348}
examples/basic-process-list.rs (line 51)
13fn main() -> Result<(), Error> {
14    // Setup VMI.
15    let driver = VmiXenDriver::<Amd64>::try_from_env()?
16        .context("invalid VMI_XEN_DOMAIN environment variable")?;
17    let core = VmiCore::new(driver)?;
18
19    // Try to find the kernel information.
20    // This is necessary in order to load the profile.
21    let kernel_info = {
22        // Pause the VM to get consistent state.
23        let _pause_guard = core.pause_guard()?;
24
25        // Get the register state for the first vCPU.
26        let registers = core.registers(VcpuId(0))?;
27
28        // On AMD64 architecture, the kernel is usually found using the
29        // `MSR_LSTAR` register, which contains the address of the system call
30        // handler. This register is set by the operating system during boot
31        // and is left unchanged (unless some rootkits are involved).
32        //
33        // Therefore, we can take an arbitrary registers at any point in time
34        // (as long as the OS has booted and the page tables are set up) and
35        // use them to find the kernel.
36        WindowsOs::find_kernel(&core, &registers)?.expect("kernel information")
37    };
38
39    // Load the profile.
40    // The profile contains offsets to kernel functions and data structures.
41    let isr = IsrCache::new("cache")?;
42    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
43    let profile = entry.profile()?;
44
45    // Create the VMI session.
46    tracing::info!("Creating VMI session");
47    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
48    let session = VmiSession::new(&core, &os);
49
50    // Pause the VM again to get consistent state.
51    let paused = session.pause_guard()?;
52
53    // Create a new `VmiState` with the boot CPU registers.
54    let vmi = paused.state();
55
56    // Get the list of processes and print them.
57    for process in vmi.os().processes()? {
58        let process = process?;
59
60        println!(
61            "{} [{}] {} (root @ {})",
62            process.object()?,
63            process.id()?,
64            process.name()?,
65            process.translation_root()?
66        );
67    }
68
69    Ok(())
70}
examples/windows-reactor/main.rs (line 302)
244fn main() -> Result<(), Error> {
245    let filter = EnvFilter::default()
246        .add_directive(tracing::Level::DEBUG.into())
247        .add_directive("reqwest=warn".parse()?)
248        .add_directive("rustls=warn".parse()?);
249
250    tracing_subscriber::fmt()
251        .with_env_filter(filter)
252        .with_target(false)
253        .init();
254
255    // Setup VMI.
256    let driver = VmiXenDriver::<Amd64>::try_from_env()?
257        .context("invalid VMI_XEN_DOMAIN environment variable")?;
258    let core = VmiCore::new(driver)?;
259
260    // Try to find the kernel information.
261    // This is necessary in order to load the profile.
262    let kernel_info = {
263        let _pause_guard = core.pause_guard()?;
264        let registers = core.registers(VcpuId(0))?;
265
266        WindowsOs::find_kernel(&core, &registers)?.context("cannot find kernel information")?
267    };
268
269    // Load the kernel profile.
270    // The profile contains offsets to kernel functions and data structures.
271    tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
272    let isr = IsrCache::new("cache")?;
273    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
274    let profile = entry.profile()?;
275
276    // Create the VMI session.
277    tracing::info!("creating VMI session");
278    let terminate_flag = Arc::new(AtomicBool::new(false));
279    signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
280    signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
281    signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
282    signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
283
284    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
285    let session = VmiSession::new(&core, &os);
286
287    let handler = NetIo::default();
288
289    //
290    // The following `let ncrypt_* = ...` lines demonstrate how to manually
291    // resolve a module, load its profile (symbols) and add it to the resolver
292    // via `with_module(_in_process)`.
293    //
294    // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
295    // will automatically resolve modules if they are not explicitly added.
296    //
297    // Manually resolving modules can be useful in cases where you want to deal
298    // with the resolved information (base address, profile) in other places.
299    //
300
301    let ncrypt_resolved = {
302        let paused = session.pause_guard()?;
303        let vmi = paused.state();
304
305        // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
306        // would also work, but this demonstrates how to use a custom predicate.
307        //
308        // Also, `match_lsass` is more strict, because it specifically looks
309        // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
310        // positives or potential malicious processes).
311        vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
312            .context("ncrypt.dll not found in lsass.exe")?
313    };
314
315    let ncrypt_entry = isr
316        .entry_from_codeview(ncrypt_resolved.debug_signature)
317        .context("cannot find symbols for ncrypt.dll")?;
318
319    let ncrypt_profile = ncrypt_entry
320        .profile()
321        .context("cannot load profile for ncrypt.dll")?;
322
323    // The `SymbolCache` holds the resolved `isr::Entry` items.
324    let mut cache = SymbolCache::default();
325    let modules = ModuleResolver::default()
326        // `with_kernel` MUST be called if `Event` variants reference kernel
327        // symbols - like `NtWriteFile` in this example.
328        //
329        // This is because the "kernel" module is always optional.
330        .with_kernel(kernel_info.base_address, profile)
331        .with_module_in_process(
332            Module::NcryptDll,
333            ncrypt_resolved.process,
334            ncrypt_resolved.image_base,
335            ncrypt_profile,
336        )
337        // This will automatically resolve the `netio.sys` module and load
338        // its profile.
339        //
340        // Note that if we hadn't called `with_module_in_process` for
341        // `ncrypt.dll`, it would also be automatically resolved here.
342        .resolve(&session, &isr, &mut cache)?;
343
344    // Finally, we collect the events according to the resolved information
345    // and the metadata.
346    //
347    // For example, if some module/event is marked as `optional` and the
348    // resolver fails to resolve it, then it will simply not be included
349    // in the `events`.
350    let events = modules.into_events()?;
351
352    // And we're ready to create the reactor!
353    session.handle(|session| {
354        Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
355    })?;
356
357    Ok(())
358}
examples/windows-breakpoint-manager.rs (line 187)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }

Methods from Deref<Target = VmiCore<<Os as VmiOs>::Driver>>§

Source

pub fn driver(&self) -> &Driver

Available on crate features injector and utils only.

Returns the driver used by this VmiCore instance.

Source

pub fn info(&self) -> Result<VmiInfo, VmiError>

Available on crate features injector and utils only.

Retrieves information about the virtual machine.

Examples found in repository?
examples/basic.rs (line 17)
9fn main() -> Result<(), Error> {
10    // Setup VMI.
11    let driver = VmiXenDriver::<Amd64>::try_from_env()?
12        .context("invalid VMI_XEN_DOMAIN environment variable")?;
13    let vmi = VmiCore::new(driver)?;
14
15    // Get the interrupt descriptor table for each vCPU and print it.
16    let _pause_guard = vmi.pause_guard()?;
17    let info = vmi.info()?;
18    for vcpu_id in 0..info.vcpus {
19        let registers = vmi.registers(VcpuId(vcpu_id))?;
20        let idt = Amd64::interrupt_descriptor_table(&vmi, &registers)?;
21
22        println!("IDT[{vcpu_id}]: {idt:#?}");
23    }
24
25    Ok(())
26}
Source

pub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage>

Available on crate features injector and utils only.

Removes a specific entry from the GFN cache.

Returns the removed entry if it was present. This is useful for invalidating cached data that might have become stale.

Source

pub fn flush_gfn_cache(&self)

Available on crate features injector and utils only.

Clears the entire GFN cache.

Source

pub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa>

Available on crate features injector and utils only.

Removes a specific entry from the V2P cache.

Returns the removed entry if it was present. This can be used to invalidate cached translations that may have become stale due to changes in the guest’s memory mapping.

Source

pub fn flush_v2p_cache(&self)

Available on crate features injector and utils only.

Clears the entire V2P cache.

This method is crucial for maintaining consistency when handling events. The guest operating system can modify page tables or other structures related to address translation between events. Using stale translations can lead to incorrect memory access and unexpected behavior. It is recommended to call this method at the beginning of each VmiHandler::handle_event loop to ensure that you are working with the most up-to-date address mappings.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 510)
508    fn handle_event(&mut self, vmi: VmiContext<WindowsOs<Driver>>) -> VmiEventResponse<Amd64> {
509        // Flush the V2P cache on every event to avoid stale translations.
510        vmi.flush_v2p_cache();
511
512        self.dispatch(&vmi).expect("dispatch")
513    }
Source

pub fn read_string_length_limit(&self) -> Option<usize>

Available on crate features injector and utils only.

Returns the current limit on the length of strings read by the read_string methods.

Source

pub fn set_read_string_length_limit(&self, limit: usize)

Available on crate features injector and utils only.

Sets a limit on the length of strings read by the read_string methods.

This method allows you to set a maximum length (in bytes) for strings read from the virtual machine’s memory. When set, string reading operations will truncate their results to this limit. This can be useful for preventing excessively long string reads, which might impact performance or consume too much memory.

If the limit is reached during a string read operation, the resulting string will be truncated to the specified length.

To remove the limit, call this method with None.

Source

pub fn read( &self, ctx: impl Into<AccessContext>, buffer: &mut [u8], ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Reads memory from the virtual machine.

Source

pub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>

Available on crate features injector and utils only.

Reads a single byte from the virtual machine.

Source

pub fn read_u16(&self, ctx: impl Into<AccessContext>) -> Result<u16, VmiError>

Available on crate features injector and utils only.

Reads a 16-bit unsigned integer from the virtual machine.

Source

pub fn read_u32(&self, ctx: impl Into<AccessContext>) -> Result<u32, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit unsigned integer from the virtual machine.

Source

pub fn read_u64(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit unsigned integer from the virtual machine.

Source

pub fn read_uint( &self, ctx: impl Into<AccessContext>, size: usize, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an unsigned integer of the specified size from the virtual machine.

This method reads an unsigned integer of the specified size (in bytes) from the virtual machine. Note that the size must be 1, 2, 4, or 8.

The result is returned as a u64 to accommodate the widest possible integer size.

Source

pub fn read_field( &self, ctx: impl Into<AccessContext>, field: &Field, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a field of a structure from the virtual machine.

This method reads a field from the virtual machine. The field is defined by the provided Field structure, which specifies the offset and size of the field within the memory region.

The result is returned as a u64 to accommodate the widest possible integer size.

Source

pub fn read_address( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads an address-sized unsigned integer from the virtual machine.

This method reads an address of the specified width (in bytes) from the given access context. It’s useful when dealing with architectures that can operate in different address modes.

Source

pub fn read_address32( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit address from the virtual machine.

Source

pub fn read_address64( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit address from the virtual machine.

Source

pub fn read_va( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a virtual address from the virtual machine.

Source

pub fn read_va32(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a 32-bit virtual address from the virtual machine.

Source

pub fn read_va64(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>

Available on crate features injector and utils only.

Reads a 64-bit virtual address from the virtual machine.

Source

pub fn read_string_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u8>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string of bytes from the virtual machine with a specified limit.

Source

pub fn read_string_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u8>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string of bytes from the virtual machine.

Source

pub fn read_string_utf16_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u16>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.

Source

pub fn read_string_utf16_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u16>, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine.

Source

pub fn read_string_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string from the virtual machine with a specified limit.

Source

pub fn read_string( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated string from the virtual machine.

Source

pub fn read_string_utf16_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.

Source

pub fn read_string_utf16( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>

Available on crate features injector and utils only.

Reads a null-terminated wide string (UTF-16) from the virtual machine.

Source

pub fn read_struct<T>( &self, ctx: impl Into<AccessContext>, ) -> Result<T, VmiError>
where T: FromBytes + IntoBytes,

Available on crate features injector and utils only.

Reads a struct from the virtual machine.

Source

pub fn translate_address( &self, ctx: impl Into<AddressContext>, ) -> Result<Pa, VmiError>

Available on crate features injector and utils only.

Translates a virtual address to a physical address.

Source

pub fn translate_access_context( &self, ctx: AccessContext, ) -> Result<Pa, VmiError>

Available on crate features injector and utils only.

Translates an access context to a physical address.

Source

pub fn read_page(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError>

Available on crate features injector and utils only.

Reads a page of memory from the virtual machine.

Source

pub fn write( &self, ctx: impl Into<AccessContext>, buffer: &[u8], ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes memory to the virtual machine.

Source

pub fn write_u8( &self, ctx: impl Into<AccessContext>, value: u8, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a single byte to the virtual machine.

Source

pub fn write_u16( &self, ctx: impl Into<AccessContext>, value: u16, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a 16-bit unsigned integer to the virtual machine.

Source

pub fn write_u32( &self, ctx: impl Into<AccessContext>, value: u32, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a 32-bit unsigned integer to the virtual machine.

Source

pub fn write_u64( &self, ctx: impl Into<AccessContext>, value: u64, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Writes a 64-bit unsigned integer to the virtual machine.

Source

pub fn write_struct<T>( &self, ctx: impl Into<AccessContext>, value: T, ) -> Result<(), VmiError>
where T: IntoBytes + Immutable,

Available on crate features injector and utils only.

Writes a struct to the virtual machine.

Source

pub fn memory_access( &self, gfn: Gfn, view: View, ) -> Result<MemoryAccess, VmiError>

Available on crate features injector and utils only.

Retrieves the memory access permissions for a specific guest frame number (GFN).

The returned MemoryAccess indicates the current read, write, and execute permissions for the specified memory page in the given view.

Source

pub fn set_memory_access( &self, gfn: Gfn, view: View, access: MemoryAccess, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Sets the memory access permissions for a specific guest frame number (GFN).

This method allows you to modify the read, write, and execute permissions for a given memory page in the specified view.

Source

pub fn set_memory_access_with_options( &self, gfn: Gfn, view: View, access: MemoryAccess, options: MemoryAccessOptions, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Sets the memory access permissions for a specific guest frame number (GFN) with additional options.

In addition to the basic read, write, and execute permissions, this method allows you to specify additional options for the memory access.

Source

pub fn registers( &self, vcpu: VcpuId, ) -> Result<<<Driver as VmiDriver>::Architecture as Architecture>::Registers, VmiError>

Available on crate features injector and utils only.

Retrieves the current state of CPU registers for a specified virtual CPU.

This method allows you to access the current values of CPU registers, which is crucial for understanding the state of the virtual machine at a given point in time.

§Notes

The exact structure and content of the returned registers depend on the specific architecture of the VM being introspected. Refer to the documentation of your Architecture implementation for details on how to interpret the register values.

Examples found in repository?
examples/basic.rs (line 19)
9fn main() -> Result<(), Error> {
10    // Setup VMI.
11    let driver = VmiXenDriver::<Amd64>::try_from_env()?
12        .context("invalid VMI_XEN_DOMAIN environment variable")?;
13    let vmi = VmiCore::new(driver)?;
14
15    // Get the interrupt descriptor table for each vCPU and print it.
16    let _pause_guard = vmi.pause_guard()?;
17    let info = vmi.info()?;
18    for vcpu_id in 0..info.vcpus {
19        let registers = vmi.registers(VcpuId(vcpu_id))?;
20        let idt = Amd64::interrupt_descriptor_table(&vmi, &registers)?;
21
22        println!("IDT[{vcpu_id}]: {idt:#?}");
23    }
24
25    Ok(())
26}
More examples
Hide additional examples
examples/windows-dump.rs (line 412)
393fn main() -> Result<(), Box<dyn std::error::Error>> {
394    tracing_subscriber::fmt()
395        .with_max_level(tracing::Level::DEBUG)
396        .with_ansi(false)
397        .init();
398
399    // First argument is the path to the dump file.
400    let args = std::env::args().collect::<Vec<_>>();
401    if args.len() != 2 {
402        eprintln!("Usage: {} <dump-file>", args[0]);
403        std::process::exit(1);
404    }
405
406    let dump_file = &args[1];
407
408    // Setup VMI.
409    let driver = Driver::new(dump_file)?;
410    let core = VmiCore::new(driver)?;
411
412    let registers = core.registers(VcpuId(0))?;
413
414    // Try to find the kernel information.
415    // This is necessary in order to load the profile.
416    let kernel_info = WindowsOs::find_kernel(&core, &registers)?.expect("kernel information");
417    tracing::info!(?kernel_info, "Kernel information");
418
419    // Load the profile.
420    // The profile contains offsets to kernel functions and data structures.
421    let isr = IsrCache::new("cache")?;
422    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
423    let profile = entry.profile()?;
424
425    // Create the VMI session.
426    tracing::info!("Creating VMI session");
427    let os = WindowsOs::<Driver>::with_kernel_base(&profile, kernel_info.base_address)?;
428    let session = VmiSession::new(&core, &os);
429
430    let vmi = session.with_registers(&registers);
431    let root_directory = vmi.os().object_root_directory()?;
432
433    println!("Kernel Modules:");
434    println!("=================================================");
435    enumerate_kernel_modules(&vmi)?;
436
437    println!("Object Tree (root directory: {}):", root_directory.va());
438    println!("=================================================");
439    enumerate_directory_object(&root_directory, 0)?;
440
441    println!("Processes:");
442    println!("=================================================");
443    enumerate_processes(&vmi)?;
444
445    Ok(())
446}
examples/basic-process-list.rs (line 26)
13fn main() -> Result<(), Error> {
14    // Setup VMI.
15    let driver = VmiXenDriver::<Amd64>::try_from_env()?
16        .context("invalid VMI_XEN_DOMAIN environment variable")?;
17    let core = VmiCore::new(driver)?;
18
19    // Try to find the kernel information.
20    // This is necessary in order to load the profile.
21    let kernel_info = {
22        // Pause the VM to get consistent state.
23        let _pause_guard = core.pause_guard()?;
24
25        // Get the register state for the first vCPU.
26        let registers = core.registers(VcpuId(0))?;
27
28        // On AMD64 architecture, the kernel is usually found using the
29        // `MSR_LSTAR` register, which contains the address of the system call
30        // handler. This register is set by the operating system during boot
31        // and is left unchanged (unless some rootkits are involved).
32        //
33        // Therefore, we can take an arbitrary registers at any point in time
34        // (as long as the OS has booted and the page tables are set up) and
35        // use them to find the kernel.
36        WindowsOs::find_kernel(&core, &registers)?.expect("kernel information")
37    };
38
39    // Load the profile.
40    // The profile contains offsets to kernel functions and data structures.
41    let isr = IsrCache::new("cache")?;
42    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
43    let profile = entry.profile()?;
44
45    // Create the VMI session.
46    tracing::info!("Creating VMI session");
47    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
48    let session = VmiSession::new(&core, &os);
49
50    // Pause the VM again to get consistent state.
51    let paused = session.pause_guard()?;
52
53    // Create a new `VmiState` with the boot CPU registers.
54    let vmi = paused.state();
55
56    // Get the list of processes and print them.
57    for process in vmi.os().processes()? {
58        let process = process?;
59
60        println!(
61            "{} [{}] {} (root @ {})",
62            process.object()?,
63            process.id()?,
64            process.name()?,
65            process.translation_root()?
66        );
67    }
68
69    Ok(())
70}
examples/common/mod.rs (line 32)
9pub fn create_vmi_session() -> Result<VmiSession<'static, WindowsOs<VmiXenDriver<Amd64>>>, Error> {
10    let filter = EnvFilter::default()
11        .add_directive(tracing::Level::DEBUG.into())
12        .add_directive("reqwest=warn".parse()?)
13        .add_directive("rustls=warn".parse()?);
14
15    tracing_subscriber::fmt()
16        .with_env_filter(filter)
17        .with_target(false)
18        .init();
19
20    // Setup VMI.
21    let driver = VmiXenDriver::<Amd64>::try_from_env()?
22        .context("invalid VMI_XEN_DOMAIN environment variable")?;
23    let core = VmiCore::new(driver)?;
24
25    // Try to find the kernel information.
26    // This is necessary in order to load the profile.
27    let kernel_info = {
28        // Pause the vCPU to get consistent state.
29        let _pause_guard = core.pause_guard()?;
30
31        // Get the register state for the first vCPU.
32        let registers = core.registers(VcpuId(0))?;
33
34        // On AMD64 architecture, the kernel is usually found using the
35        // `MSR_LSTAR` register, which contains the address of the system call
36        // handler. This register is set by the operating system during boot
37        // and is left unchanged (unless some rootkits are involved).
38        //
39        // Therefore, we can take an arbitrary registers at any point in time
40        // (as long as the OS has booted and the page tables are set up) and
41        // use them to find the kernel.
42        WindowsOs::find_kernel(&core, &registers)?.context("cannot find kernel information")?
43    };
44
45    // Load the profile.
46    // The profile contains offsets to kernel functions and data structures.
47    tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
48    let isr = IsrCache::new("cache")?;
49    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
50    let entry = Box::leak(Box::new(entry));
51    let profile = entry.profile()?;
52
53    // Create the VMI session.
54    tracing::info!("creating VMI session");
55    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
56
57    // Please don't do this in production code.
58    // This is only done for the sake of the example.
59    let core = Box::leak(Box::new(core));
60    let os = Box::leak(Box::new(os));
61
62    Ok(VmiSession::new(core, os))
63}
examples/windows-reactor/main.rs (line 264)
244fn main() -> Result<(), Error> {
245    let filter = EnvFilter::default()
246        .add_directive(tracing::Level::DEBUG.into())
247        .add_directive("reqwest=warn".parse()?)
248        .add_directive("rustls=warn".parse()?);
249
250    tracing_subscriber::fmt()
251        .with_env_filter(filter)
252        .with_target(false)
253        .init();
254
255    // Setup VMI.
256    let driver = VmiXenDriver::<Amd64>::try_from_env()?
257        .context("invalid VMI_XEN_DOMAIN environment variable")?;
258    let core = VmiCore::new(driver)?;
259
260    // Try to find the kernel information.
261    // This is necessary in order to load the profile.
262    let kernel_info = {
263        let _pause_guard = core.pause_guard()?;
264        let registers = core.registers(VcpuId(0))?;
265
266        WindowsOs::find_kernel(&core, &registers)?.context("cannot find kernel information")?
267    };
268
269    // Load the kernel profile.
270    // The profile contains offsets to kernel functions and data structures.
271    tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
272    let isr = IsrCache::new("cache")?;
273    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
274    let profile = entry.profile()?;
275
276    // Create the VMI session.
277    tracing::info!("creating VMI session");
278    let terminate_flag = Arc::new(AtomicBool::new(false));
279    signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
280    signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
281    signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
282    signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
283
284    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
285    let session = VmiSession::new(&core, &os);
286
287    let handler = NetIo::default();
288
289    //
290    // The following `let ncrypt_* = ...` lines demonstrate how to manually
291    // resolve a module, load its profile (symbols) and add it to the resolver
292    // via `with_module(_in_process)`.
293    //
294    // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
295    // will automatically resolve modules if they are not explicitly added.
296    //
297    // Manually resolving modules can be useful in cases where you want to deal
298    // with the resolved information (base address, profile) in other places.
299    //
300
301    let ncrypt_resolved = {
302        let paused = session.pause_guard()?;
303        let vmi = paused.state();
304
305        // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
306        // would also work, but this demonstrates how to use a custom predicate.
307        //
308        // Also, `match_lsass` is more strict, because it specifically looks
309        // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
310        // positives or potential malicious processes).
311        vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
312            .context("ncrypt.dll not found in lsass.exe")?
313    };
314
315    let ncrypt_entry = isr
316        .entry_from_codeview(ncrypt_resolved.debug_signature)
317        .context("cannot find symbols for ncrypt.dll")?;
318
319    let ncrypt_profile = ncrypt_entry
320        .profile()
321        .context("cannot load profile for ncrypt.dll")?;
322
323    // The `SymbolCache` holds the resolved `isr::Entry` items.
324    let mut cache = SymbolCache::default();
325    let modules = ModuleResolver::default()
326        // `with_kernel` MUST be called if `Event` variants reference kernel
327        // symbols - like `NtWriteFile` in this example.
328        //
329        // This is because the "kernel" module is always optional.
330        .with_kernel(kernel_info.base_address, profile)
331        .with_module_in_process(
332            Module::NcryptDll,
333            ncrypt_resolved.process,
334            ncrypt_resolved.image_base,
335            ncrypt_profile,
336        )
337        // This will automatically resolve the `netio.sys` module and load
338        // its profile.
339        //
340        // Note that if we hadn't called `with_module_in_process` for
341        // `ncrypt.dll`, it would also be automatically resolved here.
342        .resolve(&session, &isr, &mut cache)?;
343
344    // Finally, we collect the events according to the resolved information
345    // and the metadata.
346    //
347    // For example, if some module/event is marked as `optional` and the
348    // resolver fails to resolve it, then it will simply not be included
349    // in the `events`.
350    let events = modules.into_events()?;
351
352    // And we're ready to create the reactor!
353    session.handle(|session| {
354        Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
355    })?;
356
357    Ok(())
358}
examples/windows-breakpoint-manager.rs (line 88)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
239
240    #[tracing::instrument(skip_all)]
241    fn memory_access(
242        &mut self,
243        vmi: &VmiContext<WindowsOs<Driver>>,
244    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
245        let memory_access = vmi.event().reason().as_memory_access();
246
247        tracing::trace!(
248            pa = %memory_access.pa,
249            va = %memory_access.va,
250            access = %memory_access.access,
251        );
252
253        if memory_access.access.contains(MemoryAccess::W) {
254            // It is assumed that a write memory access event is caused by a
255            // page table modification.
256            //
257            // The page table entry is marked as dirty in the page table monitor
258            // and a singlestep is performed to process the dirty entries.
259            self.ptm
260                .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
261
262            Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
263        }
264        else if memory_access.access.contains(MemoryAccess::R) {
265            // When the guest tries to read from the memory, a fast-singlestep
266            // is performed over the instruction that tried to read the memory.
267            // This is done to allow the instruction to read the original memory
268            // content.
269            Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
270        }
271        else {
272            panic!("Unhandled memory access: {memory_access:?}");
273        }
274    }
275
276    #[tracing::instrument(skip_all, fields(pid, process))]
277    fn interrupt(
278        &mut self,
279        vmi: &VmiContext<WindowsOs<Driver>>,
280    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
281        let tag = match self.bpm.get_by_event(vmi.event(), ()) {
282            Some(breakpoint) => breakpoint.tag(),
283            None => {
284                if BreakpointController::is_breakpoint(vmi, vmi.event())? {
285                    // This breakpoint was not set by us. Reinject it.
286                    tracing::warn!("Unknown breakpoint, reinjecting");
287                    return Ok(VmiEventResponse::reinject_interrupt());
288                }
289                else {
290                    // We have received a breakpoint event, but there is no
291                    // breakpoint instruction at the current memory location.
292                    // This can happen if the event was triggered by a breakpoint
293                    // we just removed.
294                    tracing::warn!("Ignoring old breakpoint event");
295                    return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
296                }
297            }
298        };
299
300        let process = vmi.os().current_process()?;
301        let process_id = process.id()?;
302        let process_name = process.name()?;
303        tracing::Span::current()
304            .record("pid", process_id.0)
305            .record("process", process_name);
306
307        match tag {
308            "NtCreateFile" => self.NtCreateFile(vmi)?,
309            "NtWriteFile" => self.NtWriteFile(vmi)?,
310            "PspInsertProcess" => self.PspInsertProcess(vmi)?,
311            "MmCleanProcessAddressSpace" => self.MmCleanProcessAddressSpace(vmi)?,
312            _ => panic!("Unhandled tag: {tag}"),
313        }
314
315        Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
316    }
317
318    #[tracing::instrument(skip_all)]
319    fn singlestep(
320        &mut self,
321        vmi: &VmiContext<WindowsOs<Driver>>,
322    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
323        // Get the page table modifications by processing the dirty page table
324        // entries.
325        let ptm_events = self.ptm.process_dirty_entries(vmi, vmi.event().vcpu_id())?;
326
327        // Let the breakpoint controller handle the page table modifications.
328        self.bpm.handle_ptm_events(vmi, ptm_events)?;
329
330        // Disable singlestep and switch back to our view.
331        Ok(VmiEventResponse::default().with_view(self.view))
332    }
333
334    #[tracing::instrument(skip_all)]
335    fn NtCreateFile(&mut self, vmi: &VmiContext<WindowsOs<Driver>>) -> Result<(), VmiError> {
336        //
337        // NTSTATUS
338        // NtCreateFile (
339        //     _Out_ PHANDLE FileHandle,
340        //     _In_ ACCESS_MASK DesiredAccess,
341        //     _In_ POBJECT_ATTRIBUTES ObjectAttributes,
342        //     _Out_ PIO_STATUS_BLOCK IoStatusBlock,
343        //     _In_opt_ PLARGE_INTEGER AllocationSize,
344        //     _In_ ULONG FileAttributes,
345        //     _In_ ULONG ShareAccess,
346        //     _In_ ULONG CreateDisposition,
347        //     _In_ ULONG CreateOptions,
348        //     _In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
349        //     _In_ ULONG EaLength
350        //     );
351        //
352
353        let ObjectAttributes = Va(vmi.os().function_argument(2)?);
354
355        let object_attributes = vmi.os().object_attributes(ObjectAttributes)?;
356        let object_name = match object_attributes.object_name()? {
357            Some(object_name) => object_name,
358            None => {
359                tracing::warn!(%ObjectAttributes, "No object name found");
360                return Ok(());
361            }
362        };
363
364        tracing::info!(%object_name);
365
366        Ok(())
367    }
368
369    #[tracing::instrument(skip_all)]
370    fn NtWriteFile(&mut self, vmi: &VmiContext<WindowsOs<Driver>>) -> Result<(), VmiError> {
371        //
372        // NTSTATUS
373        // NtWriteFile (
374        //     _In_ HANDLE FileHandle,
375        //     _In_opt_ HANDLE Event,
376        //     _In_opt_ PIO_APC_ROUTINE ApcRoutine,
377        //     _In_opt_ PVOID ApcContext,
378        //     _Out_ PIO_STATUS_BLOCK IoStatusBlock,
379        //     _In_reads_bytes_(Length) PVOID Buffer,
380        //     _In_ ULONG Length,
381        //     _In_opt_ PLARGE_INTEGER ByteOffset,
382        //     _In_opt_ PULONG Key
383        //     );
384        //
385
386        let FileHandle = vmi.os().function_argument(0)?;
387
388        let file_object = match vmi
389            .os()
390            .current_process()?
391            .lookup_object::<WindowsFileObject<_>>(FileHandle)?
392        {
393            Some(file_object) => file_object,
394            None => {
395                tracing::warn!(FileHandle = %Hex(FileHandle), "No object found");
396                return Ok(());
397            }
398        };
399
400        let path = file_object.full_path()?;
401        tracing::info!(%path);
402
403        Ok(())
404    }
405
406    #[tracing::instrument(skip_all)]
407    fn PspInsertProcess(&mut self, vmi: &VmiContext<WindowsOs<Driver>>) -> Result<(), VmiError> {
408        //
409        // NTSTATUS
410        // PspInsertProcess (
411        //     _In_ PEPROCESS NewProcess,
412        //     _In_ PEPROCESS Parent,
413        //     _In_ ULONG DesiredAccess,
414        //     _In_ ULONG CreateFlags,
415        //     ...
416        //     );
417        //
418
419        let NewProcess = vmi.os().function_argument(0)?;
420        let Parent = vmi.os().function_argument(1)?;
421
422        let process = vmi.os().process(ProcessObject(Va(NewProcess)))?;
423        let process_id = process.id()?;
424
425        let parent_process = vmi.os().process(ProcessObject(Va(Parent)))?;
426        let parent_process_id = parent_process.id()?;
427
428        // We rely heavily on the 2nd argument to be the parent process object.
429        // If that ever changes, this assertion should catch it.
430        //
431        // So far it is verified that it works for Windows 7 up to Windows 11
432        // (23H2, build 22631).
433        debug_assert_eq!(parent_process_id, process.parent_id()?);
434
435        let name = process.name()?;
436        let image_base = process.image_base()?;
437        let peb = process.peb()?;
438
439        tracing::info!(
440            %process_id,
441            name,
442            %image_base,
443            ?peb,
444        );
445
446        Ok(())
447    }
448
449    #[tracing::instrument(skip_all)]
450    fn MmCleanProcessAddressSpace(
451        &mut self,
452        vmi: &VmiContext<WindowsOs<Driver>>,
453    ) -> Result<(), VmiError> {
454        //
455        // VOID
456        // MmCleanProcessAddressSpace (
457        //     _In_ PEPROCESS Process
458        //     );
459        //
460
461        let Process = vmi.os().function_argument(0)?;
462
463        let process = vmi.os().process(ProcessObject(Va(Process)))?;
464        let process_id = process.id()?;
465
466        let name = process.name()?;
467        let image_base = process.image_base()?;
468
469        tracing::info!(%process_id, name, %image_base);
470
471        Ok(())
472    }
473
474    fn dispatch(
475        &mut self,
476        vmi: &VmiContext<WindowsOs<Driver>>,
477    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
478        let event = vmi.event();
479        let result = match event.reason() {
480            EventReason::MemoryAccess(_) => self.memory_access(vmi),
481            EventReason::Interrupt(_) => self.interrupt(vmi),
482            EventReason::Singlestep(_) => self.singlestep(vmi),
483            _ => panic!("Unhandled event: {:?}", event.reason()),
484        };
485
486        // If VMI tries to read from a page that is not present, it will return
487        // a page fault error. In this case, we inject a page fault interrupt
488        // to the guest.
489        //
490        // Once the guest handles the page fault, it will retry to execute the
491        // instruction that caused the page fault.
492        if let Err(VmiError::Translation(pf)) = result {
493            tracing::warn!(?pf, "Page fault, injecting");
494            vmi.inject_interrupt(event.vcpu_id(), Interrupt::page_fault(pf.va, 0))?;
495            return Ok(VmiEventResponse::default());
496        }
497
498        result
499    }
500}
501
502impl<Driver> VmiHandler<WindowsOs<Driver>> for Monitor<Driver>
503where
504    Driver: VmiFullDriver<Architecture = Amd64>,
505{
506    type Output = ();
507
508    fn handle_event(&mut self, vmi: VmiContext<WindowsOs<Driver>>) -> VmiEventResponse<Amd64> {
509        // Flush the V2P cache on every event to avoid stale translations.
510        vmi.flush_v2p_cache();
511
512        self.dispatch(&vmi).expect("dispatch")
513    }
514
515    fn poll(&self) -> Option<Self::Output> {
516        self.terminate_flag.load(Ordering::Relaxed).then_some(())
517    }
518}
519
520fn main() -> Result<(), Error> {
521    tracing_subscriber::fmt()
522        .with_max_level(tracing::Level::DEBUG)
523        .init();
524
525    // Setup VMI.
526    let driver = VmiXenDriver::<Amd64>::try_from_env()?
527        .context("invalid VMI_XEN_DOMAIN environment variable")?;
528    let core = VmiCore::new(driver)?;
529
530    // Try to find the kernel information.
531    // This is necessary in order to load the profile.
532    let kernel_info = {
533        let _pause_guard = core.pause_guard()?;
534        let regs = core.registers(0.into())?;
535
536        WindowsOs::find_kernel(&core, &regs)?.expect("kernel information")
537    };
538
539    // Load the profile.
540    // The profile contains offsets to kernel functions and data structures.
541    let isr = IsrCache::new("cache")?;
542    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
543    let profile = entry.profile()?;
544
545    // Create the VMI session.
546    tracing::info!("Creating VMI session");
547    let terminate_flag = Arc::new(AtomicBool::new(false));
548    signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
549    signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
550    signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
551    signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
552
553    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
554    let session = VmiSession::new(&core, &os);
555
556    session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
557
558    Ok(())
559}
Source

pub fn set_registers( &self, vcpu: VcpuId, registers: <<Driver as VmiDriver>::Architecture as Architecture>::Registers, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Sets the registers of a virtual CPU.

Source

pub fn default_view(&self) -> View

Available on crate features injector and utils only.

Returns the default view for the virtual machine.

The default view typically represents the normal, unmodified state of the VM’s memory.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 262)
241    fn memory_access(
242        &mut self,
243        vmi: &VmiContext<WindowsOs<Driver>>,
244    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
245        let memory_access = vmi.event().reason().as_memory_access();
246
247        tracing::trace!(
248            pa = %memory_access.pa,
249            va = %memory_access.va,
250            access = %memory_access.access,
251        );
252
253        if memory_access.access.contains(MemoryAccess::W) {
254            // It is assumed that a write memory access event is caused by a
255            // page table modification.
256            //
257            // The page table entry is marked as dirty in the page table monitor
258            // and a singlestep is performed to process the dirty entries.
259            self.ptm
260                .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
261
262            Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
263        }
264        else if memory_access.access.contains(MemoryAccess::R) {
265            // When the guest tries to read from the memory, a fast-singlestep
266            // is performed over the instruction that tried to read the memory.
267            // This is done to allow the instruction to read the original memory
268            // content.
269            Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
270        }
271        else {
272            panic!("Unhandled memory access: {memory_access:?}");
273        }
274    }
275
276    #[tracing::instrument(skip_all, fields(pid, process))]
277    fn interrupt(
278        &mut self,
279        vmi: &VmiContext<WindowsOs<Driver>>,
280    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
281        let tag = match self.bpm.get_by_event(vmi.event(), ()) {
282            Some(breakpoint) => breakpoint.tag(),
283            None => {
284                if BreakpointController::is_breakpoint(vmi, vmi.event())? {
285                    // This breakpoint was not set by us. Reinject it.
286                    tracing::warn!("Unknown breakpoint, reinjecting");
287                    return Ok(VmiEventResponse::reinject_interrupt());
288                }
289                else {
290                    // We have received a breakpoint event, but there is no
291                    // breakpoint instruction at the current memory location.
292                    // This can happen if the event was triggered by a breakpoint
293                    // we just removed.
294                    tracing::warn!("Ignoring old breakpoint event");
295                    return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
296                }
297            }
298        };
299
300        let process = vmi.os().current_process()?;
301        let process_id = process.id()?;
302        let process_name = process.name()?;
303        tracing::Span::current()
304            .record("pid", process_id.0)
305            .record("process", process_name);
306
307        match tag {
308            "NtCreateFile" => self.NtCreateFile(vmi)?,
309            "NtWriteFile" => self.NtWriteFile(vmi)?,
310            "PspInsertProcess" => self.PspInsertProcess(vmi)?,
311            "MmCleanProcessAddressSpace" => self.MmCleanProcessAddressSpace(vmi)?,
312            _ => panic!("Unhandled tag: {tag}"),
313        }
314
315        Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
316    }
Source

pub fn create_view( &self, default_access: MemoryAccess, ) -> Result<View, VmiError>

Available on crate features injector and utils only.

Creates a new view with the specified default access permissions.

Views allow for creating different perspectives of the VM’s memory, which can be useful for analysis or isolation purposes. The default access permissions apply to memory pages not explicitly modified within this view.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 124)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
Source

pub fn destroy_view(&self, view: View) -> Result<(), VmiError>

Available on crate features injector and utils only.

Destroys a previously created view.

This method removes a view and frees associated resources. It should be called when a view is no longer needed to prevent resource leaks.

Source

pub fn switch_to_view(&self, view: View) -> Result<(), VmiError>

Available on crate features injector and utils only.

Switches to a different view for all virtual CPUs.

This method changes the current active view for all vCPUs, affecting subsequent memory operations across the entire VM. It allows for quick transitions between different memory perspectives globally.

Note the difference between this method and VmiEventResponse::with_view():

  • switch_to_view() changes the view for all vCPUs immediately.
  • VmiEventResponse::with_view() sets the view only for the specific vCPU that received the event, and the change is applied when the event handler returns.

Use switch_to_view() for global view changes, and VmiEventResponse::with_view() for targeted, event-specific view modifications on individual vCPUs.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 125)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
Source

pub fn change_view_gfn( &self, view: View, old_gfn: Gfn, new_gfn: Gfn, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Changes the mapping of a guest frame number (GFN) in a specific view.

This method allows for remapping a GFN to a different physical frame within a view, enabling fine-grained control over memory layout in different views.

A notable use case for this method is implementing “stealth hooks”:

  1. Create a new GFN and copy the contents of the original page to it.
  2. Modify the new page by installing a breakpoint (e.g., 0xcc on AMD64) at a strategic location.
  3. Use this method to change the mapping of the original GFN to the new one.
  4. Set the memory access of the new GFN to non-readable.

When a read access occurs:

  • The handler should enable single-stepping.
  • Switch to an unmodified view (e.g., default_view) to execute the read instruction, which will read the original non-breakpoint byte.
  • Re-enable single-stepping afterwards.

This technique allows for transparent breakpoints that are difficult to detect by the guest OS or applications.

Source

pub fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError>

Available on crate features injector and utils only.

Resets the mapping of a guest frame number (GFN) in a specific view to its original state.

This method reverts any custom mapping for the specified GFN in the given view, restoring it to the default mapping.

Source

pub fn monitor_enable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Enables monitoring of specific events.

This method allows you to enable monitoring of specific events, such as control register writes, interrupts, or single-step execution. Monitoring events can be useful for tracking specific guest behavior or for implementing custom analysis tools.

The type of event to monitor is defined by the architecture-specific Architecture::EventMonitor type.

When an event occurs, it will be passed to the event callback function for processing.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 119)
67    pub fn new(
68        session: &VmiSession<WindowsOs<Driver>>,
69        profile: &Profile,
70        terminate_flag: Arc<AtomicBool>,
71    ) -> Result<Self, VmiError> {
72        // Capture the current state of the vCPU and get the base address of
73        // the kernel.
74        //
75        // This base address is essential to correctly offset monitored
76        // functions.
77        //
78        // NOTE: `kernel_image_base` tries to find the kernel in the memory
79        //       with the help of the CPU registers. On AMD64 architecture,
80        //       the kernel image base is usually found using the `MSR_LSTAR`
81        //       register, which contains the address of the system call
82        //       handler. This register is set by the operating system during
83        //       boot and is left unchanged (unless some rootkits are involved).
84        //
85        //       Therefore, we can take an arbitrary registers at any point
86        //       in time (as long as the OS has booted and the page tables are
87        //       set up) and use them to find the kernel image base.
88        let registers = session.registers(VcpuId(0))?;
89        let vmi = session.with_registers(&registers);
90
91        let kernel_image_base = vmi.os().kernel_image_base()?;
92        tracing::info!(%kernel_image_base);
93
94        // Get the system process.
95        //
96        // The system process is the first process created by the kernel.
97        // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98        // To monitor page table entries, we need to locate the translation root
99        // of this process.
100        let system_process = vmi.os().system_process()?;
101        tracing::info!(system_process = %system_process.object()?);
102
103        // Get the translation root of the system process.
104        // This is effectively "the CR3 of the kernel".
105        //
106        // The translation root is the root of the page table hierarchy (also
107        // known as the Directory Table Base or PML4).
108        let root = system_process.translation_root()?;
109        tracing::info!(%root);
110
111        // Load the symbols from the profile.
112        let symbols = Symbols::new(profile)?;
113
114        // Enable monitoring of the INT3 and singlestep events.
115        //
116        // INT3 is used to monitor the execution of specific functions.
117        // Singlestep is used to monitor the modifications of page table
118        // entries.
119        vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120        vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122        // Create a new view for the monitor.
123        // This view is used for monitoring function calls and memory accesses.
124        let view = vmi.create_view(MemoryAccess::RWX)?;
125        vmi.switch_to_view(view)?;
126
127        // Create a new breakpoint controller.
128        //
129        // The breakpoint controller is used to insert breakpoints for specific
130        // functions.
131        //
132        // From the guest's perspective, these breakpoints are "hidden", since
133        // the breakpoint controller will unset the read/write access to the
134        // physical memory page where the breakpoint is inserted, while keeping
135        // the execute access.
136        //
137        // This way, the guest will be able to execute the code, but attempts to
138        // read or write the memory will trigger the `memory_access` callback.
139        //
140        // When a vCPU tries to execute the breakpoint instruction:
141        // - an `interrupt` callback will be triggered
142        // - the breakpoint will be handled (e.g., log the function call)
143        // - a fast-singlestep[1] will be performed over the INT3 instruction
144        //
145        // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146        // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147        //   access type)
148        // - fast-singlestep[1] will be performed over the instruction that tried to
149        //   read the memory
150        //
151        // This way, the instruction will read the original memory content.
152        //
153        // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154        //     to a different view, execute a single instruction, and then
155        //     switch back to the original view. In this case, the view is
156        //     switched to the `default_view` (which is unmodified).
157        let mut bpm = BreakpointManager::new();
158
159        // Create a new page table monitor.
160        //
161        // The page table monitor is used to monitor the page table entries of
162        // the hooked functions.
163        //
164        // More specifically, it is used to monitor the pages that the breakpoint
165        // was inserted into. This is necessary to handle the case when the
166        // page containing the breakpoint is paged out (and then paged in
167        // again).
168        //
169        // `PageTableMonitor` works by unsetting the write access to the page
170        // tables of the hooked functions. When the page is paged out, the
171        // `PRESENT` bit in the page table entry is unset and, conversely, when
172        // the page is paged in, the `PRESENT` bit is set again.
173        //
174        // When that happens:
175        // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176        //   access type)
177        // - the callback will mark the page as dirty in the page table monitor
178        // - a singlestep will be performed over the instruction that tried to modify
179        //   the memory containing the page table entry
180        // - the `singlestep` handler will process the dirty page table entries and
181        //   inform the breakpoint controller to handle the changes
182        let mut ptm = PageTableMonitor::new();
183
184        // Pause the VM to avoid race conditions between inserting breakpoints
185        // and monitoring page table entries. The VM resumes when the pause
186        // guard is dropped.
187        let _pause_guard = vmi.pause_guard()?;
188
189        // Insert breakpoint for the `NtCreateFile` function.
190        let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191        let cx_NtCreateFile = (va_NtCreateFile, root);
192        let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193            .global()
194            .with_tag("NtCreateFile");
195        bpm.insert(&vmi, bp_NtCreateFile)?;
196        ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197        tracing::info!(%va_NtCreateFile);
198
199        // Insert breakpoint for the `NtWriteFile` function.
200        let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201        let cx_NtWriteFile = (va_NtWriteFile, root);
202        let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203            .global()
204            .with_tag("NtWriteFile");
205        bpm.insert(&vmi, bp_NtWriteFile)?;
206        ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207        tracing::info!(%va_NtWriteFile);
208
209        // Insert breakpoint for the `PspInsertProcess` function.
210        let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211        let cx_PspInsertProcess = (va_PspInsertProcess, root);
212        let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213            .global()
214            .with_tag("PspInsertProcess");
215        bpm.insert(&vmi, bp_PspInsertProcess)?;
216        ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218        // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219        let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220        let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221        let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222            .global()
223            .with_tag("MmCleanProcessAddressSpace");
224        bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225        ptm.monitor(
226            &vmi,
227            cx_MmCleanProcessAddressSpace,
228            view,
229            "MmCleanProcessAddressSpace",
230        )?;
231
232        Ok(Self {
233            terminate_flag,
234            view,
235            bpm,
236            ptm,
237        })
238    }
Source

pub fn monitor_disable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Disables monitoring of specific events.

This method allows you to disable monitoring of specific events that were previously enabled. It can be used to stop tracking certain hardware events or to reduce the overhead of event processing.

The type of event to disable is defined by the architecture-specific Architecture::EventMonitor type.

Source

pub fn events_pending(&self) -> usize

Available on crate features injector and utils only.

Returns the number of pending events.

This method provides a count of events that have occurred but have not yet been processed.

Source

pub fn event_processing_overhead(&self) -> Duration

Available on crate features injector and utils only.

Returns the time spent processing events by the driver.

This method provides a measure of the overhead introduced by event processing. It can be useful for performance tuning and understanding the impact of VMI operations on overall system performance.

Source

pub fn wait_for_event( &self, timeout: Duration, handler: impl FnMut(&VmiEvent<<Driver as VmiDriver>::Architecture>) -> VmiEventResponse<<Driver as VmiDriver>::Architecture>, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Waits for an event to occur and processes it with the provided handler.

This method blocks until an event occurs or the specified timeout is reached. When an event occurs, it is passed to the provided callback function for processing.

Source

pub fn pause(&self) -> Result<(), VmiError>

Available on crate features injector and utils only.

Pauses the virtual machine.

Source

pub fn resume(&self) -> Result<(), VmiError>

Available on crate features injector and utils only.

Resumes the virtual machine.

Source

pub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError>

Available on crate features injector and utils only.

Pauses the virtual machine and returns a guard that will resume it when dropped.

Examples found in repository?
examples/basic.rs (line 16)
9fn main() -> Result<(), Error> {
10    // Setup VMI.
11    let driver = VmiXenDriver::<Amd64>::try_from_env()?
12        .context("invalid VMI_XEN_DOMAIN environment variable")?;
13    let vmi = VmiCore::new(driver)?;
14
15    // Get the interrupt descriptor table for each vCPU and print it.
16    let _pause_guard = vmi.pause_guard()?;
17    let info = vmi.info()?;
18    for vcpu_id in 0..info.vcpus {
19        let registers = vmi.registers(VcpuId(vcpu_id))?;
20        let idt = Amd64::interrupt_descriptor_table(&vmi, &registers)?;
21
22        println!("IDT[{vcpu_id}]: {idt:#?}");
23    }
24
25    Ok(())
26}
More examples
Hide additional examples
examples/windows-breakpoint-manager.rs (line 533)
520fn main() -> Result<(), Error> {
521    tracing_subscriber::fmt()
522        .with_max_level(tracing::Level::DEBUG)
523        .init();
524
525    // Setup VMI.
526    let driver = VmiXenDriver::<Amd64>::try_from_env()?
527        .context("invalid VMI_XEN_DOMAIN environment variable")?;
528    let core = VmiCore::new(driver)?;
529
530    // Try to find the kernel information.
531    // This is necessary in order to load the profile.
532    let kernel_info = {
533        let _pause_guard = core.pause_guard()?;
534        let regs = core.registers(0.into())?;
535
536        WindowsOs::find_kernel(&core, &regs)?.expect("kernel information")
537    };
538
539    // Load the profile.
540    // The profile contains offsets to kernel functions and data structures.
541    let isr = IsrCache::new("cache")?;
542    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
543    let profile = entry.profile()?;
544
545    // Create the VMI session.
546    tracing::info!("Creating VMI session");
547    let terminate_flag = Arc::new(AtomicBool::new(false));
548    signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
549    signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
550    signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
551    signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
552
553    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
554    let session = VmiSession::new(&core, &os);
555
556    session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
557
558    Ok(())
559}
examples/basic-process-list.rs (line 23)
13fn main() -> Result<(), Error> {
14    // Setup VMI.
15    let driver = VmiXenDriver::<Amd64>::try_from_env()?
16        .context("invalid VMI_XEN_DOMAIN environment variable")?;
17    let core = VmiCore::new(driver)?;
18
19    // Try to find the kernel information.
20    // This is necessary in order to load the profile.
21    let kernel_info = {
22        // Pause the VM to get consistent state.
23        let _pause_guard = core.pause_guard()?;
24
25        // Get the register state for the first vCPU.
26        let registers = core.registers(VcpuId(0))?;
27
28        // On AMD64 architecture, the kernel is usually found using the
29        // `MSR_LSTAR` register, which contains the address of the system call
30        // handler. This register is set by the operating system during boot
31        // and is left unchanged (unless some rootkits are involved).
32        //
33        // Therefore, we can take an arbitrary registers at any point in time
34        // (as long as the OS has booted and the page tables are set up) and
35        // use them to find the kernel.
36        WindowsOs::find_kernel(&core, &registers)?.expect("kernel information")
37    };
38
39    // Load the profile.
40    // The profile contains offsets to kernel functions and data structures.
41    let isr = IsrCache::new("cache")?;
42    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
43    let profile = entry.profile()?;
44
45    // Create the VMI session.
46    tracing::info!("Creating VMI session");
47    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
48    let session = VmiSession::new(&core, &os);
49
50    // Pause the VM again to get consistent state.
51    let paused = session.pause_guard()?;
52
53    // Create a new `VmiState` with the boot CPU registers.
54    let vmi = paused.state();
55
56    // Get the list of processes and print them.
57    for process in vmi.os().processes()? {
58        let process = process?;
59
60        println!(
61            "{} [{}] {} (root @ {})",
62            process.object()?,
63            process.id()?,
64            process.name()?,
65            process.translation_root()?
66        );
67    }
68
69    Ok(())
70}
examples/common/mod.rs (line 29)
9pub fn create_vmi_session() -> Result<VmiSession<'static, WindowsOs<VmiXenDriver<Amd64>>>, Error> {
10    let filter = EnvFilter::default()
11        .add_directive(tracing::Level::DEBUG.into())
12        .add_directive("reqwest=warn".parse()?)
13        .add_directive("rustls=warn".parse()?);
14
15    tracing_subscriber::fmt()
16        .with_env_filter(filter)
17        .with_target(false)
18        .init();
19
20    // Setup VMI.
21    let driver = VmiXenDriver::<Amd64>::try_from_env()?
22        .context("invalid VMI_XEN_DOMAIN environment variable")?;
23    let core = VmiCore::new(driver)?;
24
25    // Try to find the kernel information.
26    // This is necessary in order to load the profile.
27    let kernel_info = {
28        // Pause the vCPU to get consistent state.
29        let _pause_guard = core.pause_guard()?;
30
31        // Get the register state for the first vCPU.
32        let registers = core.registers(VcpuId(0))?;
33
34        // On AMD64 architecture, the kernel is usually found using the
35        // `MSR_LSTAR` register, which contains the address of the system call
36        // handler. This register is set by the operating system during boot
37        // and is left unchanged (unless some rootkits are involved).
38        //
39        // Therefore, we can take an arbitrary registers at any point in time
40        // (as long as the OS has booted and the page tables are set up) and
41        // use them to find the kernel.
42        WindowsOs::find_kernel(&core, &registers)?.context("cannot find kernel information")?
43    };
44
45    // Load the profile.
46    // The profile contains offsets to kernel functions and data structures.
47    tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
48    let isr = IsrCache::new("cache")?;
49    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
50    let entry = Box::leak(Box::new(entry));
51    let profile = entry.profile()?;
52
53    // Create the VMI session.
54    tracing::info!("creating VMI session");
55    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
56
57    // Please don't do this in production code.
58    // This is only done for the sake of the example.
59    let core = Box::leak(Box::new(core));
60    let os = Box::leak(Box::new(os));
61
62    Ok(VmiSession::new(core, os))
63}
examples/windows-reactor/main.rs (line 263)
244fn main() -> Result<(), Error> {
245    let filter = EnvFilter::default()
246        .add_directive(tracing::Level::DEBUG.into())
247        .add_directive("reqwest=warn".parse()?)
248        .add_directive("rustls=warn".parse()?);
249
250    tracing_subscriber::fmt()
251        .with_env_filter(filter)
252        .with_target(false)
253        .init();
254
255    // Setup VMI.
256    let driver = VmiXenDriver::<Amd64>::try_from_env()?
257        .context("invalid VMI_XEN_DOMAIN environment variable")?;
258    let core = VmiCore::new(driver)?;
259
260    // Try to find the kernel information.
261    // This is necessary in order to load the profile.
262    let kernel_info = {
263        let _pause_guard = core.pause_guard()?;
264        let registers = core.registers(VcpuId(0))?;
265
266        WindowsOs::find_kernel(&core, &registers)?.context("cannot find kernel information")?
267    };
268
269    // Load the kernel profile.
270    // The profile contains offsets to kernel functions and data structures.
271    tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
272    let isr = IsrCache::new("cache")?;
273    let entry = isr.entry_from_codeview(kernel_info.codeview)?;
274    let profile = entry.profile()?;
275
276    // Create the VMI session.
277    tracing::info!("creating VMI session");
278    let terminate_flag = Arc::new(AtomicBool::new(false));
279    signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
280    signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
281    signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
282    signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
283
284    let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
285    let session = VmiSession::new(&core, &os);
286
287    let handler = NetIo::default();
288
289    //
290    // The following `let ncrypt_* = ...` lines demonstrate how to manually
291    // resolve a module, load its profile (symbols) and add it to the resolver
292    // via `with_module(_in_process)`.
293    //
294    // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
295    // will automatically resolve modules if they are not explicitly added.
296    //
297    // Manually resolving modules can be useful in cases where you want to deal
298    // with the resolved information (base address, profile) in other places.
299    //
300
301    let ncrypt_resolved = {
302        let paused = session.pause_guard()?;
303        let vmi = paused.state();
304
305        // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
306        // would also work, but this demonstrates how to use a custom predicate.
307        //
308        // Also, `match_lsass` is more strict, because it specifically looks
309        // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
310        // positives or potential malicious processes).
311        vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
312            .context("ncrypt.dll not found in lsass.exe")?
313    };
314
315    let ncrypt_entry = isr
316        .entry_from_codeview(ncrypt_resolved.debug_signature)
317        .context("cannot find symbols for ncrypt.dll")?;
318
319    let ncrypt_profile = ncrypt_entry
320        .profile()
321        .context("cannot load profile for ncrypt.dll")?;
322
323    // The `SymbolCache` holds the resolved `isr::Entry` items.
324    let mut cache = SymbolCache::default();
325    let modules = ModuleResolver::default()
326        // `with_kernel` MUST be called if `Event` variants reference kernel
327        // symbols - like `NtWriteFile` in this example.
328        //
329        // This is because the "kernel" module is always optional.
330        .with_kernel(kernel_info.base_address, profile)
331        .with_module_in_process(
332            Module::NcryptDll,
333            ncrypt_resolved.process,
334            ncrypt_resolved.image_base,
335            ncrypt_profile,
336        )
337        // This will automatically resolve the `netio.sys` module and load
338        // its profile.
339        //
340        // Note that if we hadn't called `with_module_in_process` for
341        // `ncrypt.dll`, it would also be automatically resolved here.
342        .resolve(&session, &isr, &mut cache)?;
343
344    // Finally, we collect the events according to the resolved information
345    // and the metadata.
346    //
347    // For example, if some module/event is marked as `optional` and the
348    // resolver fails to resolve it, then it will simply not be included
349    // in the `events`.
350    let events = modules.into_events()?;
351
352    // And we're ready to create the reactor!
353    session.handle(|session| {
354        Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
355    })?;
356
357    Ok(())
358}
Source

pub fn allocate_gfn(&self) -> Result<Gfn, VmiError>

Available on crate features injector and utils only.

Allocates a guest frame number (GFN).

This method allocates a new GFN, with the driver responsible for choosing the specific frame to allocate. It’s useful when you need to allocate new memory pages for the VM without caring about the specific location.

Source

pub fn allocate_gfn_at(&self, gfn: Gfn) -> Result<(), VmiError>

Available on crate features injector and utils only.

Allocates a guest frame number (GFN) at a specific location.

This method allows you to allocate a particular GFN. It’s useful when you need to allocate a specific memory page for the VM.

Source

pub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError>

Available on crate features injector and utils only.

Frees a previously allocated guest frame number (GFN).

This method deallocates a GFN that was previously allocated. It’s important to free GFNs when they’re no longer needed to prevent memory leaks in the VM.

Source

pub fn inject_interrupt( &self, vcpu: VcpuId, interrupt: <<Driver as VmiDriver>::Architecture as Architecture>::Interrupt, ) -> Result<(), VmiError>

Available on crate features injector and utils only.

Injects an interrupt into a specific virtual CPU.

This method allows for the injection of architecture-specific interrupts into a given vCPU. It can be used to simulate hardware events or to manipulate the guest’s execution flow for analysis purposes.

The type of interrupt and its parameters are defined by the architecture-specific Architecture::Interrupt type.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 494)
474    fn dispatch(
475        &mut self,
476        vmi: &VmiContext<WindowsOs<Driver>>,
477    ) -> Result<VmiEventResponse<Amd64>, VmiError> {
478        let event = vmi.event();
479        let result = match event.reason() {
480            EventReason::MemoryAccess(_) => self.memory_access(vmi),
481            EventReason::Interrupt(_) => self.interrupt(vmi),
482            EventReason::Singlestep(_) => self.singlestep(vmi),
483            _ => panic!("Unhandled event: {:?}", event.reason()),
484        };
485
486        // If VMI tries to read from a page that is not present, it will return
487        // a page fault error. In this case, we inject a page fault interrupt
488        // to the guest.
489        //
490        // Once the guest handles the page fault, it will retry to execute the
491        // instruction that caused the page fault.
492        if let Err(VmiError::Translation(pf)) = result {
493            tracing::warn!(?pf, "Page fault, injecting");
494            vmi.inject_interrupt(event.vcpu_id(), Interrupt::page_fault(pf.va, 0))?;
495            return Ok(VmiEventResponse::default());
496        }
497
498        result
499    }
Source

pub fn reset_state(&self) -> Result<(), VmiError>

Available on crate features injector and utils only.

Resets the state of the VMI system.

This method clears all event monitors, caches, and any other stateful data maintained by the VMI system. It’s useful for bringing the VMI system back to a known clean state, which can be necessary when switching between different analysis tasks or recovering from error conditions.

Trait Implementations§

Source§

impl<Os> Clone for VmiState<'_, Os>
where Os: VmiOs,

Source§

fn clone(&self) -> VmiState<'_, Os>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<Os> Copy for VmiState<'_, Os>
where Os: VmiOs,

Source§

impl<'a, Os> Deref for VmiState<'a, Os>
where Os: VmiOs,

Source§

type Target = VmiSession<'a, Os>

The resulting type after dereferencing.
Source§

fn deref(&self) -> &<VmiState<'a, Os> as Deref>::Target

Dereferences the value.

Auto Trait Implementations§

§

impl<'a, Os> !RefUnwindSafe for VmiState<'a, Os>

§

impl<'a, Os> !Send for VmiState<'a, Os>

§

impl<'a, Os> !Sync for VmiState<'a, Os>

§

impl<'a, Os> !UnwindSafe for VmiState<'a, Os>

§

impl<'a, Os> Freeze for VmiState<'a, Os>
where VmiSession<'a, Os>: Freeze, &'a <<Os as VmiOs>::Architecture as Architecture>::Registers: Freeze,

§

impl<'a, Os> Unpin for VmiState<'a, Os>
where VmiSession<'a, Os>: Unpin, &'a <<Os as VmiOs>::Architecture as Architecture>::Registers: Unpin,

§

impl<'a, Os> UnsafeUnpin for VmiState<'a, Os>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> ArchivePointee for T

Source§

type ArchivedMetadata = ()

The archived version of the pointer metadata for this type.
Source§

fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata

Converts some archived metadata to the pointer metadata for itself.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> LayoutRaw for T

Source§

fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>

Returns the layout of the type.
Source§

impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
where T: SharedNiching<N1, N2>, N1: Niching<T>, N2: Niching<T>,

Source§

unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool

Returns whether the given value has been niched. Read more
Source§

fn resolve_niched(out: Place<NichedOption<T, N1>>)

Writes data to out indicating that a T is niched.
Source§

impl<T> Pointee for T

Source§

type Metadata = ()

The metadata type for pointers and references to this type.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<P, T> Receiver for P
where P: Deref<Target = T> + ?Sized, T: ?Sized,

Source§

type Target = T

🔬This is a nightly-only experimental API. (arbitrary_self_types)
The target type on which the method may be called.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more