pub struct VmiState<'a, Driver, Os = NoOS>{ /* 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, Driver, Os> VmiState<'a, Driver, Os>
impl<'a, Driver, Os> VmiState<'a, Driver, Os>
Sourcepub fn new(
session: &'a VmiSession<'a, Driver, Os>,
registers: &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers,
) -> VmiState<'a, Driver, Os>
Available on crate feature utils only.
pub fn new( session: &'a VmiSession<'a, Driver, Os>, registers: &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Driver, Os>
utils only.Creates a new VMI state.
Sourcepub fn with_registers(
&'a self,
registers: &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers,
) -> VmiState<'a, Driver, Os>
Available on crate feature utils only.
pub fn with_registers( &'a self, registers: &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Driver, Os>
utils only.Creates a new VMI state with the specified registers.
Sourcepub fn without_os(&self) -> VmiState<'a, Driver>
Available on crate feature utils only.
pub fn without_os(&self) -> VmiState<'a, Driver>
utils only.Creates a new VMI state without an OS-specific implementation.
Sourcepub fn session(&self) -> &VmiSession<'a, Driver, Os>
Available on crate feature utils only.
pub fn session(&self) -> &VmiSession<'a, Driver, Os>
utils only.Returns the VMI session.
Sourcepub fn registers(
&self,
) -> &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers
Available on crate feature utils only.
pub fn registers( &self, ) -> &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers
utils only.Returns the CPU registers associated with the current event.
Sourcepub fn os(&self) -> VmiOsState<'a, Driver, Os>
Available on crate feature utils only.
pub fn os(&self) -> VmiOsState<'a, Driver, Os>
utils only.Returns a wrapper providing access to OS-specific operations.
Examples found in repository?
80pub fn find_process<'a, Driver, Os>(
81 vmi: &VmiState<'a, Driver, Os>,
82 name: &str,
83) -> Result<Option<Os::Process<'a>>, VmiError>
84where
85 Driver: VmiDriver,
86 Os: VmiOs<Driver>,
87{
88 for process in vmi.os().processes()? {
89 let process = process?;
90
91 if process.name()?.to_lowercase() == name {
92 return Ok(Some(process));
93 }
94 }
95
96 Ok(None)
97}More examples
102fn enumerate_kernel_modules(vmi: &VmiState<Driver, 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<Driver, 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, ®isters)?.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::<JsonCodec>::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(®isters);
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}10fn main() -> Result<(), Box<dyn std::error::Error>> {
11 let domain_id = 'x: {
12 for name in &["win7", "win10", "win11", "ubuntu22"] {
13 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
14 break 'x domain_id;
15 }
16 }
17
18 panic!("Domain not found");
19 };
20
21 // Setup VMI.
22 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
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 VM 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, ®isters)?.expect("kernel information")
43 };
44
45 // Load the profile.
46 // The profile contains offsets to kernel functions and data structures.
47 let isr = IsrCache::<JsonCodec>::new("cache")?;
48 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
49 let profile = entry.profile()?;
50
51 // Create the VMI session.
52 tracing::info!("Creating VMI session");
53 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
54 let session = VmiSession::new(&core, &os);
55
56 // Pause the VM again to get consistent state.
57 let _pause_guard = session.pause_guard()?;
58
59 // Create a new `VmiState` with the current register.
60 let registers = session.registers(VcpuId(0))?;
61 let vmi = session.with_registers(®isters);
62
63 // Get the list of processes and print them.
64 for process in vmi.os().processes()? {
65 let process = process?;
66
67 println!(
68 "{} [{}] {} (root @ {})",
69 process.object()?,
70 process.id()?,
71 process.name()?,
72 process.translation_root()?
73 );
74 }
75
76 Ok(())
77}63 pub fn new(
64 session: &VmiSession<Driver, WindowsOs<Driver>>,
65 profile: &Profile,
66 terminate_flag: Arc<AtomicBool>,
67 ) -> Result<Self, VmiError> {
68 // Capture the current state of the VCPU and get the base address of
69 // the kernel.
70 //
71 // This base address is essential to correctly offset monitored
72 // functions.
73 //
74 // NOTE: `kernel_image_base` tries to find the kernel in the memory
75 // with the help of the CPU registers. On AMD64 architecture,
76 // the kernel image base is usually found using the `MSR_LSTAR`
77 // register, which contains the address of the system call
78 // handler. This register is set by the operating system during
79 // boot and is left unchanged (unless some rootkits are involved).
80 //
81 // Therefore, we can take an arbitrary registers at any point
82 // in time (as long as the OS has booted and the page tables are
83 // set up) and use them to find the kernel image base.
84 let registers = session.registers(VcpuId(0))?;
85 let vmi = session.with_registers(®isters);
86
87 let kernel_image_base = vmi.os().kernel_image_base()?;
88 tracing::info!(%kernel_image_base);
89
90 // Get the system process.
91 //
92 // The system process is the first process created by the kernel.
93 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
94 // To monitor page table entries, we need to locate the translation root
95 // of this process.
96 let system_process = vmi.os().system_process()?;
97 tracing::info!(system_process = %system_process.object()?);
98
99 // Get the translation root of the system process.
100 // This is effectively "the CR3 of the kernel".
101 //
102 // The translation root is the root of the page table hierarchy (also
103 // known as the Directory Table Base or PML4).
104 let root = system_process.translation_root()?;
105 tracing::info!(%root);
106
107 // Load the symbols from the profile.
108 let symbols = Symbols::new(profile)?;
109
110 // Enable monitoring of the INT3 and singlestep events.
111 //
112 // INT3 is used to monitor the execution of specific functions.
113 // Singlestep is used to monitor the modifications of page table
114 // entries.
115 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
116 vmi.monitor_enable(EventMonitor::Singlestep)?;
117
118 // Create a new view for the monitor.
119 // This view is used for monitoring function calls and memory accesses.
120 let view = vmi.create_view(MemoryAccess::RWX)?;
121 vmi.switch_to_view(view)?;
122
123 // Create a new breakpoint controller.
124 //
125 // The breakpoint controller is used to insert breakpoints for specific
126 // functions.
127 //
128 // From the guest's perspective, these breakpoints are "hidden", since
129 // the breakpoint controller will unset the read/write access to the
130 // physical memory page where the breakpoint is inserted, while keeping
131 // the execute access.
132 //
133 // This way, the guest will be able to execute the code, but attempts to
134 // read or write the memory will trigger the `memory_access` callback.
135 //
136 // When a VCPU tries to execute the breakpoint instruction:
137 // - an `interrupt` callback will be triggered
138 // - the breakpoint will be handled (e.g., log the function call)
139 // - a fast-singlestep[1] will be performed over the INT3 instruction
140 //
141 // When a VCPU tries to read from this page (e.g., a PatchGuard check):
142 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
143 // access type)
144 // - fast-singlestep[1] will be performed over the instruction that tried to
145 // read the memory
146 //
147 // This way, the instruction will read the original memory content.
148 //
149 // [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
150 // to a different view, execute a single instruction, and then
151 // switch back to the original view. In this case, the view is
152 // switched to the `default_view` (which is unmodified).
153 let mut bpm = BreakpointManager::new();
154
155 // Create a new page table monitor.
156 //
157 // The page table monitor is used to monitor the page table entries of
158 // the hooked functions.
159 //
160 // More specifically, it is used to monitor the pages that the breakpoint
161 // was inserted into. This is necessary to handle the case when the
162 // page containing the breakpoint is paged out (and then paged in
163 // again).
164 //
165 // `PageTableMonitor` works by unsetting the write access to the page
166 // tables of the hooked functions. When the page is paged out, the
167 // `PRESENT` bit in the page table entry is unset and, conversely, when
168 // the page is paged in, the `PRESENT` bit is set again.
169 //
170 // When that happens:
171 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
172 // access type)
173 // - the callback will mark the page as dirty in the page table monitor
174 // - a singlestep will be performed over the instruction that tried to modify
175 // the memory containing the page table entry
176 // - the `singlestep` handler will process the dirty page table entries and
177 // inform the breakpoint controller to handle the changes
178 let mut ptm = PageTableMonitor::new();
179
180 // Pause the VM to avoid race conditions between inserting breakpoints
181 // and monitoring page table entries. The VM resumes when the pause
182 // guard is dropped.
183 let _pause_guard = vmi.pause_guard()?;
184
185 // Insert breakpoint for the `NtCreateFile` function.
186 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
187 let cx_NtCreateFile = (va_NtCreateFile, root);
188 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
189 .global()
190 .with_tag("NtCreateFile");
191 bpm.insert(&vmi, bp_NtCreateFile)?;
192 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
193 tracing::info!(%va_NtCreateFile);
194
195 // Insert breakpoint for the `NtWriteFile` function.
196 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
197 let cx_NtWriteFile = (va_NtWriteFile, root);
198 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
199 .global()
200 .with_tag("NtWriteFile");
201 bpm.insert(&vmi, bp_NtWriteFile)?;
202 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
203 tracing::info!(%va_NtWriteFile);
204
205 // Insert breakpoint for the `PspInsertProcess` function.
206 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
207 let cx_PspInsertProcess = (va_PspInsertProcess, root);
208 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
209 .global()
210 .with_tag("PspInsertProcess");
211 bpm.insert(&vmi, bp_PspInsertProcess)?;
212 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
213
214 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
215 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
216 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
217 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
218 .global()
219 .with_tag("MmCleanProcessAddressSpace");
220 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
221 ptm.monitor(
222 &vmi,
223 cx_MmCleanProcessAddressSpace,
224 view,
225 "MmCleanProcessAddressSpace",
226 )?;
227
228 Ok(Self {
229 terminate_flag,
230 view,
231 bpm,
232 ptm,
233 })
234 }Sourcepub fn access_context(&self, address: Va) -> AccessContext
Available on crate feature utils only.
pub fn access_context(&self, address: Va) -> AccessContext
utils only.Creates an address context for a given virtual address.
Sourcepub fn address_context(&self, address: Va) -> AddressContext
Available on crate feature utils only.
pub fn address_context(&self, address: Va) -> AddressContext
utils only.Creates an address context for a given virtual address.
Sourcepub fn translation_root(&self, va: Va) -> Pa
Available on crate feature utils only.
pub fn translation_root(&self, va: Va) -> Pa
utils only.Returns the physical address of the root of the current page table hierarchy for a given virtual address.
Sourcepub fn return_address(&self) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn return_address(&self) -> Result<Va, VmiError>
utils only.Returns the return address from the current stack frame.
Sourcepub fn translate_address(&self, va: Va) -> Result<Pa, VmiError>
Available on crate feature utils only.
pub fn translate_address(&self, va: Va) -> Result<Pa, VmiError>
utils only.Translates a virtual address to a physical address.
Sourcepub fn read(&self, address: Va, buffer: &mut [u8]) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn read(&self, address: Va, buffer: &mut [u8]) -> Result<(), VmiError>
utils only.Reads memory from the virtual machine.
Sourcepub fn write(&self, address: Va, buffer: &[u8]) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write(&self, address: Va, buffer: &[u8]) -> Result<(), VmiError>
utils only.Writes memory to the virtual machine.
Sourcepub fn read_u8(&self, address: Va) -> Result<u8, VmiError>
Available on crate feature utils only.
pub fn read_u8(&self, address: Va) -> Result<u8, VmiError>
utils only.Reads a single byte from the virtual machine.
Sourcepub fn read_u16(&self, address: Va) -> Result<u16, VmiError>
Available on crate feature utils only.
pub fn read_u16(&self, address: Va) -> Result<u16, VmiError>
utils only.Reads a 16-bit unsigned integer from the virtual machine.
Sourcepub fn read_u32(&self, address: Va) -> Result<u32, VmiError>
Available on crate feature utils only.
pub fn read_u32(&self, address: Va) -> Result<u32, VmiError>
utils only.Reads a 32-bit unsigned integer from the virtual machine.
Examples found in repository?
93fn recipe_factory<Driver>(data: GuestFile) -> Recipe<Driver, WindowsOs<Driver>, GuestFile>
94where
95 Driver: VmiDriver<Architecture = Amd64>,
96{
97 recipe![
98 Recipe::<_, WindowsOs<Driver>, _>::new(data),
99 //
100 // Step 1:
101 // - Create a file
102 //
103 {
104 tracing::info!(
105 target_path = data![target_path],
106 "step 1: kernel32!CreateFileA()"
107 );
108
109 const GENERIC_WRITE: u64 = 0x40000000;
110 const CREATE_ALWAYS: u64 = 2;
111 const FILE_ATTRIBUTE_NORMAL: u64 = 0x80;
112
113 inject! {
114 kernel32!CreateFileA(
115 &data![target_path], // lpFileName
116 GENERIC_WRITE, // dwDesiredAccess
117 0, // dwShareMode
118 0, // lpSecurityAttributes
119 CREATE_ALWAYS, // dwCreationDisposition
120 FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
121 0 // hTemplateFile
122 )
123 }
124 },
125 //
126 // Step 2:
127 // - Verify the file handle
128 // - Write the content to the file
129 //
130 {
131 let return_value = registers!().rax;
132
133 const INVALID_HANDLE_VALUE: u64 = 0xffff_ffff_ffff_ffff;
134
135 if return_value == INVALID_HANDLE_VALUE {
136 tracing::error!(
137 return_value = %Hex(return_value),
138 "step 2: kernel32!CreateFileA() failed"
139 );
140
141 return Ok(RecipeControlFlow::Break);
142 }
143
144 tracing::info!(
145 handle = %Hex(data![handle]),
146 "step 2: kernel32!WriteFile()"
147 );
148
149 // Save the handle.
150 data![handle] = return_value;
151
152 // Allocate a value on the stack to store the output parameter.
153 data![bytes_written_ptr] = copy_to_stack!(0u64)?;
154
155 inject! {
156 kernel32!WriteFile(
157 data![handle], // hFile
158 data![content], // lpBuffer
159 data![content].len(), // nNumberOfBytesToWrite
160 data![bytes_written_ptr], // lpNumberOfBytesWritten
161 0 // lpOverlapped
162 )
163 }
164 },
165 //
166 // Step 3:
167 // - Verify that the `WriteFile()` call succeeded
168 // - Close the file handle
169 //
170 {
171 let return_value = registers!().rax;
172
173 // Check if the `WriteFile()` call failed.
174 if return_value == 0 {
175 tracing::error!(
176 return_value = %Hex(return_value),
177 "step 3: kernel32!WriteFile() failed"
178 );
179
180 // Don't exit, we want to close the handle.
181 // return Ok(RecipeControlFlow::Break);
182 }
183
184 // Read the number of bytes written.
185 let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
186 tracing::info!(number_of_bytes_written, "step 3: kernel32!WriteFile()");
187
188 tracing::info!(
189 handle = %Hex(data![handle]),
190 "step 3: kernel32!CloseHandle()"
191 );
192
193 inject! {
194 kernel32!CloseHandle(
195 data![handle] // hObject
196 )
197 }
198 },
199 ]
200}More examples
133pub fn recipe_factory<Driver>(data: GuestFile) -> Recipe<Driver, WindowsOs<Driver>, GuestFile>
134where
135 Driver: VmiDriver<Architecture = Amd64>,
136{
137 recipe![
138 Recipe::<_, WindowsOs<Driver>, _>::new(data),
139 //
140 // Step 1:
141 // - Create a file.
142 //
143 {
144 tracing::info!(
145 target_path = data![target_path],
146 "step 1: kernel32!CreateFileA()"
147 );
148
149 const GENERIC_WRITE: u64 = 0x40000000;
150 const CREATE_ALWAYS: u64 = 2;
151 const FILE_ATTRIBUTE_NORMAL: u64 = 0x80;
152
153 inject! {
154 kernel32!CreateFileA(
155 &data![target_path], // lpFileName
156 GENERIC_WRITE, // dwDesiredAccess
157 0, // dwShareMode
158 0, // lpSecurityAttributes
159 CREATE_ALWAYS, // dwCreationDisposition
160 FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
161 0 // hTemplateFile
162 )
163 }
164 },
165 //
166 // Step 2:
167 // - Verify the file handle
168 // - Write the first chunk to the file
169 //
170 {
171 let return_value = registers!().rax;
172
173 const INVALID_HANDLE_VALUE: u64 = 0xffff_ffff_ffff_ffff;
174
175 if return_value == INVALID_HANDLE_VALUE {
176 tracing::error!(
177 return_value = %Hex(return_value),
178 "step 2: kernel32!CreateFileA() failed"
179 );
180
181 return Ok(RecipeControlFlow::Break);
182 }
183
184 tracing::info!(
185 handle = %Hex(data![handle]),
186 "step 2: kernel32!WriteFile()"
187 );
188
189 // Save the handle.
190 data![handle] = return_value;
191
192 // Allocate a value on the stack to store the output parameter.
193 data![bytes_written_ptr] = copy_to_stack!(0u64)?;
194
195 // Get the first chunk of content.
196 let content = &data![content];
197 let chunk_size = usize::min(content.len(), data![chunk_size]);
198 let chunk = content[..chunk_size].to_vec();
199
200 inject! {
201 kernel32!WriteFile(
202 data![handle], // hFile
203 chunk, // lpBuffer
204 chunk.len() as u64, // nNumberOfBytesToWrite
205 data![bytes_written_ptr], // lpNumberOfBytesWritten
206 0 // lpOverlapped
207 )
208 }
209 },
210 //
211 // Step 3:
212 // - Verify that the `WriteFile()` call succeeded
213 // - Write the next chunk to the file
214 // - Repeat this step until all content is written
215 //
216 {
217 let return_value = registers!().rax;
218
219 if return_value == 0 {
220 tracing::error!(
221 return_value = %Hex(return_value),
222 "step 3: kernel32!WriteFile() failed"
223 );
224
225 return Ok(RecipeControlFlow::Break);
226 }
227
228 // Read the number of bytes written and update the total.
229 let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
230 data![bytes_written_total] += number_of_bytes_written;
231
232 let bytes_written_total = data![bytes_written_total];
233 let content = &data![content];
234
235 // If all content is written, move to the next step.
236 if bytes_written_total >= content.len() as u32 {
237 return Ok(RecipeControlFlow::Continue);
238 }
239
240 // Get the next chunk of content.
241 let remaining = content.len() - bytes_written_total as usize;
242 let chunk_size = usize::min(remaining, data![chunk_size]);
243 let chunk = &content[bytes_written_total as usize..];
244 let chunk = chunk[..chunk_size].to_vec();
245
246 // Allocate a value on the stack to store the output parameter.
247 data![bytes_written_ptr] = copy_to_stack!(0u64)?;
248
249 inject! {
250 kernel32!WriteFile(
251 data![handle], // hFile
252 chunk, // lpBuffer
253 chunk.len() as u64, // nNumberOfBytesToWrite
254 data![bytes_written_ptr], // lpNumberOfBytesWritten
255 0 // lpOverlapped
256 )
257 }?;
258
259 Ok(RecipeControlFlow::Repeat)
260 },
261 //
262 // Step 4:
263 // - Verify that the last `WriteFile()` call succeeded
264 // - Close the file handle
265 //
266 {
267 let return_value = registers!().rax;
268
269 if return_value == 0 {
270 tracing::error!(
271 return_value = %Hex(return_value),
272 "step 4: kernel32!WriteFile() failed"
273 );
274
275 // Don't exit, we want to close the handle.
276 // return Ok(RecipeControlFlow::Break);
277 }
278
279 // Read the number of bytes written.
280 let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
281 tracing::info!(number_of_bytes_written, "step 4: kernel32!WriteFile()");
282
283 tracing::info!(
284 handle = %Hex(data![handle]),
285 "step 4: kernel32!CloseHandle()"
286 );
287
288 inject! {
289 kernel32!CloseHandle(
290 data![handle] // hObject
291 )
292 }
293 },
294 ]
295}Sourcepub fn read_u64(&self, address: Va) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_u64(&self, address: Va) -> Result<u64, VmiError>
utils only.Reads a 64-bit unsigned integer from the virtual machine.
Sourcepub fn read_uint(&self, address: Va, size: usize) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_uint(&self, address: Va, size: usize) -> Result<u64, VmiError>
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.
Sourcepub fn read_field(
&self,
base_address: Va,
field: &Field,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_field( &self, base_address: Va, field: &Field, ) -> Result<u64, VmiError>
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.
Sourcepub fn read_address(&self, address: Va) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address(&self, address: Va) -> Result<u64, VmiError>
utils only.Reads an address-sized unsigned integer from the virtual machine.
Sourcepub fn read_address_native(&self, address: Va) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address_native(&self, address: Va) -> Result<u64, VmiError>
utils only.Reads an address-sized unsigned integer from the virtual machine.
Sourcepub fn read_address32(&self, address: Va) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address32(&self, address: Va) -> Result<u64, VmiError>
utils only.Reads a 32-bit address from the virtual machine.
Sourcepub fn read_address64(&self, address: Va) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address64(&self, address: Va) -> Result<u64, VmiError>
utils only.Reads a 64-bit address from the virtual machine.
Sourcepub fn read_va(&self, address: Va) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va(&self, address: Va) -> Result<Va, VmiError>
utils only.Reads a virtual address from the virtual machine.
Sourcepub fn read_va_native(&self, address: Va) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va_native(&self, address: Va) -> Result<Va, VmiError>
utils only.Reads a virtual address from the virtual machine.
Sourcepub fn read_va32(&self, address: Va) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va32(&self, address: Va) -> Result<Va, VmiError>
utils only.Reads a 32-bit virtual address from the virtual machine.
Sourcepub fn read_va64(&self, address: Va) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va64(&self, address: Va) -> Result<Va, VmiError>
utils only.Reads a 64-bit virtual address from the virtual machine.
Sourcepub fn read_string_bytes_limited(
&self,
address: Va,
limit: usize,
) -> Result<Vec<u8>, VmiError>
Available on crate feature utils only.
pub fn read_string_bytes_limited( &self, address: Va, limit: usize, ) -> Result<Vec<u8>, VmiError>
utils only.Reads a null-terminated string of bytes from the virtual machine with a specified limit.
Sourcepub fn read_string_bytes(&self, address: Va) -> Result<Vec<u8>, VmiError>
Available on crate feature utils only.
pub fn read_string_bytes(&self, address: Va) -> Result<Vec<u8>, VmiError>
utils only.Reads a null-terminated string of bytes from the virtual machine.
Sourcepub fn read_wstring_bytes_limited(
&self,
address: Va,
limit: usize,
) -> Result<Vec<u16>, VmiError>
Available on crate feature utils only.
pub fn read_wstring_bytes_limited( &self, address: Va, limit: usize, ) -> Result<Vec<u16>, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring_bytes(&self, address: Va) -> Result<Vec<u16>, VmiError>
Available on crate feature utils only.
pub fn read_wstring_bytes(&self, address: Va) -> Result<Vec<u16>, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine.
Sourcepub fn read_string_limited(
&self,
address: Va,
limit: usize,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_string_limited( &self, address: Va, limit: usize, ) -> Result<String, VmiError>
utils only.Reads a null-terminated string from the virtual machine with a specified limit.
Sourcepub fn read_string(&self, address: Va) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_string(&self, address: Va) -> Result<String, VmiError>
utils only.Reads a null-terminated string from the virtual machine.
Sourcepub fn read_wstring_limited(
&self,
address: Va,
limit: usize,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_wstring_limited( &self, address: Va, limit: usize, ) -> Result<String, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring(&self, address: Va) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_wstring(&self, address: Va) -> Result<String, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine.
Sourcepub fn read_struct<T>(&self, address: Va) -> Result<T, VmiError>
Available on crate feature utils only.
pub fn read_struct<T>(&self, address: Va) -> Result<T, VmiError>
utils only.Reads a struct from the virtual machine.
Sourcepub fn read_in(
&self,
ctx: impl Into<AccessContext>,
buffer: &mut [u8],
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn read_in( &self, ctx: impl Into<AccessContext>, buffer: &mut [u8], ) -> Result<(), VmiError>
utils only.Reads memory from the virtual machine.
Sourcepub fn write_in(
&self,
ctx: impl Into<AccessContext>,
buffer: &[u8],
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_in( &self, ctx: impl Into<AccessContext>, buffer: &[u8], ) -> Result<(), VmiError>
utils only.Writes memory to the virtual machine.
Sourcepub fn read_u8_in(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
Available on crate feature utils only.
pub fn read_u8_in(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
utils only.Reads a single byte from the virtual machine.
Sourcepub fn read_u16_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u16, VmiError>
Available on crate feature utils only.
pub fn read_u16_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u16, VmiError>
utils only.Reads a 16-bit unsigned integer from the virtual machine.
Sourcepub fn read_u32_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u32, VmiError>
Available on crate feature utils only.
pub fn read_u32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u32, VmiError>
utils only.Reads a 32-bit unsigned integer from the virtual machine.
Sourcepub fn read_u64_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_u64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils only.Reads a 64-bit unsigned integer from the virtual machine.
Sourcepub fn read_uint_in(
&self,
ctx: impl Into<AccessContext>,
size: usize,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_uint_in( &self, ctx: impl Into<AccessContext>, size: usize, ) -> Result<u64, VmiError>
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.
Sourcepub fn read_field_in(
&self,
ctx: impl Into<AccessContext>,
field: &Field,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_field_in( &self, ctx: impl Into<AccessContext>, field: &Field, ) -> Result<u64, VmiError>
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.
Sourcepub fn read_address_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils only.Reads an address-sized unsigned integer from the virtual machine.
Sourcepub fn read_address_native_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address_native_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils only.Reads an address-sized unsigned integer from the virtual machine.
Sourcepub fn read_address32_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils only.Reads a 32-bit address from the virtual machine.
Sourcepub fn read_address64_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils only.Reads a 64-bit address from the virtual machine.
Sourcepub fn read_va_in(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va_in(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
utils only.Reads a virtual address from the virtual machine.
Sourcepub fn read_va_native_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va_native_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>
utils only.Reads a virtual address from the virtual machine.
Sourcepub fn read_va32_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>
utils only.Reads a 32-bit virtual address from the virtual machine.
Sourcepub fn read_va64_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>
utils only.Reads a 64-bit virtual address from the virtual machine.
Sourcepub fn read_string_bytes_limited_in(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<Vec<u8>, VmiError>
Available on crate feature utils only.
pub fn read_string_bytes_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u8>, VmiError>
utils only.Reads a null-terminated string of bytes from the virtual machine with a specified limit.
Sourcepub fn read_string_bytes_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u8>, VmiError>
Available on crate feature utils only.
pub fn read_string_bytes_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u8>, VmiError>
utils only.Reads a null-terminated string of bytes from the virtual machine.
Sourcepub fn read_wstring_bytes_limited_in(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<Vec<u16>, VmiError>
Available on crate feature utils only.
pub fn read_wstring_bytes_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u16>, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring_bytes_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError>
Available on crate feature utils only.
pub fn read_wstring_bytes_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u16>, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine.
Sourcepub fn read_string_limited_in(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_string_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
utils only.Reads a null-terminated string from the virtual machine with a specified limit.
Sourcepub fn read_string_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_string_in( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
utils only.Reads a null-terminated string from the virtual machine.
Sourcepub fn read_wstring_limited_in(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_wstring_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_wstring_in( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine.
Sourcepub fn read_struct_in<T>(
&self,
ctx: impl Into<AccessContext>,
) -> Result<T, VmiError>
Available on crate feature utils only.
pub fn read_struct_in<T>( &self, ctx: impl Into<AccessContext>, ) -> Result<T, VmiError>
utils only.Reads a struct from the virtual machine.
Sourcepub fn write_u8(&self, address: Va, value: u8) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_u8(&self, address: Va, value: u8) -> Result<(), VmiError>
utils only.Writes a single byte to the virtual machine.
Sourcepub fn write_u16(&self, address: Va, value: u16) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_u16(&self, address: Va, value: u16) -> Result<(), VmiError>
utils only.Writes a 16-bit unsigned integer to the virtual machine.
Sourcepub fn write_u32(&self, address: Va, value: u32) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_u32(&self, address: Va, value: u32) -> Result<(), VmiError>
utils only.Writes a 32-bit unsigned integer to the virtual machine.
Methods from Deref<Target = VmiSession<'a, Driver, Os>>§
Sourcepub fn with_registers(
&'a self,
registers: &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers,
) -> VmiState<'a, Driver, Os>
Available on crate feature utils only.
pub fn with_registers( &'a self, registers: &'a <<Driver as VmiDriver>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Driver, Os>
utils only.Creates a new VMI state with the specified registers.
Examples found in repository?
64fn main() -> Result<(), Box<dyn std::error::Error>> {
65 let (session, profile) = common::create_vmi_session()?;
66
67 let explorer_pid = {
68 // This block is used to drop the pause guard after the PID is found.
69 // If the `session.handle()` would be called with the VM paused, no
70 // events would be triggered.
71 let _pause_guard = session.pause_guard()?;
72
73 let registers = session.registers(VcpuId(0))?;
74 let vmi = session.with_registers(®isters);
75
76 let explorer = match common::find_process(&vmi, "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 InjectorHandler::new(
95 session,
96 &profile,
97 explorer_pid,
98 recipe_factory(MessageBox::new(
99 "Hello, World!",
100 "This is a message box from the VMI!",
101 )),
102 )
103 })?;
104
105 Ok(())
106}More examples
202fn main() -> Result<(), Box<dyn std::error::Error>> {
203 let (session, profile) = common::create_vmi_session()?;
204
205 let explorer_pid = {
206 // This block is used to drop the pause guard after the PID is found.
207 // If the `session.handle()` would be called with the VM paused, no
208 // events would be triggered.
209 let _pause_guard = session.pause_guard()?;
210
211 let registers = session.registers(VcpuId(0))?;
212 let vmi = session.with_registers(®isters);
213
214 let explorer = match common::find_process(&vmi, "explorer.exe")? {
215 Some(explorer) => explorer,
216 None => {
217 tracing::error!("explorer.exe not found");
218 return Ok(());
219 }
220 };
221
222 tracing::info!(
223 pid = %explorer.id()?,
224 object = %explorer.object()?,
225 "found explorer.exe"
226 );
227
228 explorer.id()?
229 };
230
231 session.handle(|session| {
232 InjectorHandler::new(
233 session,
234 &profile,
235 explorer_pid,
236 recipe_factory(GuestFile::new(
237 "C:\\Users\\John\\Desktop\\test.txt",
238 "Hello, World!".as_bytes(),
239 )),
240 )
241 })?;
242
243 Ok(())
244}297fn main() -> Result<(), Box<dyn std::error::Error>> {
298 let (session, profile) = common::create_vmi_session()?;
299
300 let explorer_pid = {
301 // This block is used to drop the pause guard after the PID is found.
302 // If the `session.handle()` would be called with the VM paused, no
303 // events would be triggered.
304 let _pause_guard = session.pause_guard()?;
305
306 let registers = session.registers(VcpuId(0))?;
307 let vmi = session.with_registers(®isters);
308
309 let explorer = match common::find_process(&vmi, "explorer.exe")? {
310 Some(explorer) => explorer,
311 None => {
312 tracing::error!("explorer.exe not found");
313 return Ok(());
314 }
315 };
316
317 tracing::info!(
318 pid = %explorer.id()?,
319 object = %explorer.object()?,
320 "found explorer.exe"
321 );
322
323 explorer.id()?
324 };
325
326 let mut content = Vec::new();
327 for c in 'A'..='Z' {
328 content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
329 }
330
331 session.handle(|session| {
332 InjectorHandler::new(
333 session,
334 &profile,
335 explorer_pid,
336 recipe_factory(GuestFile::new(
337 "C:\\Users\\John\\Desktop\\test.txt",
338 content,
339 )),
340 )
341 })?;
342
343 Ok(())
344}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, ®isters)?.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::<JsonCodec>::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(®isters);
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}10fn main() -> Result<(), Box<dyn std::error::Error>> {
11 let domain_id = 'x: {
12 for name in &["win7", "win10", "win11", "ubuntu22"] {
13 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
14 break 'x domain_id;
15 }
16 }
17
18 panic!("Domain not found");
19 };
20
21 // Setup VMI.
22 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
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 VM 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, ®isters)?.expect("kernel information")
43 };
44
45 // Load the profile.
46 // The profile contains offsets to kernel functions and data structures.
47 let isr = IsrCache::<JsonCodec>::new("cache")?;
48 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
49 let profile = entry.profile()?;
50
51 // Create the VMI session.
52 tracing::info!("Creating VMI session");
53 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
54 let session = VmiSession::new(&core, &os);
55
56 // Pause the VM again to get consistent state.
57 let _pause_guard = session.pause_guard()?;
58
59 // Create a new `VmiState` with the current register.
60 let registers = session.registers(VcpuId(0))?;
61 let vmi = session.with_registers(®isters);
62
63 // Get the list of processes and print them.
64 for process in vmi.os().processes()? {
65 let process = process?;
66
67 println!(
68 "{} [{}] {} (root @ {})",
69 process.object()?,
70 process.id()?,
71 process.name()?,
72 process.translation_root()?
73 );
74 }
75
76 Ok(())
77}63 pub fn new(
64 session: &VmiSession<Driver, WindowsOs<Driver>>,
65 profile: &Profile,
66 terminate_flag: Arc<AtomicBool>,
67 ) -> Result<Self, VmiError> {
68 // Capture the current state of the VCPU and get the base address of
69 // the kernel.
70 //
71 // This base address is essential to correctly offset monitored
72 // functions.
73 //
74 // NOTE: `kernel_image_base` tries to find the kernel in the memory
75 // with the help of the CPU registers. On AMD64 architecture,
76 // the kernel image base is usually found using the `MSR_LSTAR`
77 // register, which contains the address of the system call
78 // handler. This register is set by the operating system during
79 // boot and is left unchanged (unless some rootkits are involved).
80 //
81 // Therefore, we can take an arbitrary registers at any point
82 // in time (as long as the OS has booted and the page tables are
83 // set up) and use them to find the kernel image base.
84 let registers = session.registers(VcpuId(0))?;
85 let vmi = session.with_registers(®isters);
86
87 let kernel_image_base = vmi.os().kernel_image_base()?;
88 tracing::info!(%kernel_image_base);
89
90 // Get the system process.
91 //
92 // The system process is the first process created by the kernel.
93 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
94 // To monitor page table entries, we need to locate the translation root
95 // of this process.
96 let system_process = vmi.os().system_process()?;
97 tracing::info!(system_process = %system_process.object()?);
98
99 // Get the translation root of the system process.
100 // This is effectively "the CR3 of the kernel".
101 //
102 // The translation root is the root of the page table hierarchy (also
103 // known as the Directory Table Base or PML4).
104 let root = system_process.translation_root()?;
105 tracing::info!(%root);
106
107 // Load the symbols from the profile.
108 let symbols = Symbols::new(profile)?;
109
110 // Enable monitoring of the INT3 and singlestep events.
111 //
112 // INT3 is used to monitor the execution of specific functions.
113 // Singlestep is used to monitor the modifications of page table
114 // entries.
115 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
116 vmi.monitor_enable(EventMonitor::Singlestep)?;
117
118 // Create a new view for the monitor.
119 // This view is used for monitoring function calls and memory accesses.
120 let view = vmi.create_view(MemoryAccess::RWX)?;
121 vmi.switch_to_view(view)?;
122
123 // Create a new breakpoint controller.
124 //
125 // The breakpoint controller is used to insert breakpoints for specific
126 // functions.
127 //
128 // From the guest's perspective, these breakpoints are "hidden", since
129 // the breakpoint controller will unset the read/write access to the
130 // physical memory page where the breakpoint is inserted, while keeping
131 // the execute access.
132 //
133 // This way, the guest will be able to execute the code, but attempts to
134 // read or write the memory will trigger the `memory_access` callback.
135 //
136 // When a VCPU tries to execute the breakpoint instruction:
137 // - an `interrupt` callback will be triggered
138 // - the breakpoint will be handled (e.g., log the function call)
139 // - a fast-singlestep[1] will be performed over the INT3 instruction
140 //
141 // When a VCPU tries to read from this page (e.g., a PatchGuard check):
142 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
143 // access type)
144 // - fast-singlestep[1] will be performed over the instruction that tried to
145 // read the memory
146 //
147 // This way, the instruction will read the original memory content.
148 //
149 // [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
150 // to a different view, execute a single instruction, and then
151 // switch back to the original view. In this case, the view is
152 // switched to the `default_view` (which is unmodified).
153 let mut bpm = BreakpointManager::new();
154
155 // Create a new page table monitor.
156 //
157 // The page table monitor is used to monitor the page table entries of
158 // the hooked functions.
159 //
160 // More specifically, it is used to monitor the pages that the breakpoint
161 // was inserted into. This is necessary to handle the case when the
162 // page containing the breakpoint is paged out (and then paged in
163 // again).
164 //
165 // `PageTableMonitor` works by unsetting the write access to the page
166 // tables of the hooked functions. When the page is paged out, the
167 // `PRESENT` bit in the page table entry is unset and, conversely, when
168 // the page is paged in, the `PRESENT` bit is set again.
169 //
170 // When that happens:
171 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
172 // access type)
173 // - the callback will mark the page as dirty in the page table monitor
174 // - a singlestep will be performed over the instruction that tried to modify
175 // the memory containing the page table entry
176 // - the `singlestep` handler will process the dirty page table entries and
177 // inform the breakpoint controller to handle the changes
178 let mut ptm = PageTableMonitor::new();
179
180 // Pause the VM to avoid race conditions between inserting breakpoints
181 // and monitoring page table entries. The VM resumes when the pause
182 // guard is dropped.
183 let _pause_guard = vmi.pause_guard()?;
184
185 // Insert breakpoint for the `NtCreateFile` function.
186 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
187 let cx_NtCreateFile = (va_NtCreateFile, root);
188 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
189 .global()
190 .with_tag("NtCreateFile");
191 bpm.insert(&vmi, bp_NtCreateFile)?;
192 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
193 tracing::info!(%va_NtCreateFile);
194
195 // Insert breakpoint for the `NtWriteFile` function.
196 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
197 let cx_NtWriteFile = (va_NtWriteFile, root);
198 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
199 .global()
200 .with_tag("NtWriteFile");
201 bpm.insert(&vmi, bp_NtWriteFile)?;
202 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
203 tracing::info!(%va_NtWriteFile);
204
205 // Insert breakpoint for the `PspInsertProcess` function.
206 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
207 let cx_PspInsertProcess = (va_PspInsertProcess, root);
208 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
209 .global()
210 .with_tag("PspInsertProcess");
211 bpm.insert(&vmi, bp_PspInsertProcess)?;
212 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
213
214 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
215 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
216 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
217 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
218 .global()
219 .with_tag("MmCleanProcessAddressSpace");
220 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
221 ptm.monitor(
222 &vmi,
223 cx_MmCleanProcessAddressSpace,
224 view,
225 "MmCleanProcessAddressSpace",
226 )?;
227
228 Ok(Self {
229 terminate_flag,
230 view,
231 bpm,
232 ptm,
233 })
234 }Sourcepub fn without_os(&self) -> VmiSession<'a, Driver>
Available on crate feature utils only.
pub fn without_os(&self) -> VmiSession<'a, Driver>
utils only.Creates a new VMI session without an OS-specific implementation.
Sourcepub fn core(&self) -> &'a VmiCore<Driver>
Available on crate feature utils only.
pub fn core(&self) -> &'a VmiCore<Driver>
utils only.Returns the VMI core.
Sourcepub fn underlying_os(&self) -> &'a Os
Available on crate feature utils only.
pub fn underlying_os(&self) -> &'a Os
utils only.Returns the underlying OS-specific implementation.
Sourcepub fn wait_for_event(
&self,
timeout: Duration,
handler: &mut impl VmiHandler<Driver, Os>,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn wait_for_event( &self, timeout: Duration, handler: &mut impl VmiHandler<Driver, Os>, ) -> Result<(), VmiError>
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.
Sourcepub fn handle<Handler>(
&self,
handler_factory: impl FnOnce(&VmiSession<'_, Driver, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Driver, Os>>::Output>, VmiError>where
Handler: VmiHandler<Driver, Os>,
Available on crate feature utils only.
pub fn handle<Handler>(
&self,
handler_factory: impl FnOnce(&VmiSession<'_, Driver, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Driver, Os>>::Output>, VmiError>where
Handler: VmiHandler<Driver, Os>,
utils only.Enters the main event handling loop that processes VMI events until finished.
Examples found in repository?
64fn main() -> Result<(), Box<dyn std::error::Error>> {
65 let (session, profile) = common::create_vmi_session()?;
66
67 let explorer_pid = {
68 // This block is used to drop the pause guard after the PID is found.
69 // If the `session.handle()` would be called with the VM paused, no
70 // events would be triggered.
71 let _pause_guard = session.pause_guard()?;
72
73 let registers = session.registers(VcpuId(0))?;
74 let vmi = session.with_registers(®isters);
75
76 let explorer = match common::find_process(&vmi, "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 InjectorHandler::new(
95 session,
96 &profile,
97 explorer_pid,
98 recipe_factory(MessageBox::new(
99 "Hello, World!",
100 "This is a message box from the VMI!",
101 )),
102 )
103 })?;
104
105 Ok(())
106}More examples
202fn main() -> Result<(), Box<dyn std::error::Error>> {
203 let (session, profile) = common::create_vmi_session()?;
204
205 let explorer_pid = {
206 // This block is used to drop the pause guard after the PID is found.
207 // If the `session.handle()` would be called with the VM paused, no
208 // events would be triggered.
209 let _pause_guard = session.pause_guard()?;
210
211 let registers = session.registers(VcpuId(0))?;
212 let vmi = session.with_registers(®isters);
213
214 let explorer = match common::find_process(&vmi, "explorer.exe")? {
215 Some(explorer) => explorer,
216 None => {
217 tracing::error!("explorer.exe not found");
218 return Ok(());
219 }
220 };
221
222 tracing::info!(
223 pid = %explorer.id()?,
224 object = %explorer.object()?,
225 "found explorer.exe"
226 );
227
228 explorer.id()?
229 };
230
231 session.handle(|session| {
232 InjectorHandler::new(
233 session,
234 &profile,
235 explorer_pid,
236 recipe_factory(GuestFile::new(
237 "C:\\Users\\John\\Desktop\\test.txt",
238 "Hello, World!".as_bytes(),
239 )),
240 )
241 })?;
242
243 Ok(())
244}297fn main() -> Result<(), Box<dyn std::error::Error>> {
298 let (session, profile) = common::create_vmi_session()?;
299
300 let explorer_pid = {
301 // This block is used to drop the pause guard after the PID is found.
302 // If the `session.handle()` would be called with the VM paused, no
303 // events would be triggered.
304 let _pause_guard = session.pause_guard()?;
305
306 let registers = session.registers(VcpuId(0))?;
307 let vmi = session.with_registers(®isters);
308
309 let explorer = match common::find_process(&vmi, "explorer.exe")? {
310 Some(explorer) => explorer,
311 None => {
312 tracing::error!("explorer.exe not found");
313 return Ok(());
314 }
315 };
316
317 tracing::info!(
318 pid = %explorer.id()?,
319 object = %explorer.object()?,
320 "found explorer.exe"
321 );
322
323 explorer.id()?
324 };
325
326 let mut content = Vec::new();
327 for c in 'A'..='Z' {
328 content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
329 }
330
331 session.handle(|session| {
332 InjectorHandler::new(
333 session,
334 &profile,
335 explorer_pid,
336 recipe_factory(GuestFile::new(
337 "C:\\Users\\John\\Desktop\\test.txt",
338 content,
339 )),
340 )
341 })?;
342
343 Ok(())
344}563fn main() -> Result<(), Box<dyn std::error::Error>> {
564 tracing_subscriber::fmt()
565 .with_max_level(tracing::Level::DEBUG)
566 .init();
567
568 let domain_id = 'x: {
569 for name in &["win7", "win10", "win11", "ubuntu22"] {
570 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
571 break 'x domain_id;
572 }
573 }
574
575 panic!("Domain not found");
576 };
577
578 tracing::debug!(?domain_id);
579
580 // Setup VMI.
581 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
582 let core = VmiCore::new(driver)?;
583
584 // Try to find the kernel information.
585 // This is necessary in order to load the profile.
586 let kernel_info = {
587 let _pause_guard = core.pause_guard()?;
588 let regs = core.registers(0.into())?;
589
590 WindowsOs::find_kernel(&core, ®s)?.expect("kernel information")
591 };
592
593 // Load the profile.
594 // The profile contains offsets to kernel functions and data structures.
595 let isr = IsrCache::<JsonCodec>::new("cache")?;
596 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
597 let profile = entry.profile()?;
598
599 // Create the VMI session.
600 tracing::info!("Creating VMI session");
601 let terminate_flag = Arc::new(AtomicBool::new(false));
602 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
603 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
604 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
605 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
606
607 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
608 let session = VmiSession::new(&core, &os);
609
610 session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
611
612 Ok(())
613}Sourcepub fn handle_with_timeout<Handler>(
&self,
timeout: Duration,
handler_factory: impl FnOnce(&VmiSession<'_, Driver, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Driver, Os>>::Output>, VmiError>where
Handler: VmiHandler<Driver, Os>,
Available on crate feature utils only.
pub fn handle_with_timeout<Handler>(
&self,
timeout: Duration,
handler_factory: impl FnOnce(&VmiSession<'_, Driver, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Driver, Os>>::Output>, VmiError>where
Handler: VmiHandler<Driver, Os>,
utils only.Enters the main event handling loop that processes VMI events until finished, with a timeout for each event.
Methods from Deref<Target = VmiCore<Driver>>§
Sourcepub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage>
Available on crate feature utils only.
pub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage>
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.
Sourcepub fn flush_gfn_cache(&self)
Available on crate feature utils only.
pub fn flush_gfn_cache(&self)
utils only.Clears the entire GFN cache.
Sourcepub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa>
Available on crate feature utils only.
pub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa>
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.
Sourcepub fn flush_v2p_cache(&self)
Available on crate feature utils only.
pub fn flush_v2p_cache(&self)
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.
Sourcepub fn read_string_length_limit(&self) -> Option<usize>
Available on crate feature utils only.
pub fn read_string_length_limit(&self) -> Option<usize>
utils only.Returns the current limit on the length of strings read by the
read_string methods.
Sourcepub fn set_read_string_length_limit(&self, limit: usize)
Available on crate feature utils only.
pub fn set_read_string_length_limit(&self, limit: usize)
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.
Sourcepub fn driver(&self) -> &Driver
Available on crate feature utils only.
pub fn driver(&self) -> &Driver
utils only.Returns the driver used by this VmiCore instance.
Sourcepub fn info(&self) -> Result<VmiInfo, VmiError>
Available on crate feature utils only.
pub fn info(&self) -> Result<VmiInfo, VmiError>
utils only.Retrieves information about the virtual machine.
Examples found in repository?
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let domain_id = 'x: {
8 for name in &["win7", "win10", "win11", "ubuntu22"] {
9 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
10 break 'x domain_id;
11 }
12 }
13
14 panic!("Domain not found");
15 };
16
17 // Setup VMI.
18 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
19 let vmi = VmiCore::new(driver)?;
20
21 // Get the interrupt descriptor table for each VCPU and print it.
22 let _pause_guard = vmi.pause_guard()?;
23 let info = vmi.info()?;
24 for vcpu_id in 0..info.vcpus {
25 let registers = vmi.registers(VcpuId(vcpu_id))?;
26 let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
27
28 println!("IDT[{vcpu_id}]: {idt:#?}");
29 }
30
31 Ok(())
32}Sourcepub fn pause(&self) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn pause(&self) -> Result<(), VmiError>
utils only.Pauses the virtual machine.
Sourcepub fn resume(&self) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn resume(&self) -> Result<(), VmiError>
utils only.Resumes the virtual machine.
Sourcepub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError>
Available on crate feature utils only.
pub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError>
utils only.Pauses the virtual machine and returns a guard that will resume it when dropped.
Examples found in repository?
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let domain_id = 'x: {
8 for name in &["win7", "win10", "win11", "ubuntu22"] {
9 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
10 break 'x domain_id;
11 }
12 }
13
14 panic!("Domain not found");
15 };
16
17 // Setup VMI.
18 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
19 let vmi = VmiCore::new(driver)?;
20
21 // Get the interrupt descriptor table for each VCPU and print it.
22 let _pause_guard = vmi.pause_guard()?;
23 let info = vmi.info()?;
24 for vcpu_id in 0..info.vcpus {
25 let registers = vmi.registers(VcpuId(vcpu_id))?;
26 let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
27
28 println!("IDT[{vcpu_id}]: {idt:#?}");
29 }
30
31 Ok(())
32}More examples
64fn main() -> Result<(), Box<dyn std::error::Error>> {
65 let (session, profile) = common::create_vmi_session()?;
66
67 let explorer_pid = {
68 // This block is used to drop the pause guard after the PID is found.
69 // If the `session.handle()` would be called with the VM paused, no
70 // events would be triggered.
71 let _pause_guard = session.pause_guard()?;
72
73 let registers = session.registers(VcpuId(0))?;
74 let vmi = session.with_registers(®isters);
75
76 let explorer = match common::find_process(&vmi, "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 InjectorHandler::new(
95 session,
96 &profile,
97 explorer_pid,
98 recipe_factory(MessageBox::new(
99 "Hello, World!",
100 "This is a message box from the VMI!",
101 )),
102 )
103 })?;
104
105 Ok(())
106}202fn main() -> Result<(), Box<dyn std::error::Error>> {
203 let (session, profile) = common::create_vmi_session()?;
204
205 let explorer_pid = {
206 // This block is used to drop the pause guard after the PID is found.
207 // If the `session.handle()` would be called with the VM paused, no
208 // events would be triggered.
209 let _pause_guard = session.pause_guard()?;
210
211 let registers = session.registers(VcpuId(0))?;
212 let vmi = session.with_registers(®isters);
213
214 let explorer = match common::find_process(&vmi, "explorer.exe")? {
215 Some(explorer) => explorer,
216 None => {
217 tracing::error!("explorer.exe not found");
218 return Ok(());
219 }
220 };
221
222 tracing::info!(
223 pid = %explorer.id()?,
224 object = %explorer.object()?,
225 "found explorer.exe"
226 );
227
228 explorer.id()?
229 };
230
231 session.handle(|session| {
232 InjectorHandler::new(
233 session,
234 &profile,
235 explorer_pid,
236 recipe_factory(GuestFile::new(
237 "C:\\Users\\John\\Desktop\\test.txt",
238 "Hello, World!".as_bytes(),
239 )),
240 )
241 })?;
242
243 Ok(())
244}297fn main() -> Result<(), Box<dyn std::error::Error>> {
298 let (session, profile) = common::create_vmi_session()?;
299
300 let explorer_pid = {
301 // This block is used to drop the pause guard after the PID is found.
302 // If the `session.handle()` would be called with the VM paused, no
303 // events would be triggered.
304 let _pause_guard = session.pause_guard()?;
305
306 let registers = session.registers(VcpuId(0))?;
307 let vmi = session.with_registers(®isters);
308
309 let explorer = match common::find_process(&vmi, "explorer.exe")? {
310 Some(explorer) => explorer,
311 None => {
312 tracing::error!("explorer.exe not found");
313 return Ok(());
314 }
315 };
316
317 tracing::info!(
318 pid = %explorer.id()?,
319 object = %explorer.object()?,
320 "found explorer.exe"
321 );
322
323 explorer.id()?
324 };
325
326 let mut content = Vec::new();
327 for c in 'A'..='Z' {
328 content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
329 }
330
331 session.handle(|session| {
332 InjectorHandler::new(
333 session,
334 &profile,
335 explorer_pid,
336 recipe_factory(GuestFile::new(
337 "C:\\Users\\John\\Desktop\\test.txt",
338 content,
339 )),
340 )
341 })?;
342
343 Ok(())
344}13pub fn create_vmi_session() -> Result<
14 (
15 VmiSession<'static, VmiXenDriver<Amd64>, WindowsOs<VmiXenDriver<Amd64>>>,
16 Profile<'static>,
17 ),
18 Box<dyn std::error::Error>,
19> {
20 tracing_subscriber::fmt()
21 .with_max_level(tracing::Level::DEBUG)
22 .with_target(false)
23 .init();
24
25 let domain_id = 'x: {
26 for name in &["win7", "win10", "win11", "ubuntu22"] {
27 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
28 break 'x domain_id;
29 }
30 }
31
32 panic!("Domain not found");
33 };
34
35 tracing::debug!(?domain_id);
36
37 // Setup VMI.
38 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
39 let core = VmiCore::new(driver)?;
40
41 // Try to find the kernel information.
42 // This is necessary in order to load the profile.
43 let kernel_info = {
44 // Pause the VCPU to get consistent state.
45 let _pause_guard = core.pause_guard()?;
46
47 // Get the register state for the first VCPU.
48 let registers = core.registers(VcpuId(0))?;
49
50 // On AMD64 architecture, the kernel is usually found using the
51 // `MSR_LSTAR` register, which contains the address of the system call
52 // handler. This register is set by the operating system during boot
53 // and is left unchanged (unless some rootkits are involved).
54 //
55 // Therefore, we can take an arbitrary registers at any point in time
56 // (as long as the OS has booted and the page tables are set up) and
57 // use them to find the kernel.
58 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
59 };
60
61 // Load the profile.
62 // The profile contains offsets to kernel functions and data structures.
63 let isr = IsrCache::<JsonCodec>::new("cache")?;
64 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
65 let entry = Box::leak(Box::new(entry));
66 let profile = entry.profile()?;
67
68 // Create the VMI session.
69 tracing::info!("Creating VMI session");
70 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
71
72 // Please don't do this in production code.
73 // This is only done for the sake of the example.
74 let core = Box::leak(Box::new(core));
75 let os = Box::leak(Box::new(os));
76
77 Ok((VmiSession::new(core, os), profile))
78}10fn main() -> Result<(), Box<dyn std::error::Error>> {
11 let domain_id = 'x: {
12 for name in &["win7", "win10", "win11", "ubuntu22"] {
13 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
14 break 'x domain_id;
15 }
16 }
17
18 panic!("Domain not found");
19 };
20
21 // Setup VMI.
22 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
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 VM 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, ®isters)?.expect("kernel information")
43 };
44
45 // Load the profile.
46 // The profile contains offsets to kernel functions and data structures.
47 let isr = IsrCache::<JsonCodec>::new("cache")?;
48 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
49 let profile = entry.profile()?;
50
51 // Create the VMI session.
52 tracing::info!("Creating VMI session");
53 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
54 let session = VmiSession::new(&core, &os);
55
56 // Pause the VM again to get consistent state.
57 let _pause_guard = session.pause_guard()?;
58
59 // Create a new `VmiState` with the current register.
60 let registers = session.registers(VcpuId(0))?;
61 let vmi = session.with_registers(®isters);
62
63 // Get the list of processes and print them.
64 for process in vmi.os().processes()? {
65 let process = process?;
66
67 println!(
68 "{} [{}] {} (root @ {})",
69 process.object()?,
70 process.id()?,
71 process.name()?,
72 process.translation_root()?
73 );
74 }
75
76 Ok(())
77}Sourcepub fn registers(
&self,
vcpu: VcpuId,
) -> Result<<<Driver as VmiDriver>::Architecture as Architecture>::Registers, VmiError>
Available on crate feature utils only.
pub fn registers( &self, vcpu: VcpuId, ) -> Result<<<Driver as VmiDriver>::Architecture as Architecture>::Registers, VmiError>
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?
6fn main() -> Result<(), Box<dyn std::error::Error>> {
7 let domain_id = 'x: {
8 for name in &["win7", "win10", "win11", "ubuntu22"] {
9 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
10 break 'x domain_id;
11 }
12 }
13
14 panic!("Domain not found");
15 };
16
17 // Setup VMI.
18 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
19 let vmi = VmiCore::new(driver)?;
20
21 // Get the interrupt descriptor table for each VCPU and print it.
22 let _pause_guard = vmi.pause_guard()?;
23 let info = vmi.info()?;
24 for vcpu_id in 0..info.vcpus {
25 let registers = vmi.registers(VcpuId(vcpu_id))?;
26 let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
27
28 println!("IDT[{vcpu_id}]: {idt:#?}");
29 }
30
31 Ok(())
32}More examples
64fn main() -> Result<(), Box<dyn std::error::Error>> {
65 let (session, profile) = common::create_vmi_session()?;
66
67 let explorer_pid = {
68 // This block is used to drop the pause guard after the PID is found.
69 // If the `session.handle()` would be called with the VM paused, no
70 // events would be triggered.
71 let _pause_guard = session.pause_guard()?;
72
73 let registers = session.registers(VcpuId(0))?;
74 let vmi = session.with_registers(®isters);
75
76 let explorer = match common::find_process(&vmi, "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 InjectorHandler::new(
95 session,
96 &profile,
97 explorer_pid,
98 recipe_factory(MessageBox::new(
99 "Hello, World!",
100 "This is a message box from the VMI!",
101 )),
102 )
103 })?;
104
105 Ok(())
106}202fn main() -> Result<(), Box<dyn std::error::Error>> {
203 let (session, profile) = common::create_vmi_session()?;
204
205 let explorer_pid = {
206 // This block is used to drop the pause guard after the PID is found.
207 // If the `session.handle()` would be called with the VM paused, no
208 // events would be triggered.
209 let _pause_guard = session.pause_guard()?;
210
211 let registers = session.registers(VcpuId(0))?;
212 let vmi = session.with_registers(®isters);
213
214 let explorer = match common::find_process(&vmi, "explorer.exe")? {
215 Some(explorer) => explorer,
216 None => {
217 tracing::error!("explorer.exe not found");
218 return Ok(());
219 }
220 };
221
222 tracing::info!(
223 pid = %explorer.id()?,
224 object = %explorer.object()?,
225 "found explorer.exe"
226 );
227
228 explorer.id()?
229 };
230
231 session.handle(|session| {
232 InjectorHandler::new(
233 session,
234 &profile,
235 explorer_pid,
236 recipe_factory(GuestFile::new(
237 "C:\\Users\\John\\Desktop\\test.txt",
238 "Hello, World!".as_bytes(),
239 )),
240 )
241 })?;
242
243 Ok(())
244}297fn main() -> Result<(), Box<dyn std::error::Error>> {
298 let (session, profile) = common::create_vmi_session()?;
299
300 let explorer_pid = {
301 // This block is used to drop the pause guard after the PID is found.
302 // If the `session.handle()` would be called with the VM paused, no
303 // events would be triggered.
304 let _pause_guard = session.pause_guard()?;
305
306 let registers = session.registers(VcpuId(0))?;
307 let vmi = session.with_registers(®isters);
308
309 let explorer = match common::find_process(&vmi, "explorer.exe")? {
310 Some(explorer) => explorer,
311 None => {
312 tracing::error!("explorer.exe not found");
313 return Ok(());
314 }
315 };
316
317 tracing::info!(
318 pid = %explorer.id()?,
319 object = %explorer.object()?,
320 "found explorer.exe"
321 );
322
323 explorer.id()?
324 };
325
326 let mut content = Vec::new();
327 for c in 'A'..='Z' {
328 content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
329 }
330
331 session.handle(|session| {
332 InjectorHandler::new(
333 session,
334 &profile,
335 explorer_pid,
336 recipe_factory(GuestFile::new(
337 "C:\\Users\\John\\Desktop\\test.txt",
338 content,
339 )),
340 )
341 })?;
342
343 Ok(())
344}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, ®isters)?.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::<JsonCodec>::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(®isters);
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}13pub fn create_vmi_session() -> Result<
14 (
15 VmiSession<'static, VmiXenDriver<Amd64>, WindowsOs<VmiXenDriver<Amd64>>>,
16 Profile<'static>,
17 ),
18 Box<dyn std::error::Error>,
19> {
20 tracing_subscriber::fmt()
21 .with_max_level(tracing::Level::DEBUG)
22 .with_target(false)
23 .init();
24
25 let domain_id = 'x: {
26 for name in &["win7", "win10", "win11", "ubuntu22"] {
27 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
28 break 'x domain_id;
29 }
30 }
31
32 panic!("Domain not found");
33 };
34
35 tracing::debug!(?domain_id);
36
37 // Setup VMI.
38 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
39 let core = VmiCore::new(driver)?;
40
41 // Try to find the kernel information.
42 // This is necessary in order to load the profile.
43 let kernel_info = {
44 // Pause the VCPU to get consistent state.
45 let _pause_guard = core.pause_guard()?;
46
47 // Get the register state for the first VCPU.
48 let registers = core.registers(VcpuId(0))?;
49
50 // On AMD64 architecture, the kernel is usually found using the
51 // `MSR_LSTAR` register, which contains the address of the system call
52 // handler. This register is set by the operating system during boot
53 // and is left unchanged (unless some rootkits are involved).
54 //
55 // Therefore, we can take an arbitrary registers at any point in time
56 // (as long as the OS has booted and the page tables are set up) and
57 // use them to find the kernel.
58 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
59 };
60
61 // Load the profile.
62 // The profile contains offsets to kernel functions and data structures.
63 let isr = IsrCache::<JsonCodec>::new("cache")?;
64 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
65 let entry = Box::leak(Box::new(entry));
66 let profile = entry.profile()?;
67
68 // Create the VMI session.
69 tracing::info!("Creating VMI session");
70 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
71
72 // Please don't do this in production code.
73 // This is only done for the sake of the example.
74 let core = Box::leak(Box::new(core));
75 let os = Box::leak(Box::new(os));
76
77 Ok((VmiSession::new(core, os), profile))
78}Sourcepub fn set_registers(
&self,
vcpu: VcpuId,
registers: <<Driver as VmiDriver>::Architecture as Architecture>::Registers,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn set_registers( &self, vcpu: VcpuId, registers: <<Driver as VmiDriver>::Architecture as Architecture>::Registers, ) -> Result<(), VmiError>
utils only.Sets the registers of a virtual CPU.
Sourcepub fn memory_access(
&self,
gfn: Gfn,
view: View,
) -> Result<MemoryAccess, VmiError>
Available on crate feature utils only.
pub fn memory_access( &self, gfn: Gfn, view: View, ) -> Result<MemoryAccess, VmiError>
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.
Sourcepub fn set_memory_access(
&self,
gfn: Gfn,
view: View,
access: MemoryAccess,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn set_memory_access( &self, gfn: Gfn, view: View, access: MemoryAccess, ) -> Result<(), VmiError>
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.
Sourcepub fn set_memory_access_with_options(
&self,
gfn: Gfn,
view: View,
access: MemoryAccess,
options: MemoryAccessOptions,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn set_memory_access_with_options( &self, gfn: Gfn, view: View, access: MemoryAccess, options: MemoryAccessOptions, ) -> Result<(), VmiError>
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.
Sourcepub fn allocate_next_available_gfn(&self) -> Result<Gfn, VmiError>
Available on crate feature utils only.
pub fn allocate_next_available_gfn(&self) -> Result<Gfn, VmiError>
utils only.Allocates the next available guest frame number (GFN).
This method finds and allocates the next free GFN after the current maximum GFN. It’s useful when you need to allocate new memory pages for the VM.
Sourcepub fn allocate_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn allocate_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
utils only.Allocates a specific guest frame number (GFN).
This method allows you to allocate a particular GFN. It’s useful when you need to allocate a specific memory page for the VM.
Sourcepub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
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.
Sourcepub fn default_view(&self) -> View
Available on crate feature utils only.
pub fn default_view(&self) -> View
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?
237 fn memory_access(
238 &mut self,
239 vmi: &VmiContext<'_, Driver, WindowsOs<Driver>>,
240 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
241 let memory_access = vmi.event().reason().as_memory_access();
242
243 tracing::trace!(
244 pa = %memory_access.pa,
245 va = %memory_access.va,
246 access = %memory_access.access,
247 );
248
249 if memory_access.access.contains(MemoryAccess::W) {
250 // It is assumed that a write memory access event is caused by a
251 // page table modification.
252 //
253 // The page table entry is marked as dirty in the page table monitor
254 // and a singlestep is performed to process the dirty entries.
255 self.ptm
256 .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
257
258 Ok(VmiEventResponse::toggle_singlestep().and_set_view(vmi.default_view()))
259 }
260 else if memory_access.access.contains(MemoryAccess::R) {
261 // When the guest tries to read from the memory, a fast-singlestep
262 // is performed over the instruction that tried to read the memory.
263 // This is done to allow the instruction to read the original memory
264 // content.
265 Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()))
266 }
267 else {
268 panic!("Unhandled memory access: {memory_access:?}");
269 }
270 }
271
272 #[tracing::instrument(skip_all, fields(pid, process))]
273 fn interrupt(
274 &mut self,
275 vmi: &VmiContext<'_, Driver, WindowsOs<Driver>>,
276 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
277 let tag = match self.bpm.get_by_event(vmi.event(), ()) {
278 Some(breakpoints) => {
279 // Breakpoints can have multiple tags, but we have set only one
280 // tag for each breakpoint.
281 let first_breakpoint = breakpoints.into_iter().next().expect("breakpoint");
282 first_breakpoint.tag()
283 }
284 None => {
285 if BreakpointController::is_breakpoint(vmi, vmi.event())? {
286 // This breakpoint was not set by us. Reinject it.
287 tracing::warn!("Unknown breakpoint, reinjecting");
288 return Ok(VmiEventResponse::reinject_interrupt());
289 }
290 else {
291 // We have received a breakpoint event, but there is no
292 // breakpoint instruction at the current memory location.
293 // This can happen if the event was triggered by a breakpoint
294 // we just removed.
295 tracing::warn!("Ignoring old breakpoint event");
296 return Ok(
297 VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view())
298 );
299 }
300 }
301 };
302
303 let process = vmi.os().current_process()?;
304 let process_id = process.id()?;
305 let process_name = process.name()?;
306 tracing::Span::current()
307 .record("pid", process_id.0)
308 .record("process", process_name);
309
310 match tag {
311 "NtCreateFile" => self.NtCreateFile(vmi)?,
312 "NtWriteFile" => self.NtWriteFile(vmi)?,
313 "PspInsertProcess" => self.PspInsertProcess(vmi)?,
314 "MmCleanProcessAddressSpace" => self.MmCleanProcessAddressSpace(vmi)?,
315 _ => panic!("Unhandled tag: {tag}"),
316 }
317
318 Ok(VmiEventResponse::toggle_fast_singlestep().and_set_view(vmi.default_view()))
319 }Sourcepub fn create_view(
&self,
default_access: MemoryAccess,
) -> Result<View, VmiError>
Available on crate feature utils only.
pub fn create_view( &self, default_access: MemoryAccess, ) -> Result<View, VmiError>
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?
63 pub fn new(
64 session: &VmiSession<Driver, WindowsOs<Driver>>,
65 profile: &Profile,
66 terminate_flag: Arc<AtomicBool>,
67 ) -> Result<Self, VmiError> {
68 // Capture the current state of the VCPU and get the base address of
69 // the kernel.
70 //
71 // This base address is essential to correctly offset monitored
72 // functions.
73 //
74 // NOTE: `kernel_image_base` tries to find the kernel in the memory
75 // with the help of the CPU registers. On AMD64 architecture,
76 // the kernel image base is usually found using the `MSR_LSTAR`
77 // register, which contains the address of the system call
78 // handler. This register is set by the operating system during
79 // boot and is left unchanged (unless some rootkits are involved).
80 //
81 // Therefore, we can take an arbitrary registers at any point
82 // in time (as long as the OS has booted and the page tables are
83 // set up) and use them to find the kernel image base.
84 let registers = session.registers(VcpuId(0))?;
85 let vmi = session.with_registers(®isters);
86
87 let kernel_image_base = vmi.os().kernel_image_base()?;
88 tracing::info!(%kernel_image_base);
89
90 // Get the system process.
91 //
92 // The system process is the first process created by the kernel.
93 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
94 // To monitor page table entries, we need to locate the translation root
95 // of this process.
96 let system_process = vmi.os().system_process()?;
97 tracing::info!(system_process = %system_process.object()?);
98
99 // Get the translation root of the system process.
100 // This is effectively "the CR3 of the kernel".
101 //
102 // The translation root is the root of the page table hierarchy (also
103 // known as the Directory Table Base or PML4).
104 let root = system_process.translation_root()?;
105 tracing::info!(%root);
106
107 // Load the symbols from the profile.
108 let symbols = Symbols::new(profile)?;
109
110 // Enable monitoring of the INT3 and singlestep events.
111 //
112 // INT3 is used to monitor the execution of specific functions.
113 // Singlestep is used to monitor the modifications of page table
114 // entries.
115 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
116 vmi.monitor_enable(EventMonitor::Singlestep)?;
117
118 // Create a new view for the monitor.
119 // This view is used for monitoring function calls and memory accesses.
120 let view = vmi.create_view(MemoryAccess::RWX)?;
121 vmi.switch_to_view(view)?;
122
123 // Create a new breakpoint controller.
124 //
125 // The breakpoint controller is used to insert breakpoints for specific
126 // functions.
127 //
128 // From the guest's perspective, these breakpoints are "hidden", since
129 // the breakpoint controller will unset the read/write access to the
130 // physical memory page where the breakpoint is inserted, while keeping
131 // the execute access.
132 //
133 // This way, the guest will be able to execute the code, but attempts to
134 // read or write the memory will trigger the `memory_access` callback.
135 //
136 // When a VCPU tries to execute the breakpoint instruction:
137 // - an `interrupt` callback will be triggered
138 // - the breakpoint will be handled (e.g., log the function call)
139 // - a fast-singlestep[1] will be performed over the INT3 instruction
140 //
141 // When a VCPU tries to read from this page (e.g., a PatchGuard check):
142 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
143 // access type)
144 // - fast-singlestep[1] will be performed over the instruction that tried to
145 // read the memory
146 //
147 // This way, the instruction will read the original memory content.
148 //
149 // [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
150 // to a different view, execute a single instruction, and then
151 // switch back to the original view. In this case, the view is
152 // switched to the `default_view` (which is unmodified).
153 let mut bpm = BreakpointManager::new();
154
155 // Create a new page table monitor.
156 //
157 // The page table monitor is used to monitor the page table entries of
158 // the hooked functions.
159 //
160 // More specifically, it is used to monitor the pages that the breakpoint
161 // was inserted into. This is necessary to handle the case when the
162 // page containing the breakpoint is paged out (and then paged in
163 // again).
164 //
165 // `PageTableMonitor` works by unsetting the write access to the page
166 // tables of the hooked functions. When the page is paged out, the
167 // `PRESENT` bit in the page table entry is unset and, conversely, when
168 // the page is paged in, the `PRESENT` bit is set again.
169 //
170 // When that happens:
171 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
172 // access type)
173 // - the callback will mark the page as dirty in the page table monitor
174 // - a singlestep will be performed over the instruction that tried to modify
175 // the memory containing the page table entry
176 // - the `singlestep` handler will process the dirty page table entries and
177 // inform the breakpoint controller to handle the changes
178 let mut ptm = PageTableMonitor::new();
179
180 // Pause the VM to avoid race conditions between inserting breakpoints
181 // and monitoring page table entries. The VM resumes when the pause
182 // guard is dropped.
183 let _pause_guard = vmi.pause_guard()?;
184
185 // Insert breakpoint for the `NtCreateFile` function.
186 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
187 let cx_NtCreateFile = (va_NtCreateFile, root);
188 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
189 .global()
190 .with_tag("NtCreateFile");
191 bpm.insert(&vmi, bp_NtCreateFile)?;
192 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
193 tracing::info!(%va_NtCreateFile);
194
195 // Insert breakpoint for the `NtWriteFile` function.
196 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
197 let cx_NtWriteFile = (va_NtWriteFile, root);
198 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
199 .global()
200 .with_tag("NtWriteFile");
201 bpm.insert(&vmi, bp_NtWriteFile)?;
202 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
203 tracing::info!(%va_NtWriteFile);
204
205 // Insert breakpoint for the `PspInsertProcess` function.
206 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
207 let cx_PspInsertProcess = (va_PspInsertProcess, root);
208 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
209 .global()
210 .with_tag("PspInsertProcess");
211 bpm.insert(&vmi, bp_PspInsertProcess)?;
212 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
213
214 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
215 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
216 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
217 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
218 .global()
219 .with_tag("MmCleanProcessAddressSpace");
220 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
221 ptm.monitor(
222 &vmi,
223 cx_MmCleanProcessAddressSpace,
224 view,
225 "MmCleanProcessAddressSpace",
226 )?;
227
228 Ok(Self {
229 terminate_flag,
230 view,
231 bpm,
232 ptm,
233 })
234 }Sourcepub fn destroy_view(&self, view: View) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn destroy_view(&self, view: View) -> Result<(), VmiError>
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.
Sourcepub fn switch_to_view(&self, view: View) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn switch_to_view(&self, view: View) -> Result<(), VmiError>
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::set_view():
switch_to_view()changes the view for all vCPUs immediately.VmiEventResponse::set_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::set_view() for targeted, event-specific view
modifications on individual vCPUs.
Examples found in repository?
63 pub fn new(
64 session: &VmiSession<Driver, WindowsOs<Driver>>,
65 profile: &Profile,
66 terminate_flag: Arc<AtomicBool>,
67 ) -> Result<Self, VmiError> {
68 // Capture the current state of the VCPU and get the base address of
69 // the kernel.
70 //
71 // This base address is essential to correctly offset monitored
72 // functions.
73 //
74 // NOTE: `kernel_image_base` tries to find the kernel in the memory
75 // with the help of the CPU registers. On AMD64 architecture,
76 // the kernel image base is usually found using the `MSR_LSTAR`
77 // register, which contains the address of the system call
78 // handler. This register is set by the operating system during
79 // boot and is left unchanged (unless some rootkits are involved).
80 //
81 // Therefore, we can take an arbitrary registers at any point
82 // in time (as long as the OS has booted and the page tables are
83 // set up) and use them to find the kernel image base.
84 let registers = session.registers(VcpuId(0))?;
85 let vmi = session.with_registers(®isters);
86
87 let kernel_image_base = vmi.os().kernel_image_base()?;
88 tracing::info!(%kernel_image_base);
89
90 // Get the system process.
91 //
92 // The system process is the first process created by the kernel.
93 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
94 // To monitor page table entries, we need to locate the translation root
95 // of this process.
96 let system_process = vmi.os().system_process()?;
97 tracing::info!(system_process = %system_process.object()?);
98
99 // Get the translation root of the system process.
100 // This is effectively "the CR3 of the kernel".
101 //
102 // The translation root is the root of the page table hierarchy (also
103 // known as the Directory Table Base or PML4).
104 let root = system_process.translation_root()?;
105 tracing::info!(%root);
106
107 // Load the symbols from the profile.
108 let symbols = Symbols::new(profile)?;
109
110 // Enable monitoring of the INT3 and singlestep events.
111 //
112 // INT3 is used to monitor the execution of specific functions.
113 // Singlestep is used to monitor the modifications of page table
114 // entries.
115 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
116 vmi.monitor_enable(EventMonitor::Singlestep)?;
117
118 // Create a new view for the monitor.
119 // This view is used for monitoring function calls and memory accesses.
120 let view = vmi.create_view(MemoryAccess::RWX)?;
121 vmi.switch_to_view(view)?;
122
123 // Create a new breakpoint controller.
124 //
125 // The breakpoint controller is used to insert breakpoints for specific
126 // functions.
127 //
128 // From the guest's perspective, these breakpoints are "hidden", since
129 // the breakpoint controller will unset the read/write access to the
130 // physical memory page where the breakpoint is inserted, while keeping
131 // the execute access.
132 //
133 // This way, the guest will be able to execute the code, but attempts to
134 // read or write the memory will trigger the `memory_access` callback.
135 //
136 // When a VCPU tries to execute the breakpoint instruction:
137 // - an `interrupt` callback will be triggered
138 // - the breakpoint will be handled (e.g., log the function call)
139 // - a fast-singlestep[1] will be performed over the INT3 instruction
140 //
141 // When a VCPU tries to read from this page (e.g., a PatchGuard check):
142 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
143 // access type)
144 // - fast-singlestep[1] will be performed over the instruction that tried to
145 // read the memory
146 //
147 // This way, the instruction will read the original memory content.
148 //
149 // [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
150 // to a different view, execute a single instruction, and then
151 // switch back to the original view. In this case, the view is
152 // switched to the `default_view` (which is unmodified).
153 let mut bpm = BreakpointManager::new();
154
155 // Create a new page table monitor.
156 //
157 // The page table monitor is used to monitor the page table entries of
158 // the hooked functions.
159 //
160 // More specifically, it is used to monitor the pages that the breakpoint
161 // was inserted into. This is necessary to handle the case when the
162 // page containing the breakpoint is paged out (and then paged in
163 // again).
164 //
165 // `PageTableMonitor` works by unsetting the write access to the page
166 // tables of the hooked functions. When the page is paged out, the
167 // `PRESENT` bit in the page table entry is unset and, conversely, when
168 // the page is paged in, the `PRESENT` bit is set again.
169 //
170 // When that happens:
171 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
172 // access type)
173 // - the callback will mark the page as dirty in the page table monitor
174 // - a singlestep will be performed over the instruction that tried to modify
175 // the memory containing the page table entry
176 // - the `singlestep` handler will process the dirty page table entries and
177 // inform the breakpoint controller to handle the changes
178 let mut ptm = PageTableMonitor::new();
179
180 // Pause the VM to avoid race conditions between inserting breakpoints
181 // and monitoring page table entries. The VM resumes when the pause
182 // guard is dropped.
183 let _pause_guard = vmi.pause_guard()?;
184
185 // Insert breakpoint for the `NtCreateFile` function.
186 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
187 let cx_NtCreateFile = (va_NtCreateFile, root);
188 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
189 .global()
190 .with_tag("NtCreateFile");
191 bpm.insert(&vmi, bp_NtCreateFile)?;
192 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
193 tracing::info!(%va_NtCreateFile);
194
195 // Insert breakpoint for the `NtWriteFile` function.
196 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
197 let cx_NtWriteFile = (va_NtWriteFile, root);
198 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
199 .global()
200 .with_tag("NtWriteFile");
201 bpm.insert(&vmi, bp_NtWriteFile)?;
202 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
203 tracing::info!(%va_NtWriteFile);
204
205 // Insert breakpoint for the `PspInsertProcess` function.
206 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
207 let cx_PspInsertProcess = (va_PspInsertProcess, root);
208 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
209 .global()
210 .with_tag("PspInsertProcess");
211 bpm.insert(&vmi, bp_PspInsertProcess)?;
212 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
213
214 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
215 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
216 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
217 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
218 .global()
219 .with_tag("MmCleanProcessAddressSpace");
220 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
221 ptm.monitor(
222 &vmi,
223 cx_MmCleanProcessAddressSpace,
224 view,
225 "MmCleanProcessAddressSpace",
226 )?;
227
228 Ok(Self {
229 terminate_flag,
230 view,
231 bpm,
232 ptm,
233 })
234 }Sourcepub fn change_view_gfn(
&self,
view: View,
old_gfn: Gfn,
new_gfn: Gfn,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn change_view_gfn( &self, view: View, old_gfn: Gfn, new_gfn: Gfn, ) -> Result<(), VmiError>
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”:
- Create a new GFN and copy the contents of the original page to it.
- Modify the new page by installing a breakpoint (e.g., 0xcc on AMD64) at a strategic location.
- Use this method to change the mapping of the original GFN to the new one.
- 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.
Sourcepub fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError>
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.
Sourcepub fn monitor_enable(
&self,
option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn monitor_enable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>
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?
63 pub fn new(
64 session: &VmiSession<Driver, WindowsOs<Driver>>,
65 profile: &Profile,
66 terminate_flag: Arc<AtomicBool>,
67 ) -> Result<Self, VmiError> {
68 // Capture the current state of the VCPU and get the base address of
69 // the kernel.
70 //
71 // This base address is essential to correctly offset monitored
72 // functions.
73 //
74 // NOTE: `kernel_image_base` tries to find the kernel in the memory
75 // with the help of the CPU registers. On AMD64 architecture,
76 // the kernel image base is usually found using the `MSR_LSTAR`
77 // register, which contains the address of the system call
78 // handler. This register is set by the operating system during
79 // boot and is left unchanged (unless some rootkits are involved).
80 //
81 // Therefore, we can take an arbitrary registers at any point
82 // in time (as long as the OS has booted and the page tables are
83 // set up) and use them to find the kernel image base.
84 let registers = session.registers(VcpuId(0))?;
85 let vmi = session.with_registers(®isters);
86
87 let kernel_image_base = vmi.os().kernel_image_base()?;
88 tracing::info!(%kernel_image_base);
89
90 // Get the system process.
91 //
92 // The system process is the first process created by the kernel.
93 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
94 // To monitor page table entries, we need to locate the translation root
95 // of this process.
96 let system_process = vmi.os().system_process()?;
97 tracing::info!(system_process = %system_process.object()?);
98
99 // Get the translation root of the system process.
100 // This is effectively "the CR3 of the kernel".
101 //
102 // The translation root is the root of the page table hierarchy (also
103 // known as the Directory Table Base or PML4).
104 let root = system_process.translation_root()?;
105 tracing::info!(%root);
106
107 // Load the symbols from the profile.
108 let symbols = Symbols::new(profile)?;
109
110 // Enable monitoring of the INT3 and singlestep events.
111 //
112 // INT3 is used to monitor the execution of specific functions.
113 // Singlestep is used to monitor the modifications of page table
114 // entries.
115 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
116 vmi.monitor_enable(EventMonitor::Singlestep)?;
117
118 // Create a new view for the monitor.
119 // This view is used for monitoring function calls and memory accesses.
120 let view = vmi.create_view(MemoryAccess::RWX)?;
121 vmi.switch_to_view(view)?;
122
123 // Create a new breakpoint controller.
124 //
125 // The breakpoint controller is used to insert breakpoints for specific
126 // functions.
127 //
128 // From the guest's perspective, these breakpoints are "hidden", since
129 // the breakpoint controller will unset the read/write access to the
130 // physical memory page where the breakpoint is inserted, while keeping
131 // the execute access.
132 //
133 // This way, the guest will be able to execute the code, but attempts to
134 // read or write the memory will trigger the `memory_access` callback.
135 //
136 // When a VCPU tries to execute the breakpoint instruction:
137 // - an `interrupt` callback will be triggered
138 // - the breakpoint will be handled (e.g., log the function call)
139 // - a fast-singlestep[1] will be performed over the INT3 instruction
140 //
141 // When a VCPU tries to read from this page (e.g., a PatchGuard check):
142 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
143 // access type)
144 // - fast-singlestep[1] will be performed over the instruction that tried to
145 // read the memory
146 //
147 // This way, the instruction will read the original memory content.
148 //
149 // [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
150 // to a different view, execute a single instruction, and then
151 // switch back to the original view. In this case, the view is
152 // switched to the `default_view` (which is unmodified).
153 let mut bpm = BreakpointManager::new();
154
155 // Create a new page table monitor.
156 //
157 // The page table monitor is used to monitor the page table entries of
158 // the hooked functions.
159 //
160 // More specifically, it is used to monitor the pages that the breakpoint
161 // was inserted into. This is necessary to handle the case when the
162 // page containing the breakpoint is paged out (and then paged in
163 // again).
164 //
165 // `PageTableMonitor` works by unsetting the write access to the page
166 // tables of the hooked functions. When the page is paged out, the
167 // `PRESENT` bit in the page table entry is unset and, conversely, when
168 // the page is paged in, the `PRESENT` bit is set again.
169 //
170 // When that happens:
171 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
172 // access type)
173 // - the callback will mark the page as dirty in the page table monitor
174 // - a singlestep will be performed over the instruction that tried to modify
175 // the memory containing the page table entry
176 // - the `singlestep` handler will process the dirty page table entries and
177 // inform the breakpoint controller to handle the changes
178 let mut ptm = PageTableMonitor::new();
179
180 // Pause the VM to avoid race conditions between inserting breakpoints
181 // and monitoring page table entries. The VM resumes when the pause
182 // guard is dropped.
183 let _pause_guard = vmi.pause_guard()?;
184
185 // Insert breakpoint for the `NtCreateFile` function.
186 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
187 let cx_NtCreateFile = (va_NtCreateFile, root);
188 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
189 .global()
190 .with_tag("NtCreateFile");
191 bpm.insert(&vmi, bp_NtCreateFile)?;
192 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
193 tracing::info!(%va_NtCreateFile);
194
195 // Insert breakpoint for the `NtWriteFile` function.
196 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
197 let cx_NtWriteFile = (va_NtWriteFile, root);
198 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
199 .global()
200 .with_tag("NtWriteFile");
201 bpm.insert(&vmi, bp_NtWriteFile)?;
202 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
203 tracing::info!(%va_NtWriteFile);
204
205 // Insert breakpoint for the `PspInsertProcess` function.
206 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
207 let cx_PspInsertProcess = (va_PspInsertProcess, root);
208 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
209 .global()
210 .with_tag("PspInsertProcess");
211 bpm.insert(&vmi, bp_PspInsertProcess)?;
212 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
213
214 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
215 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
216 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
217 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
218 .global()
219 .with_tag("MmCleanProcessAddressSpace");
220 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
221 ptm.monitor(
222 &vmi,
223 cx_MmCleanProcessAddressSpace,
224 view,
225 "MmCleanProcessAddressSpace",
226 )?;
227
228 Ok(Self {
229 terminate_flag,
230 view,
231 bpm,
232 ptm,
233 })
234 }Sourcepub fn monitor_disable(
&self,
option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn monitor_disable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>
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.
Sourcepub fn inject_interrupt(
&self,
vcpu: VcpuId,
interrupt: <<Driver as VmiDriver>::Architecture as Architecture>::Interrupt,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn inject_interrupt( &self, vcpu: VcpuId, interrupt: <<Driver as VmiDriver>::Architecture as Architecture>::Interrupt, ) -> Result<(), VmiError>
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?
514 fn dispatch(
515 &mut self,
516 vmi: &VmiContext<'_, Driver, WindowsOs<Driver>>,
517 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
518 let event = vmi.event();
519 let result = match event.reason() {
520 EventReason::MemoryAccess(_) => self.memory_access(vmi),
521 EventReason::Interrupt(_) => self.interrupt(vmi),
522 EventReason::Singlestep(_) => self.singlestep(vmi),
523 _ => panic!("Unhandled event: {:?}", event.reason()),
524 };
525
526 // If VMI tries to read from a page that is not present, it will return
527 // a page fault error. In this case, we inject a page fault interrupt
528 // to the guest.
529 //
530 // Once the guest handles the page fault, it will retry to execute the
531 // instruction that caused the page fault.
532 if let Err(VmiError::Translation(pfs)) = result {
533 tracing::warn!(?pfs, "Page fault, injecting");
534 vmi.inject_interrupt(event.vcpu_id(), Interrupt::page_fault(pfs[0].va, 0))?;
535 return Ok(VmiEventResponse::default());
536 }
537
538 result
539 }Sourcepub fn events_pending(&self) -> usize
Available on crate feature utils only.
pub fn events_pending(&self) -> usize
utils only.Returns the number of pending events.
This method provides a count of events that have occurred but have not yet been processed.
Sourcepub fn event_processing_overhead(&self) -> Duration
Available on crate feature utils only.
pub fn event_processing_overhead(&self) -> Duration
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.
Sourcepub 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 feature utils only.
pub fn wait_for_event( &self, timeout: Duration, handler: impl FnMut(&VmiEvent<<Driver as VmiDriver>::Architecture>) -> VmiEventResponse<<Driver as VmiDriver>::Architecture>, ) -> Result<(), VmiError>
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.
Sourcepub fn reset_state(&self) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn reset_state(&self) -> Result<(), VmiError>
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.
Sourcepub fn read(
&self,
ctx: impl Into<AccessContext>,
buffer: &mut [u8],
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn read( &self, ctx: impl Into<AccessContext>, buffer: &mut [u8], ) -> Result<(), VmiError>
utils only.Reads memory from the virtual machine.
Sourcepub fn write(
&self,
ctx: impl Into<AccessContext>,
buffer: &[u8],
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write( &self, ctx: impl Into<AccessContext>, buffer: &[u8], ) -> Result<(), VmiError>
utils only.Writes memory to the virtual machine.
Sourcepub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
Available on crate feature utils only.
pub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
utils only.Reads a single byte from the virtual machine.
Sourcepub fn read_u16(&self, ctx: impl Into<AccessContext>) -> Result<u16, VmiError>
Available on crate feature utils only.
pub fn read_u16(&self, ctx: impl Into<AccessContext>) -> Result<u16, VmiError>
utils only.Reads a 16-bit unsigned integer from the virtual machine.
Sourcepub fn read_u32(&self, ctx: impl Into<AccessContext>) -> Result<u32, VmiError>
Available on crate feature utils only.
pub fn read_u32(&self, ctx: impl Into<AccessContext>) -> Result<u32, VmiError>
utils only.Reads a 32-bit unsigned integer from the virtual machine.
Sourcepub fn read_u64(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_u64(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError>
utils only.Reads a 64-bit unsigned integer from the virtual machine.
Sourcepub fn read_uint(
&self,
ctx: impl Into<AccessContext>,
size: usize,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_uint( &self, ctx: impl Into<AccessContext>, size: usize, ) -> Result<u64, VmiError>
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.
Sourcepub fn read_field(
&self,
ctx: impl Into<AccessContext>,
field: &Field,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_field( &self, ctx: impl Into<AccessContext>, field: &Field, ) -> Result<u64, VmiError>
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.
Sourcepub fn read_address(
&self,
ctx: impl Into<AccessContext>,
address_width: usize,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<u64, VmiError>
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.
Sourcepub fn read_address32(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address32( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils only.Reads a 32-bit address from the virtual machine.
Sourcepub fn read_address64(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate feature utils only.
pub fn read_address64( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils only.Reads a 64-bit address from the virtual machine.
Sourcepub fn read_va(
&self,
ctx: impl Into<AccessContext>,
address_width: usize,
) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<Va, VmiError>
utils only.Reads a virtual address from the virtual machine.
Sourcepub fn read_va32(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va32(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
utils only.Reads a 32-bit virtual address from the virtual machine.
Sourcepub fn read_va64(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
Available on crate feature utils only.
pub fn read_va64(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
utils only.Reads a 64-bit virtual address from the virtual machine.
Sourcepub fn read_string_bytes_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<Vec<u8>, VmiError>
Available on crate feature utils only.
pub fn read_string_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u8>, VmiError>
utils only.Reads a null-terminated string of bytes from the virtual machine with a specified limit.
Sourcepub fn read_string_bytes(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u8>, VmiError>
Available on crate feature utils only.
pub fn read_string_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u8>, VmiError>
utils only.Reads a null-terminated string of bytes from the virtual machine.
Sourcepub fn read_wstring_bytes_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<Vec<u16>, VmiError>
Available on crate feature utils only.
pub fn read_wstring_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u16>, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring_bytes(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError>
Available on crate feature utils only.
pub fn read_wstring_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u16>, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine.
Sourcepub fn read_string_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_string_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
utils only.Reads a null-terminated string from the virtual machine with a specified limit.
Sourcepub fn read_string(
&self,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_string( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
utils only.Reads a null-terminated string from the virtual machine.
Sourcepub fn read_wstring_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_wstring_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring(
&self,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError>
Available on crate feature utils only.
pub fn read_wstring( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine.
Sourcepub fn read_struct<T>(
&self,
ctx: impl Into<AccessContext>,
) -> Result<T, VmiError>
Available on crate feature utils only.
pub fn read_struct<T>( &self, ctx: impl Into<AccessContext>, ) -> Result<T, VmiError>
utils only.Reads a struct from the virtual machine.
Sourcepub fn write_u8(
&self,
ctx: impl Into<AccessContext>,
value: u8,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_u8( &self, ctx: impl Into<AccessContext>, value: u8, ) -> Result<(), VmiError>
utils only.Writes a single byte to the virtual machine.
Sourcepub fn write_u16(
&self,
ctx: impl Into<AccessContext>,
value: u16,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_u16( &self, ctx: impl Into<AccessContext>, value: u16, ) -> Result<(), VmiError>
utils only.Writes a 16-bit unsigned integer to the virtual machine.
Sourcepub fn write_u32(
&self,
ctx: impl Into<AccessContext>,
value: u32,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_u32( &self, ctx: impl Into<AccessContext>, value: u32, ) -> Result<(), VmiError>
utils only.Writes a 32-bit unsigned integer to the virtual machine.
Sourcepub fn write_u64(
&self,
ctx: impl Into<AccessContext>,
value: u64,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_u64( &self, ctx: impl Into<AccessContext>, value: u64, ) -> Result<(), VmiError>
utils only.Writes a 64-bit unsigned integer to the virtual machine.
Sourcepub fn write_struct<T>(
&self,
ctx: impl Into<AccessContext>,
value: T,
) -> Result<(), VmiError>
Available on crate feature utils only.
pub fn write_struct<T>( &self, ctx: impl Into<AccessContext>, value: T, ) -> Result<(), VmiError>
utils only.Writes a struct to the virtual machine.
Sourcepub fn translate_address(
&self,
ctx: impl Into<AddressContext>,
) -> Result<Pa, VmiError>
Available on crate feature utils only.
pub fn translate_address( &self, ctx: impl Into<AddressContext>, ) -> Result<Pa, VmiError>
utils only.Translates a virtual address to a physical address.
Sourcepub fn translate_access_context(
&self,
ctx: AccessContext,
) -> Result<Pa, VmiError>
Available on crate feature utils only.
pub fn translate_access_context( &self, ctx: AccessContext, ) -> Result<Pa, VmiError>
utils only.Translates an access context to a physical address.