pub struct VmiState<'a, Os>where
Os: VmiOs,{ /* private fields */ }Expand description
A VMI state.
The state combines access to a VmiSession with Architecture::Registers
to provide unified access to VMI operations in the context of a specific
virtual machine state.
Implementations§
Source§impl<'a, Os> VmiState<'a, Os>where
Os: VmiOs,
impl<'a, Os> VmiState<'a, Os>where
Os: VmiOs,
Sourcepub fn new(
session: &'a VmiSession<'a, Os>,
registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers,
) -> VmiState<'a, Os>
Available on crate features injector and utils only.
pub fn new( session: &'a VmiSession<'a, Os>, registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Os>
injector and utils only.Creates a new VMI state.
Sourcepub fn with_registers(
&'a self,
registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers,
) -> VmiState<'a, Os>
Available on crate features injector and utils only.
pub fn with_registers( &'a self, registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Os>
injector and utils only.Creates a new VMI state with the specified registers.
Sourcepub fn without_os(&self) -> VmiState<'a, NoOS<<Os as VmiOs>::Driver>>
Available on crate features injector and utils only.
pub fn without_os(&self) -> VmiState<'a, NoOS<<Os as VmiOs>::Driver>>
injector and utils only.Creates a new VMI state without an OS-specific implementation.
Sourcepub fn session(&self) -> &VmiSession<'a, Os>
Available on crate features injector and utils only.
pub fn session(&self) -> &VmiSession<'a, Os>
injector and utils only.Returns the VMI session.
Sourcepub fn registers(
&self,
) -> &'a <<Os as VmiOs>::Architecture as Architecture>::Registers
Available on crate features injector and utils only.
pub fn registers( &self, ) -> &'a <<Os as VmiOs>::Architecture as Architecture>::Registers
injector and utils only.Returns the CPU registers associated with the current event.
Examples found in repository?
524pub fn KfdIsLayerEmpty<Driver>(
525 vmi: &VmiContext<WindowsOs<Driver>>,
526) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
527where
528 Driver: VmiRead,
529 Driver::Architecture: ArchAdapter<Driver>,
530{
531 //
532 // BOOLEAN
533 // NTAPI
534 // KfdIsLayerEmpty (
535 // _In_ UINT16 layerId
536 // );
537 //
538
539 let layerId = FwpsLayer(vmi.os().function_argument(0)? as u16);
540
541 if !matches!(
542 layerId,
543 FwpsLayer::ALE_AUTH_CONNECT_V4
544 | FwpsLayer::ALE_AUTH_CONNECT_V6
545 | FwpsLayer::ALE_FLOW_ESTABLISHED_V4
546 | FwpsLayer::ALE_FLOW_ESTABLISHED_V6
547 ) {
548 tracing::trace!(?layerId, "passing through");
549 return Ok(Action::default());
550 }
551
552 tracing::trace!(?layerId, "overriding");
553
554 let return_address = vmi.return_address()?;
555 let stack_pointer = vmi.registers().stack_pointer();
556 let address_width = vmi.registers().address_width() as u64;
557
558 let mut registers = vmi.registers().gp_registers();
559 registers.set_result(0); // Return FALSE
560 registers.set_instruction_pointer(return_address.into());
561 registers.set_stack_pointer(stack_pointer + address_width);
562
563 Ok(Action::Response(
564 VmiEventResponse::default().with_registers(registers),
565 ))
566}Sourcepub fn os(&self) -> VmiOsState<'a, Os>
Available on crate features injector and utils only.
pub fn os(&self) -> VmiOsState<'a, Os>
injector and utils only.Returns a wrapper providing access to OS-specific operations.
Examples found in repository?
76pub fn find_process<'a, Os>(
77 vmi: &VmiState<'a, Os>,
78 name: &str,
79) -> Result<Option<Os::Process<'a>>, VmiError>
80where
81 Os: VmiOs,
82 Os::Driver: VmiRead,
83{
84 for process in vmi.os().processes()? {
85 let process = process?;
86
87 if process.name()?.to_lowercase() == name {
88 return Ok(Some(process));
89 }
90 }
91
92 Ok(None)
93}More examples
102fn enumerate_kernel_modules(vmi: &VmiState<WindowsOs<Driver>>) -> Result<(), VmiError> {
103 for module in vmi.os().modules()? {
104 let module = module?;
105
106 let module_va = module.va();
107 let base_address = module.base_address()?; // `KLDR_DATA_TABLE_ENTRY.DllBase`
108 let size = module.size()?; // `KLDR_DATA_TABLE_ENTRY.SizeOfImage`
109 let name = module.name()?; // `KLDR_DATA_TABLE_ENTRY.BaseDllName`
110 let full_name = match module.full_name() {
111 // `KLDR_DATA_TABLE_ENTRY.FullDllName`
112 Ok(full_name) => full_name,
113 Err(err) => handle_error(err)?,
114 };
115
116 println!("Module @ {module_va}");
117 println!(" Base Address: {base_address}");
118 println!(" Size: {size}");
119 println!(" Name: {name}");
120 println!(" Full Name: {full_name}");
121 }
122
123 Ok(())
124}
125
126// Enumerate entries in a `_OBJECT_DIRECTORY`.
127fn enumerate_directory_object(
128 directory_object: &WindowsDirectoryObject<Driver>,
129 level: usize,
130) -> Result<(), VmiError> {
131 for object in directory_object.iter()? {
132 // Print the indentation.
133 for _ in 0..level {
134 print!(" ");
135 }
136
137 // Retrieve the `_OBJECT_DIRECTORY_ENTRY.Object`.
138 let object = match object {
139 Ok(object) => object,
140 Err(err) => {
141 println!("{}", handle_error(err)?);
142 continue;
143 }
144 };
145
146 let object_va = object.va();
147
148 // Determine the object type.
149 let type_kind = match object.type_kind() {
150 Ok(Some(typ)) => format!("{typ:?}"),
151 Ok(None) => String::from("<unknown>"),
152 Err(err) => handle_error(err)?,
153 };
154
155 print!("{type_kind}: ");
156
157 // Retrieve the full name of the object.
158 let name = match object.full_path() {
159 Ok(Some(name)) => name,
160 Ok(None) => String::from("<unnamed>"),
161 Err(err) => handle_error(err)?,
162 };
163
164 println!("{name} (Object: {object_va})");
165
166 // If the entry is a directory, recursively enumerate it.
167 if let Ok(Some(next)) = object.as_directory() {
168 enumerate_directory_object(&next, level + 1)?;
169 }
170 }
171
172 Ok(())
173}
174
175// Enumerate entries in a `_HANDLE_TABLE`.
176fn enumerate_handle_table(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
177 const OBJ_PROTECT_CLOSE: u32 = 0x00000001;
178 const OBJ_INHERIT: u32 = 0x00000002;
179 const OBJ_AUDIT_OBJECT_CLOSE: u32 = 0x00000004;
180
181 static LABEL_PROTECTED: [&str; 2] = ["", " (Protected)"];
182 static LABEL_INHERIT: [&str; 2] = ["", " (Inherit)"];
183 static LABEL_AUDIT: [&str; 2] = ["", " (Audit)"];
184
185 // Get the handle table from `_EPROCESS.ObjectTable`.
186 let handle_table = match process.handle_table() {
187 Ok(Some(handle_table)) => handle_table,
188 Ok(None) => {
189 println!(" (No handle table)");
190 return Ok(());
191 }
192 Err(err) => {
193 tracing::error!(%err, "Failed to get handle table");
194 return Ok(());
195 }
196 };
197
198 // Iterate over `_HANDLE_TABLE_ENTRY` items.
199 for handle_entry in handle_table.iter()? {
200 let (handle, entry) = match handle_entry {
201 Ok(entry) => entry,
202 Err(err) => {
203 println!("Failed to get handle entry: {}", handle_error(err)?);
204 continue;
205 }
206 };
207
208 let attributes = match entry.attributes() {
209 Ok(attributes) => attributes,
210 Err(err) => {
211 println!("Failed to get attributes: {}", handle_error(err)?);
212 continue;
213 }
214 };
215
216 let granted_access = match entry.granted_access() {
217 Ok(granted_access) => granted_access,
218 Err(err) => {
219 println!("Failed to get granted access: {}", handle_error(err)?);
220 continue;
221 }
222 };
223
224 let object = match entry.object() {
225 Ok(Some(object)) => object,
226 Ok(None) => {
227 // [`WindowsHandleTable::iter`] should only return entries with
228 // valid objects, so this should not happen.
229 println!("<NULL>");
230 continue;
231 }
232 Err(err) => {
233 println!("Failed to get object: {}", handle_error(err)?);
234 continue;
235 }
236 };
237
238 let type_name = match object.type_name() {
239 Ok(type_name) => type_name,
240 Err(err) => handle_error(err)?,
241 };
242
243 let full_path = match object.full_path() {
244 Ok(Some(path)) => path,
245 Ok(None) => String::from("<no-path>"),
246 Err(err) => handle_error(err)?,
247 };
248
249 println!(
250 " {:04x}: Object: {:x} GrantedAccess: {:08x}{}{}{} Entry: {}",
251 handle,
252 object.va().0,
253 granted_access,
254 LABEL_PROTECTED[((attributes & OBJ_PROTECT_CLOSE) != 0) as usize],
255 LABEL_INHERIT[((attributes & OBJ_INHERIT) != 0) as usize],
256 LABEL_AUDIT[((attributes & OBJ_AUDIT_OBJECT_CLOSE) != 0) as usize],
257 entry.va(),
258 );
259
260 println!(" Type: {type_name}, Path: {full_path}");
261 }
262
263 Ok(())
264}
265
266// Enumerate VADs in a process.
267fn enumerate_regions(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
268 for region in process.regions()? {
269 let region = region?;
270
271 let region_va = region.va();
272 let start = region.start()?;
273 let end = region.end()?;
274 let protection = region.protection()?;
275 let kind = region.kind()?;
276
277 print!(" Region @ {region_va}: {start}-{end} {protection:?}");
278
279 match &kind {
280 VmiOsRegionKind::Private => println!(" Private"),
281 VmiOsRegionKind::MappedImage(mapped) => {
282 let path = match mapped.path() {
283 Ok(Some(path)) => path,
284 Ok(None) => String::from("<Pagefile>"),
285 Err(err) => handle_error(err)?,
286 };
287
288 println!(" Mapped (Exe): {path}");
289 }
290 VmiOsRegionKind::MappedData(mapped) => {
291 let path = match mapped.path() {
292 Ok(Some(path)) => path,
293 Ok(None) => String::from("<Pagefile>"),
294 Err(err) => handle_error(err)?,
295 };
296
297 println!(" Mapped: {path}");
298 }
299 }
300 }
301
302 Ok(())
303}
304
305// Enumerate threads in a process.
306fn enumerate_threads(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
307 for thread in process.threads()? {
308 let thread = thread?;
309
310 let tid = thread.id()?;
311 let object = thread.object()?;
312
313 println!(" Thread @ {object}, TID: {tid}");
314 }
315
316 Ok(())
317}
318
319// Print process information in a `_PEB.ProcessParameters`.
320fn print_process_parameters(process: &WindowsProcess<Driver>) -> Result<(), VmiError> {
321 let peb = match process.peb() {
322 Ok(Some(peb)) => peb,
323 Ok(None) => {
324 println!(" (No PEB)");
325 return Ok(());
326 }
327 Err(err) => {
328 println!("Failed to get PEB: {}", handle_error(err)?);
329 return Ok(());
330 }
331 };
332
333 let current_directory = match peb.current_directory() {
334 Ok(current_directory) => current_directory,
335 Err(err) => handle_error(err)?,
336 };
337
338 let dll_path = match peb.dll_path() {
339 Ok(dll_path) => dll_path,
340 Err(err) => handle_error(err)?,
341 };
342
343 let image_path_name = match peb.image_path_name() {
344 Ok(image_path_name) => image_path_name,
345 Err(err) => handle_error(err)?,
346 };
347
348 let command_line = match peb.command_line() {
349 Ok(command_line) => command_line,
350 Err(err) => handle_error(err)?,
351 };
352
353 println!(" Current Directory: {current_directory}");
354 println!(" DLL Path: {dll_path}");
355 println!(" Image Path Name: {image_path_name}");
356 println!(" Command Line: {command_line}");
357
358 Ok(())
359}
360
361// Enumerate processes in the system.
362fn enumerate_processes(vmi: &VmiState<WindowsOs<Driver>>) -> Result<(), VmiError> {
363 for process in vmi.os().processes()? {
364 let process = process?;
365
366 let pid = process.id()?; // `_EPROCESS.UniqueProcessId`
367 let object = process.object()?; // `_EPROCESS` pointer
368 let name = process.name()?; // `_EPROCESS.ImageFileName`
369 let session = process.session()?; // `_EPROCESS.Session`
370
371 println!("Process @ {object}, PID: {pid}");
372 println!(" Name: {name}");
373 if let Some(session) = session {
374 println!(" Session: {}", session.id()?); // `_MM_SESSION_SPACE.SessionId`
375 }
376
377 println!(" Threads:");
378 enumerate_threads(&process)?;
379
380 println!(" Regions:");
381 enumerate_regions(&process)?;
382
383 println!(" PEB:");
384 print_process_parameters(&process)?;
385
386 println!(" Handles:");
387 enumerate_handle_table(&process)?;
388 }
389
390 Ok(())
391}
392
393fn main() -> Result<(), Box<dyn std::error::Error>> {
394 tracing_subscriber::fmt()
395 .with_max_level(tracing::Level::DEBUG)
396 .with_ansi(false)
397 .init();
398
399 // First argument is the path to the dump file.
400 let args = std::env::args().collect::<Vec<_>>();
401 if args.len() != 2 {
402 eprintln!("Usage: {} <dump-file>", args[0]);
403 std::process::exit(1);
404 }
405
406 let dump_file = &args[1];
407
408 // Setup VMI.
409 let driver = Driver::new(dump_file)?;
410 let core = VmiCore::new(driver)?;
411
412 let registers = core.registers(VcpuId(0))?;
413
414 // Try to find the kernel information.
415 // This is necessary in order to load the profile.
416 let kernel_info = WindowsOs::find_kernel(&core, ®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::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}13fn main() -> Result<(), Box<dyn std::error::Error>> {
14 let domain_id = 'x: {
15 for name in &["win7", "win10", "win11", "ubuntu22"] {
16 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
17 break 'x domain_id;
18 }
19 }
20
21 panic!("Domain not found");
22 };
23
24 // Setup VMI.
25 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
26 let core = VmiCore::new(driver)?;
27
28 // Try to find the kernel information.
29 // This is necessary in order to load the profile.
30 let kernel_info = {
31 // Pause the VM to get consistent state.
32 let _pause_guard = core.pause_guard()?;
33
34 // Get the register state for the first vCPU.
35 let registers = core.registers(VcpuId(0))?;
36
37 // On AMD64 architecture, the kernel is usually found using the
38 // `MSR_LSTAR` register, which contains the address of the system call
39 // handler. This register is set by the operating system during boot
40 // and is left unchanged (unless some rootkits are involved).
41 //
42 // Therefore, we can take an arbitrary registers at any point in time
43 // (as long as the OS has booted and the page tables are set up) and
44 // use them to find the kernel.
45 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
46 };
47
48 // Load the profile.
49 // The profile contains offsets to kernel functions and data structures.
50 let isr = IsrCache::new("cache")?;
51 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
52 let profile = entry.profile()?;
53
54 // Create the VMI session.
55 tracing::info!("Creating VMI session");
56 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
57 let session = VmiSession::new(&core, &os);
58
59 // Pause the VM again to get consistent state.
60 let paused = session.pause_guard()?;
61
62 // Create a new `VmiState` with the boot CPU registers.
63 let vmi = paused.state();
64
65 // Get the list of processes and print them.
66 for process in vmi.os().processes()? {
67 let process = process?;
68
69 println!(
70 "{} [{}] {} (root @ {})",
71 process.object()?,
72 process.id()?,
73 process.name()?,
74 process.translation_root()?
75 );
76 }
77
78 Ok(())
79}67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Sourcepub fn access_context(&self, address: Va) -> AccessContext
Available on crate features injector and utils only.
pub fn access_context(&self, address: Va) -> AccessContext
injector and utils only.Creates an address context for a given virtual address.
Sourcepub fn address_context(&self, address: Va) -> AddressContext
Available on crate features injector and utils only.
pub fn address_context(&self, address: Va) -> AddressContext
injector and utils only.Creates an address context for a given virtual address.
Sourcepub fn translation_root(&self, va: Va) -> Pa
Available on crate features injector and utils only.
pub fn translation_root(&self, va: Va) -> Pa
injector and utils only.Returns the physical address of the root of the current page table hierarchy for a given virtual address.
Source§impl<'a, Os> VmiState<'a, Os>
impl<'a, Os> VmiState<'a, Os>
Sourcepub fn return_address(&self) -> Result<Va, VmiError>
Available on crate features injector and utils only.
pub fn return_address(&self) -> Result<Va, VmiError>
injector and utils only.Returns the return address from the current stack frame.
Examples found in repository?
524pub fn KfdIsLayerEmpty<Driver>(
525 vmi: &VmiContext<WindowsOs<Driver>>,
526) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
527where
528 Driver: VmiRead,
529 Driver::Architecture: ArchAdapter<Driver>,
530{
531 //
532 // BOOLEAN
533 // NTAPI
534 // KfdIsLayerEmpty (
535 // _In_ UINT16 layerId
536 // );
537 //
538
539 let layerId = FwpsLayer(vmi.os().function_argument(0)? as u16);
540
541 if !matches!(
542 layerId,
543 FwpsLayer::ALE_AUTH_CONNECT_V4
544 | FwpsLayer::ALE_AUTH_CONNECT_V6
545 | FwpsLayer::ALE_FLOW_ESTABLISHED_V4
546 | FwpsLayer::ALE_FLOW_ESTABLISHED_V6
547 ) {
548 tracing::trace!(?layerId, "passing through");
549 return Ok(Action::default());
550 }
551
552 tracing::trace!(?layerId, "overriding");
553
554 let return_address = vmi.return_address()?;
555 let stack_pointer = vmi.registers().stack_pointer();
556 let address_width = vmi.registers().address_width() as u64;
557
558 let mut registers = vmi.registers().gp_registers();
559 registers.set_result(0); // Return FALSE
560 registers.set_instruction_pointer(return_address.into());
561 registers.set_stack_pointer(stack_pointer + address_width);
562
563 Ok(Action::Response(
564 VmiEventResponse::default().with_registers(registers),
565 ))
566}Sourcepub fn translate_address(&self, va: Va) -> Result<Pa, VmiError>
Available on crate features injector and utils only.
pub fn translate_address(&self, va: Va) -> Result<Pa, VmiError>
injector and utils only.Translates a virtual address to a physical address.
Sourcepub fn read(&self, address: Va, buffer: &mut [u8]) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn read(&self, address: Va, buffer: &mut [u8]) -> Result<(), VmiError>
injector and utils only.Reads memory from the virtual machine.
Examples found in repository?
63pub fn SslGenerateSessionKeys<Driver>(
64 vmi: &VmiContext<WindowsOs<Driver>>,
65) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
66where
67 Driver: VmiRead,
68 Driver::Architecture: ArchAdapter<Driver>,
69{
70 //
71 // SECURITY_STATUS
72 // WINAPI
73 // SslGenerateSessionKeys(
74 // _In_ NCRYPT_PROV_HANDLE hSslProvider,
75 // _In_ NCRYPT_KEY_HANDLE hMasterKey,
76 // _Out_ NCRYPT_KEY_HANDLE *phReadKey,
77 // _Out_ NCRYPT_KEY_HANDLE *phWriteKey,
78 // _In_ PNCryptBufferDesc pParameterList,
79 // _In_ DWORD dwFlags
80 // );
81 //
82
83 let hMasterKey = vmi.os().function_argument(1)?;
84 let pParameterList = vmi.os().function_argument(4)?;
85
86 let mut client_random = vec![0u8; 32];
87
88 let parameter_list = vmi.read_struct::<NCryptBufferDesc>(Va(pParameterList))?;
89 for i in 0..parameter_list.cBuffers {
90 let offset = (i as u64) * size_of::<NCryptBuffer>() as u64;
91 let buffer = vmi.read_struct::<NCryptBuffer>(Va(parameter_list.pBuffers + offset))?;
92
93 if buffer.BufferType == NCRYPTBUFFER_SSL_CLIENT_RANDOM {
94 vmi.read(Va(buffer.pvBuffer), &mut client_random)?;
95 break;
96 }
97 }
98
99 let master_key = vmi.read_struct::<NCRYPT_SSL_KEY>(Va(hMasterKey))?;
100 let subkey = vmi.read_struct::<SSL_MASTER_KEY>(Va(master_key.hSubKey))?;
101
102 tracing::info!(
103 client_random = hex::encode(client_random),
104 secret = hex::encode(subkey.rgbMasterKey),
105 );
106
107 Ok(Action::default())
108}Sourcepub fn read_u8(&self, address: Va) -> Result<u8, VmiError>
Available on crate features injector and utils only.
pub fn read_u8(&self, address: Va) -> Result<u8, VmiError>
injector and utils only.Reads a single byte from the virtual machine.
Sourcepub fn read_u16(&self, address: Va) -> Result<u16, VmiError>
Available on crate features injector and utils only.
pub fn read_u16(&self, address: Va) -> Result<u16, VmiError>
injector and 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 features injector and utils only.
pub fn read_u32(&self, address: Va) -> Result<u32, VmiError>
injector and utils only.Reads a 32-bit unsigned integer from the virtual machine.
Examples found in repository?
95fn recipe_factory<Driver>(data: GuestFile) -> Recipe<WindowsOs<Driver>, GuestFile>
96where
97 Driver: VmiFullDriver<Architecture = Amd64>,
98{
99 recipe![
100 Recipe::<WindowsOs<Driver>>::new(data),
101 //
102 // Step 1:
103 // - Create a file
104 //
105 {
106 tracing::info!(
107 target_path = data![target_path],
108 "step 1: kernel32!CreateFileA()"
109 );
110
111 const GENERIC_WRITE: u64 = 0x40000000;
112 const CREATE_ALWAYS: u64 = 2;
113 const FILE_ATTRIBUTE_NORMAL: u64 = 0x80;
114
115 inject! {
116 kernel32!CreateFileA(
117 &data![target_path], // lpFileName
118 GENERIC_WRITE, // dwDesiredAccess
119 0, // dwShareMode
120 0, // lpSecurityAttributes
121 CREATE_ALWAYS, // dwCreationDisposition
122 FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
123 0 // hTemplateFile
124 )
125 }
126 },
127 //
128 // Step 2:
129 // - Verify the file handle
130 // - Write the content to the file
131 //
132 {
133 let return_value = registers!().rax;
134
135 const INVALID_HANDLE_VALUE: u64 = 0xffff_ffff_ffff_ffff;
136
137 if return_value == INVALID_HANDLE_VALUE {
138 tracing::error!(
139 return_value = %Hex(return_value),
140 "step 2: kernel32!CreateFileA() failed"
141 );
142
143 return Ok(RecipeControlFlow::Break);
144 }
145
146 tracing::info!(
147 handle = %Hex(data![handle]),
148 "step 2: kernel32!WriteFile()"
149 );
150
151 // Save the handle.
152 data![handle] = return_value;
153
154 // Allocate a value on the stack to store the output parameter.
155 data![bytes_written_ptr] = copy_to_stack!(0u64)?;
156
157 inject! {
158 kernel32!WriteFile(
159 data![handle], // hFile
160 data![content], // lpBuffer
161 data![content].len(), // nNumberOfBytesToWrite
162 data![bytes_written_ptr], // lpNumberOfBytesWritten
163 0 // lpOverlapped
164 )
165 }
166 },
167 //
168 // Step 3:
169 // - Verify that the `WriteFile()` call succeeded
170 // - Close the file handle
171 //
172 {
173 let return_value = registers!().rax;
174
175 // Check if the `WriteFile()` call failed.
176 if return_value == 0 {
177 tracing::error!(
178 return_value = %Hex(return_value),
179 "step 3: kernel32!WriteFile() failed"
180 );
181
182 // Don't exit, we want to close the handle.
183 // return Ok(RecipeControlFlow::Break);
184 }
185
186 // Read the number of bytes written.
187 let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
188 tracing::info!(number_of_bytes_written, "step 3: kernel32!WriteFile()");
189
190 tracing::info!(
191 handle = %Hex(data![handle]),
192 "step 3: kernel32!CloseHandle()"
193 );
194
195 inject! {
196 kernel32!CloseHandle(
197 data![handle] // hObject
198 )
199 }
200 },
201 ]
202}More examples
138pub fn recipe_factory<Driver>(data: GuestFile) -> Recipe<WindowsOs<Driver>, GuestFile>
139where
140 Driver: VmiFullDriver<Architecture = Amd64>,
141{
142 recipe![
143 Recipe::<WindowsOs<Driver>>::new(data),
144 //
145 // Step 1:
146 // - Create a file.
147 //
148 {
149 tracing::info!(
150 target_path = data![target_path],
151 "step 1: kernel32!CreateFileA()"
152 );
153
154 const GENERIC_WRITE: u64 = 0x40000000;
155 const CREATE_ALWAYS: u64 = 2;
156 const FILE_ATTRIBUTE_NORMAL: u64 = 0x80;
157
158 inject! {
159 kernel32!CreateFileA(
160 &data![target_path], // lpFileName
161 GENERIC_WRITE, // dwDesiredAccess
162 0, // dwShareMode
163 0, // lpSecurityAttributes
164 CREATE_ALWAYS, // dwCreationDisposition
165 FILE_ATTRIBUTE_NORMAL, // dwFlagsAndAttributes
166 0 // hTemplateFile
167 )
168 }
169 },
170 //
171 // Step 2:
172 // - Verify the file handle
173 // - Write the first chunk to the file
174 //
175 {
176 let return_value = registers!().rax;
177
178 const INVALID_HANDLE_VALUE: u64 = 0xffff_ffff_ffff_ffff;
179
180 if return_value == INVALID_HANDLE_VALUE {
181 tracing::error!(
182 return_value = %Hex(return_value),
183 "step 2: kernel32!CreateFileA() failed"
184 );
185
186 return Ok(RecipeControlFlow::Break);
187 }
188
189 tracing::info!(
190 handle = %Hex(data![handle]),
191 "step 2: kernel32!WriteFile()"
192 );
193
194 // Save the handle.
195 data![handle] = return_value;
196
197 // Allocate a value on the stack to store the output parameter.
198 data![bytes_written_ptr] = copy_to_stack!(0u64)?;
199
200 // Get the first chunk of content.
201 let content = &data![content];
202 let chunk_size = usize::min(content.len(), data![chunk_size]);
203 let chunk = content[..chunk_size].to_vec();
204
205 inject! {
206 kernel32!WriteFile(
207 data![handle], // hFile
208 chunk, // lpBuffer
209 chunk.len() as u64, // nNumberOfBytesToWrite
210 data![bytes_written_ptr], // lpNumberOfBytesWritten
211 0 // lpOverlapped
212 )
213 }
214 },
215 //
216 // Step 3:
217 // - Verify that the `WriteFile()` call succeeded
218 // - Write the next chunk to the file
219 // - Repeat this step until all content is written
220 //
221 {
222 let return_value = registers!().rax;
223
224 if return_value == 0 {
225 tracing::error!(
226 return_value = %Hex(return_value),
227 "step 3: kernel32!WriteFile() failed"
228 );
229
230 return Ok(RecipeControlFlow::Break);
231 }
232
233 // Read the number of bytes written and update the total.
234 let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
235 data![bytes_written_total] += number_of_bytes_written;
236
237 let bytes_written_total = data![bytes_written_total];
238 let content = &data![content];
239
240 // If all content is written, move to the next step.
241 if bytes_written_total >= content.len() as u32 {
242 return Ok(RecipeControlFlow::Continue);
243 }
244
245 // Get the next chunk of content.
246 let remaining = content.len() - bytes_written_total as usize;
247 let chunk_size = usize::min(remaining, data![chunk_size]);
248 let chunk = &content[bytes_written_total as usize..];
249 let chunk = chunk[..chunk_size].to_vec();
250
251 // Allocate a value on the stack to store the output parameter.
252 data![bytes_written_ptr] = copy_to_stack!(0u64)?;
253
254 inject! {
255 kernel32!WriteFile(
256 data![handle], // hFile
257 chunk, // lpBuffer
258 chunk.len() as u64, // nNumberOfBytesToWrite
259 data![bytes_written_ptr], // lpNumberOfBytesWritten
260 0 // lpOverlapped
261 )
262 }?;
263
264 Ok(RecipeControlFlow::Repeat)
265 },
266 //
267 // Step 4:
268 // - Verify that the last `WriteFile()` call succeeded
269 // - Close the file handle
270 //
271 {
272 let return_value = registers!().rax;
273
274 if return_value == 0 {
275 tracing::error!(
276 return_value = %Hex(return_value),
277 "step 4: kernel32!WriteFile() failed"
278 );
279
280 // Don't exit, we want to close the handle.
281 // return Ok(RecipeControlFlow::Break);
282 }
283
284 // Read the number of bytes written.
285 let number_of_bytes_written = vmi!().read_u32(data![bytes_written_ptr])?;
286 tracing::info!(number_of_bytes_written, "step 4: kernel32!WriteFile()");
287
288 tracing::info!(
289 handle = %Hex(data![handle]),
290 "step 4: kernel32!CloseHandle()"
291 );
292
293 inject! {
294 kernel32!CloseHandle(
295 data![handle] // hObject
296 )
297 }
298 },
299 ]
300}Sourcepub fn read_u64(&self, address: Va) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_u64(&self, address: Va) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_uint(&self, address: Va, size: usize) -> Result<u64, VmiError>
injector and utils only.Reads an unsigned integer of the specified size from the virtual machine.
This method reads an unsigned integer of the specified size (in bytes) from the virtual machine. Note that the size must be 1, 2, 4, or 8.
The result is returned as a u64 to accommodate the widest possible
integer size.
Sourcepub fn read_field(
&self,
base_address: Va,
field: &Field,
) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_field( &self, base_address: Va, field: &Field, ) -> Result<u64, VmiError>
injector and utils only.Reads a field of a structure from the virtual machine.
This method reads a field from the virtual machine. The field is
defined by the provided Field structure, which specifies the
offset and size of the field within the memory region.
The result is returned as a u64 to accommodate the widest possible
integer size.
Sourcepub fn read_address(&self, address: Va) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_address(&self, address: Va) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_address_native(&self, address: Va) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_address32(&self, address: Va) -> Result<u64, VmiError>
injector and utils only.Reads a 32-bit address from the virtual machine.
Sourcepub fn read_address64(&self, address: Va) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_address64(&self, address: Va) -> Result<u64, VmiError>
injector and utils only.Reads a 64-bit address from the virtual machine.
Sourcepub fn read_va(&self, address: Va) -> Result<Va, VmiError>
Available on crate features injector and utils only.
pub fn read_va(&self, address: Va) -> Result<Va, VmiError>
injector and utils only.Reads a virtual address from the virtual machine.
Sourcepub fn read_va_native(&self, address: Va) -> Result<Va, VmiError>
Available on crate features injector and utils only.
pub fn read_va_native(&self, address: Va) -> Result<Va, VmiError>
injector and utils only.Reads a virtual address from the virtual machine.
Sourcepub fn read_va32(&self, address: Va) -> Result<Va, VmiError>
Available on crate features injector and utils only.
pub fn read_va32(&self, address: Va) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_va64(&self, address: Va) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_bytes_limited( &self, address: Va, limit: usize, ) -> Result<Vec<u8>, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_bytes(&self, address: Va) -> Result<Vec<u8>, VmiError>
injector and utils only.Reads a null-terminated string of bytes from the virtual machine.
Sourcepub fn read_string_utf16_bytes_limited(
&self,
address: Va,
limit: usize,
) -> Result<Vec<u16>, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_bytes_limited( &self, address: Va, limit: usize, ) -> Result<Vec<u16>, VmiError>
injector and utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_string_utf16_bytes(&self, address: Va) -> Result<Vec<u16>, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_bytes(&self, address: Va) -> Result<Vec<u16>, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_limited( &self, address: Va, limit: usize, ) -> Result<String, VmiError>
injector and 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 features injector and utils only.
pub fn read_string(&self, address: Va) -> Result<String, VmiError>
injector and utils only.Reads a null-terminated string from the virtual machine.
Sourcepub fn read_string_utf16_limited(
&self,
address: Va,
limit: usize,
) -> Result<String, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_limited( &self, address: Va, limit: usize, ) -> Result<String, VmiError>
injector and utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_string_utf16(&self, address: Va) -> Result<String, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16(&self, address: Va) -> Result<String, VmiError>
injector and 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 features injector and utils only.
pub fn read_struct<T>(&self, address: Va) -> Result<T, VmiError>
injector and utils only.Reads a struct from the virtual machine.
Examples found in repository?
63pub fn SslGenerateSessionKeys<Driver>(
64 vmi: &VmiContext<WindowsOs<Driver>>,
65) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
66where
67 Driver: VmiRead,
68 Driver::Architecture: ArchAdapter<Driver>,
69{
70 //
71 // SECURITY_STATUS
72 // WINAPI
73 // SslGenerateSessionKeys(
74 // _In_ NCRYPT_PROV_HANDLE hSslProvider,
75 // _In_ NCRYPT_KEY_HANDLE hMasterKey,
76 // _Out_ NCRYPT_KEY_HANDLE *phReadKey,
77 // _Out_ NCRYPT_KEY_HANDLE *phWriteKey,
78 // _In_ PNCryptBufferDesc pParameterList,
79 // _In_ DWORD dwFlags
80 // );
81 //
82
83 let hMasterKey = vmi.os().function_argument(1)?;
84 let pParameterList = vmi.os().function_argument(4)?;
85
86 let mut client_random = vec![0u8; 32];
87
88 let parameter_list = vmi.read_struct::<NCryptBufferDesc>(Va(pParameterList))?;
89 for i in 0..parameter_list.cBuffers {
90 let offset = (i as u64) * size_of::<NCryptBuffer>() as u64;
91 let buffer = vmi.read_struct::<NCryptBuffer>(Va(parameter_list.pBuffers + offset))?;
92
93 if buffer.BufferType == NCRYPTBUFFER_SSL_CLIENT_RANDOM {
94 vmi.read(Va(buffer.pvBuffer), &mut client_random)?;
95 break;
96 }
97 }
98
99 let master_key = vmi.read_struct::<NCRYPT_SSL_KEY>(Va(hMasterKey))?;
100 let subkey = vmi.read_struct::<SSL_MASTER_KEY>(Va(master_key.hSubKey))?;
101
102 tracing::info!(
103 client_random = hex::encode(client_random),
104 secret = hex::encode(subkey.rgbMasterKey),
105 );
106
107 Ok(Action::default())
108}More examples
349pub fn KfdClassify<Driver>(
350 vmi: &VmiContext<WindowsOs<Driver>>,
351) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
352where
353 Driver: VmiRead,
354 Driver::Architecture: ArchAdapter<Driver>,
355{
356 //
357 // PVOID
358 // NTAPI
359 // KfdClassify (
360 // _In_ UINT16 layerId,
361 // _In_ const FWPS_INCOMING_VALUES* inFixedValues,
362 // _In_ const FWPS_INCOMING_METADATA_VALUES* inContext,
363 // _In_ PVOID packet,
364 // _In_ const FWPP_SHIM_PROVIDER_CONTEXT* shimProvContext,
365 // _Inout_ FWPS_CLASSIFY_OUT* classifyOut
366 // );
367 //
368
369 let layerId = FwpsLayer(vmi.os().function_argument(0)? as u16);
370 let inFixedValues = Va(vmi.os().function_argument(1)?);
371 let inContext = Va(vmi.os().function_argument(2)?);
372
373 let (
374 protocol_index,
375 local_address_index,
376 local_port_index,
377 remote_address_index,
378 remote_port_index,
379 ) = match layerId.network_5tuple_indexes() {
380 Some(indexes) => indexes,
381 None => return Ok(Action::default()),
382 };
383
384 let incoming_values = vmi.read_struct::<FWPS_INCOMING_VALUES0>(inFixedValues)?;
385 let incoming = Va(incoming_values.incomingValue);
386
387 const SIZEOF_VALUE: u64 = size_of::<FWPS_INCOMING_VALUE0>() as u64;
388
389 //
390 // Protocol.
391 //
392
393 let protocol =
394 vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + protocol_index * SIZEOF_VALUE)?;
395
396 if protocol.value.ty != FwpDataType::UINT8 {
397 tracing::debug!(
398 protocol_type = ?protocol.value.ty,
399 expected = ?FwpDataType::UINT8,
400 "unexpected protocol type"
401 );
402 return Ok(Action::default());
403 }
404
405 //
406 // Local Address.
407 //
408
409 let local_address =
410 vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + local_address_index * SIZEOF_VALUE)?;
411
412 if local_address.value.ty != FwpDataType::UINT32 {
413 tracing::debug!(
414 local_address_type = ?local_address.value.ty,
415 expected = ?FwpDataType::UINT32,
416 "unexpected local address type"
417 );
418 return Ok(Action::default());
419 }
420
421 //
422 // Local Port.
423 //
424
425 let local_port =
426 vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + local_port_index * SIZEOF_VALUE)?;
427
428 if local_port.value.ty != FwpDataType::UINT16 {
429 tracing::debug!(
430 local_port_type = ?local_port.value.ty,
431 expected = ?FwpDataType::UINT16,
432 "unexpected local port type"
433 );
434 return Ok(Action::default());
435 }
436
437 //
438 // Remote Address.
439 //
440
441 let remote_address =
442 vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + remote_address_index * SIZEOF_VALUE)?;
443
444 if remote_address.value.ty != FwpDataType::UINT32 {
445 tracing::debug!(
446 remote_address_type = ?remote_address.value.ty,
447 expected = ?FwpDataType::UINT32,
448 "unexpected remote address type"
449 );
450 return Ok(Action::default());
451 }
452
453 //
454 // Remote Port.
455 //
456
457 let remote_port =
458 vmi.read_struct::<FWPS_INCOMING_VALUE0>(incoming + remote_port_index * SIZEOF_VALUE)?;
459
460 if remote_port.value.ty != FwpDataType::UINT16 {
461 tracing::debug!(
462 remote_port_type = ?remote_port.value.ty,
463 expected = ?FwpDataType::UINT16,
464 "unexpected remote port type"
465 );
466 return Ok(Action::default());
467 }
468
469 let protocol = IpProtocol(protocol.value.data as u8);
470 let local_address = local_address.value.data as u32;
471 let local_port = local_port.value.data as u16;
472 let remote_address = remote_address.value.data as u32;
473 let remote_port = remote_port.value.data as u16;
474
475 let local_ip = IpAddr::from(local_address.to_be_bytes());
476 let remote_ip = IpAddr::from(remote_address.to_be_bytes());
477
478 // Fetch the most valuable information that can't be obtained
479 // from the pcap: the process ID that initiated the connection.
480 let context = vmi.read_struct::<FWPS_INCOMING_METADATA_VALUES0>(inContext)?;
481 let metadata_values = FwpsMetadataFields::from_bits_retain(context.currentMetadataValues);
482 let pid = if metadata_values.contains(FwpsMetadataFields::PROCESS_ID) {
483 Some(context.processId)
484 }
485 else {
486 None
487 };
488
489 tracing::info!(
490 ?protocol,
491 %local_ip,
492 local_port,
493 %remote_ip,
494 remote_port,
495 pid,
496 );
497
498 Ok(Action::default())
499}Sourcepub fn read_in(
&self,
ctx: impl Into<AccessContext>,
buffer: &mut [u8],
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn read_in( &self, ctx: impl Into<AccessContext>, buffer: &mut [u8], ) -> Result<(), VmiError>
injector and utils only.Reads memory from the virtual machine.
Sourcepub fn read_u8_in(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
Available on crate features injector and utils only.
pub fn read_u8_in(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
injector and 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 features injector and utils only.
pub fn read_u16_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u16, VmiError>
injector and 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 features injector and utils only.
pub fn read_u32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u32, VmiError>
injector and 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 features injector and utils only.
pub fn read_u64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_uint_in( &self, ctx: impl Into<AccessContext>, size: usize, ) -> Result<u64, VmiError>
injector and utils only.Reads an unsigned integer of the specified size from the virtual machine.
This method reads an unsigned integer of the specified size (in bytes) from the virtual machine. Note that the size must be 1, 2, 4, or 8.
The result is returned as a u64 to accommodate the widest possible
integer size.
Sourcepub fn read_field_in(
&self,
ctx: impl Into<AccessContext>,
field: &Field,
) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_field_in( &self, ctx: impl Into<AccessContext>, field: &Field, ) -> Result<u64, VmiError>
injector and utils only.Reads a field of a structure from the virtual machine.
This method reads a field from the virtual machine. The field is
defined by the provided Field structure, which specifies the
offset and size of the field within the memory region.
The result is returned as a u64 to accommodate the widest possible
integer size.
Sourcepub fn read_address_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_address_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_address_native_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_address32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_address64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_va_in(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_va_native_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_va32_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_va64_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_bytes_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u8>, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_bytes_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u8>, VmiError>
injector and utils only.Reads a null-terminated string of bytes from the virtual machine.
Sourcepub fn read_string_utf16_bytes_limited_in(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<Vec<u16>, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_bytes_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u16>, VmiError>
injector and utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_string_utf16_bytes_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_bytes_in( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u16>, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_in( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
injector and utils only.Reads a null-terminated string from the virtual machine.
Sourcepub fn read_string_utf16_limited_in(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<String, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_limited_in( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
injector and utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_string_utf16_in(
&self,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_in( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
injector and 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 features injector and utils only.
pub fn read_struct_in<T>( &self, ctx: impl Into<AccessContext>, ) -> Result<T, VmiError>
injector and utils only.Reads a struct from the virtual machine.
Source§impl<'a, Os> VmiState<'a, Os>
impl<'a, Os> VmiState<'a, Os>
Sourcepub fn write(&self, address: Va, buffer: &[u8]) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn write(&self, address: Va, buffer: &[u8]) -> Result<(), VmiError>
injector and utils only.Writes memory to the virtual machine.
Sourcepub fn write_in(
&self,
ctx: impl Into<AccessContext>,
buffer: &[u8],
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn write_in( &self, ctx: impl Into<AccessContext>, buffer: &[u8], ) -> Result<(), VmiError>
injector and utils only.Writes memory to the virtual machine.
Sourcepub fn write_u8(&self, address: Va, value: u8) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn write_u8(&self, address: Va, value: u8) -> Result<(), VmiError>
injector and utils only.Writes a single byte to the virtual machine.
Sourcepub fn write_u16(&self, address: Va, value: u16) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn write_u16(&self, address: Va, value: u16) -> Result<(), VmiError>
injector and 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 features injector and utils only.
pub fn write_u32(&self, address: Va, value: u32) -> Result<(), VmiError>
injector and utils only.Writes a 32-bit unsigned integer to the virtual machine.
Source§impl<'a, Os> VmiState<'a, Os>
impl<'a, Os> VmiState<'a, Os>
Sourcepub fn set_registers(
&self,
vcpu: VcpuId,
registers: <<Os as VmiOs>::Architecture as Architecture>::Registers,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn set_registers( &self, vcpu: VcpuId, registers: <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> Result<(), VmiError>
injector and utils only.Sets the registers of a virtual CPU.
Methods from Deref<Target = VmiSession<'a, Os>>§
Sourcepub fn with_registers(
&'a self,
registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers,
) -> VmiState<'a, Os>
Available on crate features injector and utils only.
pub fn with_registers( &'a self, registers: &'a <<Os as VmiOs>::Architecture as Architecture>::Registers, ) -> VmiState<'a, Os>
injector and utils only.Creates a new VMI state with the specified registers.
Examples found in repository?
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::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}More examples
67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Sourcepub fn without_os(&self) -> VmiSession<'a, NoOS<<Os as VmiOs>::Driver>>
Available on crate features injector and utils only.
pub fn without_os(&self) -> VmiSession<'a, NoOS<<Os as VmiOs>::Driver>>
injector and utils only.Creates a new VMI session without an OS-specific implementation.
Sourcepub fn core(&self) -> &'a VmiCore<<Os as VmiOs>::Driver>
Available on crate features injector and utils only.
pub fn core(&self) -> &'a VmiCore<<Os as VmiOs>::Driver>
injector and utils only.Returns the VMI core.
Sourcepub fn underlying_os(&self) -> &'a Os
Available on crate features injector and utils only.
pub fn underlying_os(&self) -> &'a Os
injector and utils only.Returns the underlying OS-specific implementation.
Sourcepub fn wait_for_event(
&self,
timeout: Duration,
handler: &mut impl VmiHandler<Os>,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn wait_for_event( &self, timeout: Duration, handler: &mut impl VmiHandler<Os>, ) -> Result<(), VmiError>
injector and utils only.Waits for an event to occur and processes it with the provided handler.
This method blocks until an event occurs or the specified timeout is reached. When an event occurs, it is passed to the provided callback function for processing.
Sourcepub fn handle<Handler>(
&self,
handler_factory: impl FnOnce(&VmiSession<'_, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Os>>::Output>, VmiError>where
Handler: VmiHandler<Os>,
Available on crate features injector and utils only.
pub fn handle<Handler>(
&self,
handler_factory: impl FnOnce(&VmiSession<'_, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Os>>::Output>, VmiError>where
Handler: VmiHandler<Os>,
injector and 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 paused = session.pause_guard()?;
72
73 let vmi = paused.state();
74
75 let explorer = match common::find_process(&vmi, "explorer.exe")? {
76 Some(explorer) => explorer,
77 None => {
78 tracing::error!("explorer.exe not found");
79 return Ok(());
80 }
81 };
82
83 tracing::info!(
84 pid = %explorer.id()?,
85 object = %explorer.object()?,
86 "found explorer.exe"
87 );
88
89 explorer.id()?
90 };
91
92 session.handle(|session| {
93 UserInjectorHandler::new(
94 session,
95 recipe_factory(MessageBox::new(
96 "Hello, World!",
97 "This is a message box from the VMI!",
98 )),
99 )?
100 .with_pid(explorer_pid)
101 })?;
102
103 Ok(())
104}More examples
204fn main() -> Result<(), Box<dyn std::error::Error>> {
205 let (session, _profile) = common::create_vmi_session()?;
206
207 let explorer_pid = {
208 // This block is used to drop the pause guard after the PID is found.
209 // If the `session.handle()` would be called with the VM paused, no
210 // events would be triggered.
211 let paused = session.pause_guard()?;
212
213 let vmi = paused.state();
214
215 let explorer = match common::find_process(&vmi, "explorer.exe")? {
216 Some(explorer) => explorer,
217 None => {
218 tracing::error!("explorer.exe not found");
219 return Ok(());
220 }
221 };
222
223 tracing::info!(
224 pid = %explorer.id()?,
225 object = %explorer.object()?,
226 "found explorer.exe"
227 );
228
229 explorer.id()?
230 };
231
232 session.handle(|session| {
233 UserInjectorHandler::new(
234 session,
235 recipe_factory(GuestFile::new(
236 "C:\\Users\\John\\Desktop\\test.txt",
237 "Hello, World!".as_bytes(),
238 )),
239 )?
240 .with_pid(explorer_pid)
241 })?;
242
243 Ok(())
244}302fn main() -> Result<(), Box<dyn std::error::Error>> {
303 let (session, _profile) = common::create_vmi_session()?;
304
305 let explorer_pid = {
306 // This block is used to drop the pause guard after the PID is found.
307 // If the `session.handle()` would be called with the VM paused, no
308 // events would be triggered.
309 let paused = session.pause_guard()?;
310
311 let vmi = paused.state();
312
313 let explorer = match common::find_process(&vmi, "explorer.exe")? {
314 Some(explorer) => explorer,
315 None => {
316 tracing::error!("explorer.exe not found");
317 return Ok(());
318 }
319 };
320
321 tracing::info!(
322 pid = %explorer.id()?,
323 object = %explorer.object()?,
324 "found explorer.exe"
325 );
326
327 explorer.id()?
328 };
329
330 let mut content = Vec::new();
331 for c in 'A'..='Z' {
332 content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
333 }
334
335 session.handle(|session| {
336 UserInjectorHandler::new(
337 session,
338 recipe_factory(GuestFile::new(
339 "C:\\Users\\John\\Desktop\\test.txt",
340 content,
341 )),
342 )?
343 .with_pid(explorer_pid)
344 })?;
345
346 Ok(())
347}525fn main() -> Result<(), Box<dyn std::error::Error>> {
526 tracing_subscriber::fmt()
527 .with_max_level(tracing::Level::DEBUG)
528 .init();
529
530 let domain_id = 'x: {
531 for name in &["win7", "win10", "win11", "ubuntu22"] {
532 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
533 break 'x domain_id;
534 }
535 }
536
537 panic!("Domain not found");
538 };
539
540 tracing::debug!(?domain_id);
541
542 // Setup VMI.
543 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
544 let core = VmiCore::new(driver)?;
545
546 // Try to find the kernel information.
547 // This is necessary in order to load the profile.
548 let kernel_info = {
549 let _pause_guard = core.pause_guard()?;
550 let regs = core.registers(0.into())?;
551
552 WindowsOs::find_kernel(&core, ®s)?.expect("kernel information")
553 };
554
555 // Load the profile.
556 // The profile contains offsets to kernel functions and data structures.
557 let isr = IsrCache::new("cache")?;
558 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
559 let profile = entry.profile()?;
560
561 // Create the VMI session.
562 tracing::info!("Creating VMI session");
563 let terminate_flag = Arc::new(AtomicBool::new(false));
564 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
565 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
566 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
567 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
568
569 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
570 let session = VmiSession::new(&core, &os);
571
572 session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
573
574 Ok(())
575}245fn main() -> Result<(), Error> {
246 let filter = EnvFilter::default()
247 .add_directive(tracing::Level::DEBUG.into())
248 .add_directive("reqwest=warn".parse()?)
249 .add_directive("rustls=warn".parse()?);
250
251 tracing_subscriber::fmt()
252 .with_env_filter(filter)
253 .with_target(false)
254 .init();
255
256 let domain_id = match std::env::var("VMI_XEN_DOMAIN_ID") {
257 Ok(domain_id) => XenDomainId(
258 domain_id
259 .parse()
260 .context("invalid VMI_XEN_DOMAIN_ID environment variable")?,
261 ),
262 Err(_) => {
263 let domain_name = std::env::var("VMI_XEN_DOMAIN_NAME")
264 .context("invalid VMI_XEN_DOMAIN_NAME environment variable")?;
265
266 tracing::info!(%domain_name, "resolving domain ID");
267
268 match XenStore::new()?.domain_id_from_name(&domain_name)? {
269 Some(domain_id) => domain_id,
270 None => return Err(anyhow::anyhow!("domain not found: {domain_name}")),
271 }
272 }
273 };
274
275 // Setup VMI.
276 tracing::info!(%domain_id, "setting up VMI");
277 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
278 let core = VmiCore::new(driver)?;
279
280 // Try to find the kernel information.
281 // This is necessary in order to load the profile.
282 let kernel_info = {
283 let _pause_guard = core.pause_guard()?;
284 let registers = core.registers(VcpuId(0))?;
285
286 WindowsOs::find_kernel(&core, ®isters)?.context("cannot find kernel information")?
287 };
288
289 // Load the kernel profile.
290 // The profile contains offsets to kernel functions and data structures.
291 tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
292 let isr = IsrCache::new("cache")?;
293 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
294 let profile = entry.profile()?;
295
296 // Create the VMI session.
297 tracing::info!("creating VMI session");
298 let terminate_flag = Arc::new(AtomicBool::new(false));
299 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
300 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
301 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
302 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
303
304 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
305 let session = VmiSession::new(&core, &os);
306
307 let handler = NetIo::default();
308
309 //
310 // The following `let ncrypt_* = ...` lines demonstrate how to manually
311 // resolve a module, load its profile (symbols) and add it to the resolver
312 // via `with_module(_in_process)`.
313 //
314 // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
315 // will automatically resolve modules if they are not explicitly added.
316 //
317 // Manually resolving modules can be useful in cases where you want to deal
318 // with the resolved information (base address, profile) in other places.
319 //
320
321 let ncrypt_resolved = {
322 let paused = session.pause_guard()?;
323 let vmi = paused.state();
324
325 // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
326 // would also work, but this demonstrates how to use a custom predicate.
327 //
328 // Also, `match_lsass` is more strict, because it specifically looks
329 // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
330 // positives or potential malicious processes).
331 vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
332 .context("ncrypt.dll not found in lsass.exe")?
333 };
334
335 let ncrypt_process = ncrypt_resolved
336 .process
337 .context("resolved ncrypt.dll is not associated with a process")?;
338
339 let ncrypt_entry = isr
340 .entry_from_codeview(ncrypt_resolved.debug_signature)
341 .context("cannot find symbols for ncrypt.dll")?;
342
343 let ncrypt_profile = ncrypt_entry
344 .profile()
345 .context("cannot load profile for ncrypt.dll")?;
346
347 // The `SymbolCache` holds the resolved `isr::Entry` items.
348 let mut cache = SymbolCache::default();
349 let modules = ModuleResolver::default()
350 // `with_kernel` MUST be called if `Event` variants reference kernel
351 // symbols - like `NtWriteFile` in this example.
352 //
353 // This is because the "kernel" module is always optional.
354 .with_kernel(kernel_info.base_address, profile)
355 .with_module_in_process(
356 Module::NcryptDll,
357 ncrypt_process,
358 ncrypt_resolved.image_base,
359 ncrypt_profile,
360 )
361 // This will automatically resolve the `netio.sys` module and load
362 // its profile.
363 //
364 // Note that if we hadn't called `with_module_in_process` for
365 // `ncrypt.dll`, it would also be automatically resolved here.
366 .resolve(&session, &isr, &mut cache)?;
367
368 // Finally, we collect the events according to the resolved information
369 // and the metadata.
370 //
371 // For example, if some module/event is marked as `optional` and the
372 // resolver fails to resolve it, then it will simply not be included
373 // in the `events`.
374 let events = modules.into_events()?;
375
376 // And we're ready to create the reactor!
377 session.handle(|session| {
378 Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
379 })?;
380
381 Ok(())
382}Sourcepub fn handle_with_timeout<Handler>(
&self,
timeout: Duration,
handler_factory: impl FnOnce(&VmiSession<'_, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Os>>::Output>, VmiError>where
Handler: VmiHandler<Os>,
Available on crate features injector and utils only.
pub fn handle_with_timeout<Handler>(
&self,
timeout: Duration,
handler_factory: impl FnOnce(&VmiSession<'_, Os>) -> Result<Handler, VmiError>,
) -> Result<Option<<Handler as VmiHandler<Os>>::Output>, VmiError>where
Handler: VmiHandler<Os>,
injector and utils only.Enters the main event handling loop that processes VMI events until finished, with a timeout for each event.
Sourcepub fn pause_guard(&self) -> Result<VmiSessionPauseGuard<'_, Os>, VmiError>
Available on crate features injector and utils only.
pub fn pause_guard(&self) -> Result<VmiSessionPauseGuard<'_, Os>, VmiError>
injector and utils only.Pauses the virtual machine, snapshots the boot CPU registers, and returns a guard that resumes the VM when dropped.
Examples found in repository?
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 paused = session.pause_guard()?;
72
73 let vmi = paused.state();
74
75 let explorer = match common::find_process(&vmi, "explorer.exe")? {
76 Some(explorer) => explorer,
77 None => {
78 tracing::error!("explorer.exe not found");
79 return Ok(());
80 }
81 };
82
83 tracing::info!(
84 pid = %explorer.id()?,
85 object = %explorer.object()?,
86 "found explorer.exe"
87 );
88
89 explorer.id()?
90 };
91
92 session.handle(|session| {
93 UserInjectorHandler::new(
94 session,
95 recipe_factory(MessageBox::new(
96 "Hello, World!",
97 "This is a message box from the VMI!",
98 )),
99 )?
100 .with_pid(explorer_pid)
101 })?;
102
103 Ok(())
104}More examples
204fn main() -> Result<(), Box<dyn std::error::Error>> {
205 let (session, _profile) = common::create_vmi_session()?;
206
207 let explorer_pid = {
208 // This block is used to drop the pause guard after the PID is found.
209 // If the `session.handle()` would be called with the VM paused, no
210 // events would be triggered.
211 let paused = session.pause_guard()?;
212
213 let vmi = paused.state();
214
215 let explorer = match common::find_process(&vmi, "explorer.exe")? {
216 Some(explorer) => explorer,
217 None => {
218 tracing::error!("explorer.exe not found");
219 return Ok(());
220 }
221 };
222
223 tracing::info!(
224 pid = %explorer.id()?,
225 object = %explorer.object()?,
226 "found explorer.exe"
227 );
228
229 explorer.id()?
230 };
231
232 session.handle(|session| {
233 UserInjectorHandler::new(
234 session,
235 recipe_factory(GuestFile::new(
236 "C:\\Users\\John\\Desktop\\test.txt",
237 "Hello, World!".as_bytes(),
238 )),
239 )?
240 .with_pid(explorer_pid)
241 })?;
242
243 Ok(())
244}302fn main() -> Result<(), Box<dyn std::error::Error>> {
303 let (session, _profile) = common::create_vmi_session()?;
304
305 let explorer_pid = {
306 // This block is used to drop the pause guard after the PID is found.
307 // If the `session.handle()` would be called with the VM paused, no
308 // events would be triggered.
309 let paused = session.pause_guard()?;
310
311 let vmi = paused.state();
312
313 let explorer = match common::find_process(&vmi, "explorer.exe")? {
314 Some(explorer) => explorer,
315 None => {
316 tracing::error!("explorer.exe not found");
317 return Ok(());
318 }
319 };
320
321 tracing::info!(
322 pid = %explorer.id()?,
323 object = %explorer.object()?,
324 "found explorer.exe"
325 );
326
327 explorer.id()?
328 };
329
330 let mut content = Vec::new();
331 for c in 'A'..='Z' {
332 content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
333 }
334
335 session.handle(|session| {
336 UserInjectorHandler::new(
337 session,
338 recipe_factory(GuestFile::new(
339 "C:\\Users\\John\\Desktop\\test.txt",
340 content,
341 )),
342 )?
343 .with_pid(explorer_pid)
344 })?;
345
346 Ok(())
347}13fn main() -> Result<(), Box<dyn std::error::Error>> {
14 let domain_id = 'x: {
15 for name in &["win7", "win10", "win11", "ubuntu22"] {
16 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
17 break 'x domain_id;
18 }
19 }
20
21 panic!("Domain not found");
22 };
23
24 // Setup VMI.
25 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
26 let core = VmiCore::new(driver)?;
27
28 // Try to find the kernel information.
29 // This is necessary in order to load the profile.
30 let kernel_info = {
31 // Pause the VM to get consistent state.
32 let _pause_guard = core.pause_guard()?;
33
34 // Get the register state for the first vCPU.
35 let registers = core.registers(VcpuId(0))?;
36
37 // On AMD64 architecture, the kernel is usually found using the
38 // `MSR_LSTAR` register, which contains the address of the system call
39 // handler. This register is set by the operating system during boot
40 // and is left unchanged (unless some rootkits are involved).
41 //
42 // Therefore, we can take an arbitrary registers at any point in time
43 // (as long as the OS has booted and the page tables are set up) and
44 // use them to find the kernel.
45 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
46 };
47
48 // Load the profile.
49 // The profile contains offsets to kernel functions and data structures.
50 let isr = IsrCache::new("cache")?;
51 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
52 let profile = entry.profile()?;
53
54 // Create the VMI session.
55 tracing::info!("Creating VMI session");
56 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
57 let session = VmiSession::new(&core, &os);
58
59 // Pause the VM again to get consistent state.
60 let paused = session.pause_guard()?;
61
62 // Create a new `VmiState` with the boot CPU registers.
63 let vmi = paused.state();
64
65 // Get the list of processes and print them.
66 for process in vmi.os().processes()? {
67 let process = process?;
68
69 println!(
70 "{} [{}] {} (root @ {})",
71 process.object()?,
72 process.id()?,
73 process.name()?,
74 process.translation_root()?
75 );
76 }
77
78 Ok(())
79}245fn main() -> Result<(), Error> {
246 let filter = EnvFilter::default()
247 .add_directive(tracing::Level::DEBUG.into())
248 .add_directive("reqwest=warn".parse()?)
249 .add_directive("rustls=warn".parse()?);
250
251 tracing_subscriber::fmt()
252 .with_env_filter(filter)
253 .with_target(false)
254 .init();
255
256 let domain_id = match std::env::var("VMI_XEN_DOMAIN_ID") {
257 Ok(domain_id) => XenDomainId(
258 domain_id
259 .parse()
260 .context("invalid VMI_XEN_DOMAIN_ID environment variable")?,
261 ),
262 Err(_) => {
263 let domain_name = std::env::var("VMI_XEN_DOMAIN_NAME")
264 .context("invalid VMI_XEN_DOMAIN_NAME environment variable")?;
265
266 tracing::info!(%domain_name, "resolving domain ID");
267
268 match XenStore::new()?.domain_id_from_name(&domain_name)? {
269 Some(domain_id) => domain_id,
270 None => return Err(anyhow::anyhow!("domain not found: {domain_name}")),
271 }
272 }
273 };
274
275 // Setup VMI.
276 tracing::info!(%domain_id, "setting up VMI");
277 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
278 let core = VmiCore::new(driver)?;
279
280 // Try to find the kernel information.
281 // This is necessary in order to load the profile.
282 let kernel_info = {
283 let _pause_guard = core.pause_guard()?;
284 let registers = core.registers(VcpuId(0))?;
285
286 WindowsOs::find_kernel(&core, ®isters)?.context("cannot find kernel information")?
287 };
288
289 // Load the kernel profile.
290 // The profile contains offsets to kernel functions and data structures.
291 tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
292 let isr = IsrCache::new("cache")?;
293 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
294 let profile = entry.profile()?;
295
296 // Create the VMI session.
297 tracing::info!("creating VMI session");
298 let terminate_flag = Arc::new(AtomicBool::new(false));
299 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
300 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
301 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
302 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
303
304 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
305 let session = VmiSession::new(&core, &os);
306
307 let handler = NetIo::default();
308
309 //
310 // The following `let ncrypt_* = ...` lines demonstrate how to manually
311 // resolve a module, load its profile (symbols) and add it to the resolver
312 // via `with_module(_in_process)`.
313 //
314 // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
315 // will automatically resolve modules if they are not explicitly added.
316 //
317 // Manually resolving modules can be useful in cases where you want to deal
318 // with the resolved information (base address, profile) in other places.
319 //
320
321 let ncrypt_resolved = {
322 let paused = session.pause_guard()?;
323 let vmi = paused.state();
324
325 // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
326 // would also work, but this demonstrates how to use a custom predicate.
327 //
328 // Also, `match_lsass` is more strict, because it specifically looks
329 // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
330 // positives or potential malicious processes).
331 vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
332 .context("ncrypt.dll not found in lsass.exe")?
333 };
334
335 let ncrypt_process = ncrypt_resolved
336 .process
337 .context("resolved ncrypt.dll is not associated with a process")?;
338
339 let ncrypt_entry = isr
340 .entry_from_codeview(ncrypt_resolved.debug_signature)
341 .context("cannot find symbols for ncrypt.dll")?;
342
343 let ncrypt_profile = ncrypt_entry
344 .profile()
345 .context("cannot load profile for ncrypt.dll")?;
346
347 // The `SymbolCache` holds the resolved `isr::Entry` items.
348 let mut cache = SymbolCache::default();
349 let modules = ModuleResolver::default()
350 // `with_kernel` MUST be called if `Event` variants reference kernel
351 // symbols - like `NtWriteFile` in this example.
352 //
353 // This is because the "kernel" module is always optional.
354 .with_kernel(kernel_info.base_address, profile)
355 .with_module_in_process(
356 Module::NcryptDll,
357 ncrypt_process,
358 ncrypt_resolved.image_base,
359 ncrypt_profile,
360 )
361 // This will automatically resolve the `netio.sys` module and load
362 // its profile.
363 //
364 // Note that if we hadn't called `with_module_in_process` for
365 // `ncrypt.dll`, it would also be automatically resolved here.
366 .resolve(&session, &isr, &mut cache)?;
367
368 // Finally, we collect the events according to the resolved information
369 // and the metadata.
370 //
371 // For example, if some module/event is marked as `optional` and the
372 // resolver fails to resolve it, then it will simply not be included
373 // in the `events`.
374 let events = modules.into_events()?;
375
376 // And we're ready to create the reactor!
377 session.handle(|session| {
378 Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
379 })?;
380
381 Ok(())
382}67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Methods from Deref<Target = VmiCore<<Os as VmiOs>::Driver>>§
Sourcepub fn driver(&self) -> &Driver
Available on crate features injector and utils only.
pub fn driver(&self) -> &Driver
injector and utils only.Returns the driver used by this VmiCore instance.
Sourcepub fn info(&self) -> Result<VmiInfo, VmiError>
Available on crate features injector and utils only.
pub fn info(&self) -> Result<VmiInfo, VmiError>
injector and utils only.Retrieves information about the virtual machine.
Examples found in repository?
9fn main() -> Result<(), Box<dyn std::error::Error>> {
10 let domain_id = 'x: {
11 for name in &["win7", "win10", "win11", "ubuntu22"] {
12 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
13 break 'x domain_id;
14 }
15 }
16
17 panic!("Domain not found");
18 };
19
20 // Setup VMI.
21 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
22 let vmi = VmiCore::new(driver)?;
23
24 // Get the interrupt descriptor table for each vCPU and print it.
25 let _pause_guard = vmi.pause_guard()?;
26 let info = vmi.info()?;
27 for vcpu_id in 0..info.vcpus {
28 let registers = vmi.registers(VcpuId(vcpu_id))?;
29 let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
30
31 println!("IDT[{vcpu_id}]: {idt:#?}");
32 }
33
34 Ok(())
35}Sourcepub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage>
Available on crate features injector and utils only.
pub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage>
injector and utils only.Removes a specific entry from the GFN cache.
Returns the removed entry if it was present. This is useful for invalidating cached data that might have become stale.
Sourcepub fn flush_gfn_cache(&self)
Available on crate features injector and utils only.
pub fn flush_gfn_cache(&self)
injector and utils only.Clears the entire GFN cache.
Sourcepub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa>
Available on crate features injector and utils only.
pub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa>
injector and utils only.Removes a specific entry from the V2P cache.
Returns the removed entry if it was present. This can be used to invalidate cached translations that may have become stale due to changes in the guest’s memory mapping.
Sourcepub fn flush_v2p_cache(&self)
Available on crate features injector and utils only.
pub fn flush_v2p_cache(&self)
injector and utils only.Clears the entire V2P cache.
This method is crucial for maintaining consistency when handling events.
The guest operating system can modify page tables or other structures
related to address translation between events. Using stale translations
can lead to incorrect memory access and unexpected behavior.
It is recommended to call this method at the beginning of each
VmiHandler::handle_event loop to ensure that you are working with
the most up-to-date address mappings.
Sourcepub fn read_string_length_limit(&self) -> Option<usize>
Available on crate features injector and utils only.
pub fn read_string_length_limit(&self) -> Option<usize>
injector and 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 features injector and utils only.
pub fn set_read_string_length_limit(&self, limit: usize)
injector and utils only.Sets a limit on the length of strings read by the read_string methods.
This method allows you to set a maximum length (in bytes) for strings read from the virtual machine’s memory. When set, string reading operations will truncate their results to this limit. This can be useful for preventing excessively long string reads, which might impact performance or consume too much memory.
If the limit is reached during a string read operation, the resulting string will be truncated to the specified length.
To remove the limit, call this method with None.
Sourcepub fn read(
&self,
ctx: impl Into<AccessContext>,
buffer: &mut [u8],
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn read( &self, ctx: impl Into<AccessContext>, buffer: &mut [u8], ) -> Result<(), VmiError>
injector and utils only.Reads memory from the virtual machine.
Sourcepub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
Available on crate features injector and utils only.
pub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
injector and 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 features injector and utils only.
pub fn read_u16(&self, ctx: impl Into<AccessContext>) -> Result<u16, VmiError>
injector and 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 features injector and utils only.
pub fn read_u32(&self, ctx: impl Into<AccessContext>) -> Result<u32, VmiError>
injector and 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 features injector and utils only.
pub fn read_u64(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_uint( &self, ctx: impl Into<AccessContext>, size: usize, ) -> Result<u64, VmiError>
injector and utils only.Reads an unsigned integer of the specified size from the virtual machine.
This method reads an unsigned integer of the specified size (in bytes) from the virtual machine. Note that the size must be 1, 2, 4, or 8.
The result is returned as a u64 to accommodate the widest possible
integer size.
Sourcepub fn read_field(
&self,
ctx: impl Into<AccessContext>,
field: &Field,
) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_field( &self, ctx: impl Into<AccessContext>, field: &Field, ) -> Result<u64, VmiError>
injector and utils only.Reads a field of a structure from the virtual machine.
This method reads a field from the virtual machine. The field is
defined by the provided Field structure, which specifies the
offset and size of the field within the memory region.
The result is returned as a u64 to accommodate the widest possible
integer size.
Sourcepub fn read_address(
&self,
ctx: impl Into<AccessContext>,
address_width: usize,
) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_address( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<u64, VmiError>
injector and utils only.Reads an address-sized unsigned integer from the virtual machine.
This method reads an address of the specified width (in bytes) from the given access context. It’s useful when dealing with architectures that can operate in different address modes.
Sourcepub fn read_address32(
&self,
ctx: impl Into<AccessContext>,
) -> Result<u64, VmiError>
Available on crate features injector and utils only.
pub fn read_address32( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_address64( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
injector and 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 features injector and utils only.
pub fn read_va( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_va32(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_va64(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u8>, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u8>, VmiError>
injector and utils only.Reads a null-terminated string of bytes from the virtual machine.
Sourcepub fn read_string_utf16_bytes_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<Vec<u16>, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u16>, VmiError>
injector and utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_string_utf16_bytes(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u16>, VmiError>
injector and 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 features injector and utils only.
pub fn read_string_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
injector and 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 features injector and utils only.
pub fn read_string( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
injector and utils only.Reads a null-terminated string from the virtual machine.
Sourcepub fn read_string_utf16_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<String, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
injector and utils only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_string_utf16(
&self,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError>
Available on crate features injector and utils only.
pub fn read_string_utf16( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
injector and 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 features injector and utils only.
pub fn read_struct<T>( &self, ctx: impl Into<AccessContext>, ) -> Result<T, VmiError>
injector and utils only.Reads a struct from the virtual machine.
Sourcepub fn translate_address(
&self,
ctx: impl Into<AddressContext>,
) -> Result<Pa, VmiError>
Available on crate features injector and utils only.
pub fn translate_address( &self, ctx: impl Into<AddressContext>, ) -> Result<Pa, VmiError>
injector and utils only.Translates a virtual address to a physical address.
Sourcepub fn translate_access_context(
&self,
ctx: AccessContext,
) -> Result<Pa, VmiError>
Available on crate features injector and utils only.
pub fn translate_access_context( &self, ctx: AccessContext, ) -> Result<Pa, VmiError>
injector and utils only.Translates an access context to a physical address.
Sourcepub fn read_page(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError>
Available on crate features injector and utils only.
pub fn read_page(&self, gfn: Gfn) -> Result<VmiMappedPage, VmiError>
injector and utils only.Reads a page of memory from the virtual machine.
Sourcepub fn write(
&self,
ctx: impl Into<AccessContext>,
buffer: &[u8],
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn write( &self, ctx: impl Into<AccessContext>, buffer: &[u8], ) -> Result<(), VmiError>
injector and utils only.Writes memory to the virtual machine.
Sourcepub fn write_u8(
&self,
ctx: impl Into<AccessContext>,
value: u8,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn write_u8( &self, ctx: impl Into<AccessContext>, value: u8, ) -> Result<(), VmiError>
injector and 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 features injector and utils only.
pub fn write_u16( &self, ctx: impl Into<AccessContext>, value: u16, ) -> Result<(), VmiError>
injector and 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 features injector and utils only.
pub fn write_u32( &self, ctx: impl Into<AccessContext>, value: u32, ) -> Result<(), VmiError>
injector and 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 features injector and utils only.
pub fn write_u64( &self, ctx: impl Into<AccessContext>, value: u64, ) -> Result<(), VmiError>
injector and 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 features injector and utils only.
pub fn write_struct<T>( &self, ctx: impl Into<AccessContext>, value: T, ) -> Result<(), VmiError>
injector and utils only.Writes a struct to the virtual machine.
Sourcepub fn memory_access(
&self,
gfn: Gfn,
view: View,
) -> Result<MemoryAccess, VmiError>
Available on crate features injector and utils only.
pub fn memory_access( &self, gfn: Gfn, view: View, ) -> Result<MemoryAccess, VmiError>
injector and utils only.Retrieves the memory access permissions for a specific guest frame number (GFN).
The returned MemoryAccess indicates the current read, write, and
execute permissions for the specified memory page in the given view.
Sourcepub fn set_memory_access(
&self,
gfn: Gfn,
view: View,
access: MemoryAccess,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn set_memory_access( &self, gfn: Gfn, view: View, access: MemoryAccess, ) -> Result<(), VmiError>
injector and utils only.Sets the memory access permissions for a specific guest frame number (GFN).
This method allows you to modify the read, write, and execute permissions for a given memory page in the specified view.
Sourcepub fn set_memory_access_with_options(
&self,
gfn: Gfn,
view: View,
access: MemoryAccess,
options: MemoryAccessOptions,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn set_memory_access_with_options( &self, gfn: Gfn, view: View, access: MemoryAccess, options: MemoryAccessOptions, ) -> Result<(), VmiError>
injector and utils only.Sets the memory access permissions for a specific guest frame number (GFN) with additional options.
In addition to the basic read, write, and execute permissions, this method allows you to specify additional options for the memory access.
Sourcepub fn registers(
&self,
vcpu: VcpuId,
) -> Result<<<Driver as VmiDriver>::Architecture as Architecture>::Registers, VmiError>
Available on crate features injector and utils only.
pub fn registers( &self, vcpu: VcpuId, ) -> Result<<<Driver as VmiDriver>::Architecture as Architecture>::Registers, VmiError>
injector and utils only.Retrieves the current state of CPU registers for a specified virtual CPU.
This method allows you to access the current values of CPU registers, which is crucial for understanding the state of the virtual machine at a given point in time.
§Notes
The exact structure and content of the returned registers depend on the
specific architecture of the VM being introspected. Refer to the
documentation of your Architecture implementation for details on
how to interpret the register values.
Examples found in repository?
9fn main() -> Result<(), Box<dyn std::error::Error>> {
10 let domain_id = 'x: {
11 for name in &["win7", "win10", "win11", "ubuntu22"] {
12 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
13 break 'x domain_id;
14 }
15 }
16
17 panic!("Domain not found");
18 };
19
20 // Setup VMI.
21 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
22 let vmi = VmiCore::new(driver)?;
23
24 // Get the interrupt descriptor table for each vCPU and print it.
25 let _pause_guard = vmi.pause_guard()?;
26 let info = vmi.info()?;
27 for vcpu_id in 0..info.vcpus {
28 let registers = vmi.registers(VcpuId(vcpu_id))?;
29 let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
30
31 println!("IDT[{vcpu_id}]: {idt:#?}");
32 }
33
34 Ok(())
35}More examples
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::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}15pub fn create_vmi_session() -> Result<Session, Box<dyn std::error::Error>> {
16 tracing_subscriber::fmt()
17 .with_max_level(tracing::Level::DEBUG)
18 .with_target(false)
19 .init();
20
21 let domain_id = 'x: {
22 for name in &["win7", "win10", "win11", "ubuntu22"] {
23 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
24 break 'x domain_id;
25 }
26 }
27
28 panic!("Domain not found");
29 };
30
31 tracing::debug!(?domain_id);
32
33 // Setup VMI.
34 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
35 let core = VmiCore::new(driver)?;
36
37 // Try to find the kernel information.
38 // This is necessary in order to load the profile.
39 let kernel_info = {
40 // Pause the vCPU to get consistent state.
41 let _pause_guard = core.pause_guard()?;
42
43 // Get the register state for the first vCPU.
44 let registers = core.registers(VcpuId(0))?;
45
46 // On AMD64 architecture, the kernel is usually found using the
47 // `MSR_LSTAR` register, which contains the address of the system call
48 // handler. This register is set by the operating system during boot
49 // and is left unchanged (unless some rootkits are involved).
50 //
51 // Therefore, we can take an arbitrary registers at any point in time
52 // (as long as the OS has booted and the page tables are set up) and
53 // use them to find the kernel.
54 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
55 };
56
57 // Load the profile.
58 // The profile contains offsets to kernel functions and data structures.
59 let isr = IsrCache::new("cache")?;
60 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
61 let entry = Box::leak(Box::new(entry));
62 let profile = entry.profile()?;
63
64 // Create the VMI session.
65 tracing::info!("Creating VMI session");
66 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
67
68 // Please don't do this in production code.
69 // This is only done for the sake of the example.
70 let core = Box::leak(Box::new(core));
71 let os = Box::leak(Box::new(os));
72
73 Ok((VmiSession::new(core, os), profile))
74}13fn main() -> Result<(), Box<dyn std::error::Error>> {
14 let domain_id = 'x: {
15 for name in &["win7", "win10", "win11", "ubuntu22"] {
16 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
17 break 'x domain_id;
18 }
19 }
20
21 panic!("Domain not found");
22 };
23
24 // Setup VMI.
25 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
26 let core = VmiCore::new(driver)?;
27
28 // Try to find the kernel information.
29 // This is necessary in order to load the profile.
30 let kernel_info = {
31 // Pause the VM to get consistent state.
32 let _pause_guard = core.pause_guard()?;
33
34 // Get the register state for the first vCPU.
35 let registers = core.registers(VcpuId(0))?;
36
37 // On AMD64 architecture, the kernel is usually found using the
38 // `MSR_LSTAR` register, which contains the address of the system call
39 // handler. This register is set by the operating system during boot
40 // and is left unchanged (unless some rootkits are involved).
41 //
42 // Therefore, we can take an arbitrary registers at any point in time
43 // (as long as the OS has booted and the page tables are set up) and
44 // use them to find the kernel.
45 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
46 };
47
48 // Load the profile.
49 // The profile contains offsets to kernel functions and data structures.
50 let isr = IsrCache::new("cache")?;
51 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
52 let profile = entry.profile()?;
53
54 // Create the VMI session.
55 tracing::info!("Creating VMI session");
56 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
57 let session = VmiSession::new(&core, &os);
58
59 // Pause the VM again to get consistent state.
60 let paused = session.pause_guard()?;
61
62 // Create a new `VmiState` with the boot CPU registers.
63 let vmi = paused.state();
64
65 // Get the list of processes and print them.
66 for process in vmi.os().processes()? {
67 let process = process?;
68
69 println!(
70 "{} [{}] {} (root @ {})",
71 process.object()?,
72 process.id()?,
73 process.name()?,
74 process.translation_root()?
75 );
76 }
77
78 Ok(())
79}245fn main() -> Result<(), Error> {
246 let filter = EnvFilter::default()
247 .add_directive(tracing::Level::DEBUG.into())
248 .add_directive("reqwest=warn".parse()?)
249 .add_directive("rustls=warn".parse()?);
250
251 tracing_subscriber::fmt()
252 .with_env_filter(filter)
253 .with_target(false)
254 .init();
255
256 let domain_id = match std::env::var("VMI_XEN_DOMAIN_ID") {
257 Ok(domain_id) => XenDomainId(
258 domain_id
259 .parse()
260 .context("invalid VMI_XEN_DOMAIN_ID environment variable")?,
261 ),
262 Err(_) => {
263 let domain_name = std::env::var("VMI_XEN_DOMAIN_NAME")
264 .context("invalid VMI_XEN_DOMAIN_NAME environment variable")?;
265
266 tracing::info!(%domain_name, "resolving domain ID");
267
268 match XenStore::new()?.domain_id_from_name(&domain_name)? {
269 Some(domain_id) => domain_id,
270 None => return Err(anyhow::anyhow!("domain not found: {domain_name}")),
271 }
272 }
273 };
274
275 // Setup VMI.
276 tracing::info!(%domain_id, "setting up VMI");
277 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
278 let core = VmiCore::new(driver)?;
279
280 // Try to find the kernel information.
281 // This is necessary in order to load the profile.
282 let kernel_info = {
283 let _pause_guard = core.pause_guard()?;
284 let registers = core.registers(VcpuId(0))?;
285
286 WindowsOs::find_kernel(&core, ®isters)?.context("cannot find kernel information")?
287 };
288
289 // Load the kernel profile.
290 // The profile contains offsets to kernel functions and data structures.
291 tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
292 let isr = IsrCache::new("cache")?;
293 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
294 let profile = entry.profile()?;
295
296 // Create the VMI session.
297 tracing::info!("creating VMI session");
298 let terminate_flag = Arc::new(AtomicBool::new(false));
299 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
300 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
301 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
302 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
303
304 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
305 let session = VmiSession::new(&core, &os);
306
307 let handler = NetIo::default();
308
309 //
310 // The following `let ncrypt_* = ...` lines demonstrate how to manually
311 // resolve a module, load its profile (symbols) and add it to the resolver
312 // via `with_module(_in_process)`.
313 //
314 // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
315 // will automatically resolve modules if they are not explicitly added.
316 //
317 // Manually resolving modules can be useful in cases where you want to deal
318 // with the resolved information (base address, profile) in other places.
319 //
320
321 let ncrypt_resolved = {
322 let paused = session.pause_guard()?;
323 let vmi = paused.state();
324
325 // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
326 // would also work, but this demonstrates how to use a custom predicate.
327 //
328 // Also, `match_lsass` is more strict, because it specifically looks
329 // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
330 // positives or potential malicious processes).
331 vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
332 .context("ncrypt.dll not found in lsass.exe")?
333 };
334
335 let ncrypt_process = ncrypt_resolved
336 .process
337 .context("resolved ncrypt.dll is not associated with a process")?;
338
339 let ncrypt_entry = isr
340 .entry_from_codeview(ncrypt_resolved.debug_signature)
341 .context("cannot find symbols for ncrypt.dll")?;
342
343 let ncrypt_profile = ncrypt_entry
344 .profile()
345 .context("cannot load profile for ncrypt.dll")?;
346
347 // The `SymbolCache` holds the resolved `isr::Entry` items.
348 let mut cache = SymbolCache::default();
349 let modules = ModuleResolver::default()
350 // `with_kernel` MUST be called if `Event` variants reference kernel
351 // symbols - like `NtWriteFile` in this example.
352 //
353 // This is because the "kernel" module is always optional.
354 .with_kernel(kernel_info.base_address, profile)
355 .with_module_in_process(
356 Module::NcryptDll,
357 ncrypt_process,
358 ncrypt_resolved.image_base,
359 ncrypt_profile,
360 )
361 // This will automatically resolve the `netio.sys` module and load
362 // its profile.
363 //
364 // Note that if we hadn't called `with_module_in_process` for
365 // `ncrypt.dll`, it would also be automatically resolved here.
366 .resolve(&session, &isr, &mut cache)?;
367
368 // Finally, we collect the events according to the resolved information
369 // and the metadata.
370 //
371 // For example, if some module/event is marked as `optional` and the
372 // resolver fails to resolve it, then it will simply not be included
373 // in the `events`.
374 let events = modules.into_events()?;
375
376 // And we're ready to create the reactor!
377 session.handle(|session| {
378 Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
379 })?;
380
381 Ok(())
382}67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }
239
240 #[tracing::instrument(skip_all)]
241 fn memory_access(
242 &mut self,
243 vmi: &VmiContext<WindowsOs<Driver>>,
244 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
245 let memory_access = vmi.event().reason().as_memory_access();
246
247 tracing::trace!(
248 pa = %memory_access.pa,
249 va = %memory_access.va,
250 access = %memory_access.access,
251 );
252
253 if memory_access.access.contains(MemoryAccess::W) {
254 // It is assumed that a write memory access event is caused by a
255 // page table modification.
256 //
257 // The page table entry is marked as dirty in the page table monitor
258 // and a singlestep is performed to process the dirty entries.
259 self.ptm
260 .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
261
262 Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
263 }
264 else if memory_access.access.contains(MemoryAccess::R) {
265 // When the guest tries to read from the memory, a fast-singlestep
266 // is performed over the instruction that tried to read the memory.
267 // This is done to allow the instruction to read the original memory
268 // content.
269 Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
270 }
271 else {
272 panic!("Unhandled memory access: {memory_access:?}");
273 }
274 }
275
276 #[tracing::instrument(skip_all, fields(pid, process))]
277 fn interrupt(
278 &mut self,
279 vmi: &VmiContext<WindowsOs<Driver>>,
280 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
281 let tag = match self.bpm.get_by_event(vmi.event(), ()) {
282 Some(breakpoints) => {
283 // Breakpoints can have multiple tags, but we have set only one
284 // tag for each breakpoint.
285 let first_breakpoint = breakpoints.into_iter().next().expect("breakpoint");
286 first_breakpoint.tag()
287 }
288 None => {
289 if BreakpointController::is_breakpoint(vmi, vmi.event())? {
290 // This breakpoint was not set by us. Reinject it.
291 tracing::warn!("Unknown breakpoint, reinjecting");
292 return Ok(VmiEventResponse::reinject_interrupt());
293 }
294 else {
295 // We have received a breakpoint event, but there is no
296 // breakpoint instruction at the current memory location.
297 // This can happen if the event was triggered by a breakpoint
298 // we just removed.
299 tracing::warn!("Ignoring old breakpoint event");
300 return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
301 }
302 }
303 };
304
305 let process = vmi.os().current_process()?;
306 let process_id = process.id()?;
307 let process_name = process.name()?;
308 tracing::Span::current()
309 .record("pid", process_id.0)
310 .record("process", process_name);
311
312 match tag {
313 "NtCreateFile" => self.NtCreateFile(vmi)?,
314 "NtWriteFile" => self.NtWriteFile(vmi)?,
315 "PspInsertProcess" => self.PspInsertProcess(vmi)?,
316 "MmCleanProcessAddressSpace" => self.MmCleanProcessAddressSpace(vmi)?,
317 _ => panic!("Unhandled tag: {tag}"),
318 }
319
320 Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
321 }
322
323 #[tracing::instrument(skip_all)]
324 fn singlestep(
325 &mut self,
326 vmi: &VmiContext<WindowsOs<Driver>>,
327 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
328 // Get the page table modifications by processing the dirty page table
329 // entries.
330 let ptm_events = self.ptm.process_dirty_entries(vmi, vmi.event().vcpu_id())?;
331
332 // Let the breakpoint controller handle the page table modifications.
333 self.bpm.handle_ptm_events(vmi, ptm_events)?;
334
335 // Disable singlestep and switch back to our view.
336 Ok(VmiEventResponse::default().with_view(self.view))
337 }
338
339 #[tracing::instrument(skip_all)]
340 fn NtCreateFile(&mut self, vmi: &VmiContext<WindowsOs<Driver>>) -> Result<(), VmiError> {
341 //
342 // NTSTATUS
343 // NtCreateFile (
344 // _Out_ PHANDLE FileHandle,
345 // _In_ ACCESS_MASK DesiredAccess,
346 // _In_ POBJECT_ATTRIBUTES ObjectAttributes,
347 // _Out_ PIO_STATUS_BLOCK IoStatusBlock,
348 // _In_opt_ PLARGE_INTEGER AllocationSize,
349 // _In_ ULONG FileAttributes,
350 // _In_ ULONG ShareAccess,
351 // _In_ ULONG CreateDisposition,
352 // _In_ ULONG CreateOptions,
353 // _In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
354 // _In_ ULONG EaLength
355 // );
356 //
357
358 let ObjectAttributes = Va(vmi.os().function_argument(2)?);
359
360 let object_attributes = vmi.os().object_attributes(ObjectAttributes)?;
361 let object_name = match object_attributes.object_name()? {
362 Some(object_name) => object_name,
363 None => {
364 tracing::warn!(%ObjectAttributes, "No object name found");
365 return Ok(());
366 }
367 };
368
369 tracing::info!(%object_name);
370
371 Ok(())
372 }
373
374 #[tracing::instrument(skip_all)]
375 fn NtWriteFile(&mut self, vmi: &VmiContext<WindowsOs<Driver>>) -> Result<(), VmiError> {
376 //
377 // NTSTATUS
378 // NtWriteFile (
379 // _In_ HANDLE FileHandle,
380 // _In_opt_ HANDLE Event,
381 // _In_opt_ PIO_APC_ROUTINE ApcRoutine,
382 // _In_opt_ PVOID ApcContext,
383 // _Out_ PIO_STATUS_BLOCK IoStatusBlock,
384 // _In_reads_bytes_(Length) PVOID Buffer,
385 // _In_ ULONG Length,
386 // _In_opt_ PLARGE_INTEGER ByteOffset,
387 // _In_opt_ PULONG Key
388 // );
389 //
390
391 let FileHandle = vmi.os().function_argument(0)?;
392
393 let file_object = match vmi
394 .os()
395 .current_process()?
396 .lookup_object::<WindowsFileObject<_>>(FileHandle)?
397 {
398 Some(file_object) => file_object,
399 None => {
400 tracing::warn!(FileHandle = %Hex(FileHandle), "No object found");
401 return Ok(());
402 }
403 };
404
405 let path = file_object.full_path()?;
406 tracing::info!(%path);
407
408 Ok(())
409 }
410
411 #[tracing::instrument(skip_all)]
412 fn PspInsertProcess(&mut self, vmi: &VmiContext<WindowsOs<Driver>>) -> Result<(), VmiError> {
413 //
414 // NTSTATUS
415 // PspInsertProcess (
416 // _In_ PEPROCESS NewProcess,
417 // _In_ PEPROCESS Parent,
418 // _In_ ULONG DesiredAccess,
419 // _In_ ULONG CreateFlags,
420 // ...
421 // );
422 //
423
424 let NewProcess = vmi.os().function_argument(0)?;
425 let Parent = vmi.os().function_argument(1)?;
426
427 let process = vmi.os().process(ProcessObject(Va(NewProcess)))?;
428 let process_id = process.id()?;
429
430 let parent_process = vmi.os().process(ProcessObject(Va(Parent)))?;
431 let parent_process_id = parent_process.id()?;
432
433 // We rely heavily on the 2nd argument to be the parent process object.
434 // If that ever changes, this assertion should catch it.
435 //
436 // So far it is verified that it works for Windows 7 up to Windows 11
437 // (23H2, build 22631).
438 debug_assert_eq!(parent_process_id, process.parent_id()?);
439
440 let name = process.name()?;
441 let image_base = process.image_base()?;
442 let peb = process.peb()?;
443
444 tracing::info!(
445 %process_id,
446 name,
447 %image_base,
448 ?peb,
449 );
450
451 Ok(())
452 }
453
454 #[tracing::instrument(skip_all)]
455 fn MmCleanProcessAddressSpace(
456 &mut self,
457 vmi: &VmiContext<WindowsOs<Driver>>,
458 ) -> Result<(), VmiError> {
459 //
460 // VOID
461 // MmCleanProcessAddressSpace (
462 // _In_ PEPROCESS Process
463 // );
464 //
465
466 let Process = vmi.os().function_argument(0)?;
467
468 let process = vmi.os().process(ProcessObject(Va(Process)))?;
469 let process_id = process.id()?;
470
471 let name = process.name()?;
472 let image_base = process.image_base()?;
473
474 tracing::info!(%process_id, name, %image_base);
475
476 Ok(())
477 }
478
479 fn dispatch(
480 &mut self,
481 vmi: &VmiContext<WindowsOs<Driver>>,
482 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
483 let event = vmi.event();
484 let result = match event.reason() {
485 EventReason::MemoryAccess(_) => self.memory_access(vmi),
486 EventReason::Interrupt(_) => self.interrupt(vmi),
487 EventReason::Singlestep(_) => self.singlestep(vmi),
488 _ => panic!("Unhandled event: {:?}", event.reason()),
489 };
490
491 // If VMI tries to read from a page that is not present, it will return
492 // a page fault error. In this case, we inject a page fault interrupt
493 // to the guest.
494 //
495 // Once the guest handles the page fault, it will retry to execute the
496 // instruction that caused the page fault.
497 if let Err(VmiError::Translation(pf)) = result {
498 tracing::warn!(?pf, "Page fault, injecting");
499 vmi.inject_interrupt(event.vcpu_id(), Interrupt::page_fault(pf.va, 0))?;
500 return Ok(VmiEventResponse::default());
501 }
502
503 result
504 }
505}
506
507impl<Driver> VmiHandler<WindowsOs<Driver>> for Monitor<Driver>
508where
509 Driver: VmiFullDriver<Architecture = Amd64>,
510{
511 type Output = ();
512
513 fn handle_event(&mut self, vmi: VmiContext<WindowsOs<Driver>>) -> VmiEventResponse<Amd64> {
514 // Flush the V2P cache on every event to avoid stale translations.
515 vmi.flush_v2p_cache();
516
517 self.dispatch(&vmi).expect("dispatch")
518 }
519
520 fn poll(&self) -> Option<Self::Output> {
521 self.terminate_flag.load(Ordering::Relaxed).then_some(())
522 }
523}
524
525fn main() -> Result<(), Box<dyn std::error::Error>> {
526 tracing_subscriber::fmt()
527 .with_max_level(tracing::Level::DEBUG)
528 .init();
529
530 let domain_id = 'x: {
531 for name in &["win7", "win10", "win11", "ubuntu22"] {
532 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
533 break 'x domain_id;
534 }
535 }
536
537 panic!("Domain not found");
538 };
539
540 tracing::debug!(?domain_id);
541
542 // Setup VMI.
543 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
544 let core = VmiCore::new(driver)?;
545
546 // Try to find the kernel information.
547 // This is necessary in order to load the profile.
548 let kernel_info = {
549 let _pause_guard = core.pause_guard()?;
550 let regs = core.registers(0.into())?;
551
552 WindowsOs::find_kernel(&core, ®s)?.expect("kernel information")
553 };
554
555 // Load the profile.
556 // The profile contains offsets to kernel functions and data structures.
557 let isr = IsrCache::new("cache")?;
558 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
559 let profile = entry.profile()?;
560
561 // Create the VMI session.
562 tracing::info!("Creating VMI session");
563 let terminate_flag = Arc::new(AtomicBool::new(false));
564 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
565 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
566 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
567 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
568
569 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
570 let session = VmiSession::new(&core, &os);
571
572 session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
573
574 Ok(())
575}Sourcepub fn set_registers(
&self,
vcpu: VcpuId,
registers: <<Driver as VmiDriver>::Architecture as Architecture>::Registers,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn set_registers( &self, vcpu: VcpuId, registers: <<Driver as VmiDriver>::Architecture as Architecture>::Registers, ) -> Result<(), VmiError>
injector and utils only.Sets the registers of a virtual CPU.
Sourcepub fn default_view(&self) -> View
Available on crate features injector and utils only.
pub fn default_view(&self) -> View
injector and utils only.Returns the default view for the virtual machine.
The default view typically represents the normal, unmodified state of the VM’s memory.
Examples found in repository?
241 fn memory_access(
242 &mut self,
243 vmi: &VmiContext<WindowsOs<Driver>>,
244 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
245 let memory_access = vmi.event().reason().as_memory_access();
246
247 tracing::trace!(
248 pa = %memory_access.pa,
249 va = %memory_access.va,
250 access = %memory_access.access,
251 );
252
253 if memory_access.access.contains(MemoryAccess::W) {
254 // It is assumed that a write memory access event is caused by a
255 // page table modification.
256 //
257 // The page table entry is marked as dirty in the page table monitor
258 // and a singlestep is performed to process the dirty entries.
259 self.ptm
260 .mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
261
262 Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
263 }
264 else if memory_access.access.contains(MemoryAccess::R) {
265 // When the guest tries to read from the memory, a fast-singlestep
266 // is performed over the instruction that tried to read the memory.
267 // This is done to allow the instruction to read the original memory
268 // content.
269 Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
270 }
271 else {
272 panic!("Unhandled memory access: {memory_access:?}");
273 }
274 }
275
276 #[tracing::instrument(skip_all, fields(pid, process))]
277 fn interrupt(
278 &mut self,
279 vmi: &VmiContext<WindowsOs<Driver>>,
280 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
281 let tag = match self.bpm.get_by_event(vmi.event(), ()) {
282 Some(breakpoints) => {
283 // Breakpoints can have multiple tags, but we have set only one
284 // tag for each breakpoint.
285 let first_breakpoint = breakpoints.into_iter().next().expect("breakpoint");
286 first_breakpoint.tag()
287 }
288 None => {
289 if BreakpointController::is_breakpoint(vmi, vmi.event())? {
290 // This breakpoint was not set by us. Reinject it.
291 tracing::warn!("Unknown breakpoint, reinjecting");
292 return Ok(VmiEventResponse::reinject_interrupt());
293 }
294 else {
295 // We have received a breakpoint event, but there is no
296 // breakpoint instruction at the current memory location.
297 // This can happen if the event was triggered by a breakpoint
298 // we just removed.
299 tracing::warn!("Ignoring old breakpoint event");
300 return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
301 }
302 }
303 };
304
305 let process = vmi.os().current_process()?;
306 let process_id = process.id()?;
307 let process_name = process.name()?;
308 tracing::Span::current()
309 .record("pid", process_id.0)
310 .record("process", process_name);
311
312 match tag {
313 "NtCreateFile" => self.NtCreateFile(vmi)?,
314 "NtWriteFile" => self.NtWriteFile(vmi)?,
315 "PspInsertProcess" => self.PspInsertProcess(vmi)?,
316 "MmCleanProcessAddressSpace" => self.MmCleanProcessAddressSpace(vmi)?,
317 _ => panic!("Unhandled tag: {tag}"),
318 }
319
320 Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
321 }Sourcepub fn create_view(
&self,
default_access: MemoryAccess,
) -> Result<View, VmiError>
Available on crate features injector and utils only.
pub fn create_view( &self, default_access: MemoryAccess, ) -> Result<View, VmiError>
injector and utils only.Creates a new view with the specified default access permissions.
Views allow for creating different perspectives of the VM’s memory, which can be useful for analysis or isolation purposes. The default access permissions apply to memory pages not explicitly modified within this view.
Examples found in repository?
67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Sourcepub fn destroy_view(&self, view: View) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn destroy_view(&self, view: View) -> Result<(), VmiError>
injector and utils only.Destroys a previously created view.
This method removes a view and frees associated resources. It should be called when a view is no longer needed to prevent resource leaks.
Sourcepub fn switch_to_view(&self, view: View) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn switch_to_view(&self, view: View) -> Result<(), VmiError>
injector and utils only.Switches to a different view for all virtual CPUs.
This method changes the current active view for all vCPUs, affecting subsequent memory operations across the entire VM. It allows for quick transitions between different memory perspectives globally.
Note the difference between this method and
VmiEventResponse::with_view():
switch_to_view()changes the view for all vCPUs immediately.VmiEventResponse::with_view()sets the view only for the specific vCPU that received the event, and the change is applied when the event handler returns.
Use switch_to_view() for global view changes, and
VmiEventResponse::with_view() for targeted, event-specific view
modifications on individual vCPUs.
Examples found in repository?
67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Sourcepub fn change_view_gfn(
&self,
view: View,
old_gfn: Gfn,
new_gfn: Gfn,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn change_view_gfn( &self, view: View, old_gfn: Gfn, new_gfn: Gfn, ) -> Result<(), VmiError>
injector and utils only.Changes the mapping of a guest frame number (GFN) in a specific view.
This method allows for remapping a GFN to a different physical frame within a view, enabling fine-grained control over memory layout in different views.
A notable use case for this method is implementing “stealth hooks”:
- 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 features injector and utils only.
pub fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError>
injector and utils only.Resets the mapping of a guest frame number (GFN) in a specific view to its original state.
This method reverts any custom mapping for the specified GFN in the given view, restoring it to the default mapping.
Sourcepub fn monitor_enable(
&self,
option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn monitor_enable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>
injector and utils only.Enables monitoring of specific events.
This method allows you to enable monitoring of specific events, such as control register writes, interrupts, or single-step execution. Monitoring events can be useful for tracking specific guest behavior or for implementing custom analysis tools.
The type of event to monitor is defined by the architecture-specific
Architecture::EventMonitor type.
When an event occurs, it will be passed to the event callback function for processing.
Examples found in repository?
67 pub fn new(
68 session: &VmiSession<WindowsOs<Driver>>,
69 profile: &Profile,
70 terminate_flag: Arc<AtomicBool>,
71 ) -> Result<Self, VmiError> {
72 // Capture the current state of the vCPU and get the base address of
73 // the kernel.
74 //
75 // This base address is essential to correctly offset monitored
76 // functions.
77 //
78 // NOTE: `kernel_image_base` tries to find the kernel in the memory
79 // with the help of the CPU registers. On AMD64 architecture,
80 // the kernel image base is usually found using the `MSR_LSTAR`
81 // register, which contains the address of the system call
82 // handler. This register is set by the operating system during
83 // boot and is left unchanged (unless some rootkits are involved).
84 //
85 // Therefore, we can take an arbitrary registers at any point
86 // in time (as long as the OS has booted and the page tables are
87 // set up) and use them to find the kernel image base.
88 let registers = session.registers(VcpuId(0))?;
89 let vmi = session.with_registers(®isters);
90
91 let kernel_image_base = vmi.os().kernel_image_base()?;
92 tracing::info!(%kernel_image_base);
93
94 // Get the system process.
95 //
96 // The system process is the first process created by the kernel.
97 // In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
98 // To monitor page table entries, we need to locate the translation root
99 // of this process.
100 let system_process = vmi.os().system_process()?;
101 tracing::info!(system_process = %system_process.object()?);
102
103 // Get the translation root of the system process.
104 // This is effectively "the CR3 of the kernel".
105 //
106 // The translation root is the root of the page table hierarchy (also
107 // known as the Directory Table Base or PML4).
108 let root = system_process.translation_root()?;
109 tracing::info!(%root);
110
111 // Load the symbols from the profile.
112 let symbols = Symbols::new(profile)?;
113
114 // Enable monitoring of the INT3 and singlestep events.
115 //
116 // INT3 is used to monitor the execution of specific functions.
117 // Singlestep is used to monitor the modifications of page table
118 // entries.
119 vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
120 vmi.monitor_enable(EventMonitor::Singlestep)?;
121
122 // Create a new view for the monitor.
123 // This view is used for monitoring function calls and memory accesses.
124 let view = vmi.create_view(MemoryAccess::RWX)?;
125 vmi.switch_to_view(view)?;
126
127 // Create a new breakpoint controller.
128 //
129 // The breakpoint controller is used to insert breakpoints for specific
130 // functions.
131 //
132 // From the guest's perspective, these breakpoints are "hidden", since
133 // the breakpoint controller will unset the read/write access to the
134 // physical memory page where the breakpoint is inserted, while keeping
135 // the execute access.
136 //
137 // This way, the guest will be able to execute the code, but attempts to
138 // read or write the memory will trigger the `memory_access` callback.
139 //
140 // When a vCPU tries to execute the breakpoint instruction:
141 // - an `interrupt` callback will be triggered
142 // - the breakpoint will be handled (e.g., log the function call)
143 // - a fast-singlestep[1] will be performed over the INT3 instruction
144 //
145 // When a vCPU tries to read from this page (e.g., a PatchGuard check):
146 // - `memory_access` callback will be triggered (with the `MemoryAccess::R`
147 // access type)
148 // - fast-singlestep[1] will be performed over the instruction that tried to
149 // read the memory
150 //
151 // This way, the instruction will read the original memory content.
152 //
153 // [1] Fast-singlestep is a VMI feature that allows to switch the vCPU
154 // to a different view, execute a single instruction, and then
155 // switch back to the original view. In this case, the view is
156 // switched to the `default_view` (which is unmodified).
157 let mut bpm = BreakpointManager::new();
158
159 // Create a new page table monitor.
160 //
161 // The page table monitor is used to monitor the page table entries of
162 // the hooked functions.
163 //
164 // More specifically, it is used to monitor the pages that the breakpoint
165 // was inserted into. This is necessary to handle the case when the
166 // page containing the breakpoint is paged out (and then paged in
167 // again).
168 //
169 // `PageTableMonitor` works by unsetting the write access to the page
170 // tables of the hooked functions. When the page is paged out, the
171 // `PRESENT` bit in the page table entry is unset and, conversely, when
172 // the page is paged in, the `PRESENT` bit is set again.
173 //
174 // When that happens:
175 // - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
176 // access type)
177 // - the callback will mark the page as dirty in the page table monitor
178 // - a singlestep will be performed over the instruction that tried to modify
179 // the memory containing the page table entry
180 // - the `singlestep` handler will process the dirty page table entries and
181 // inform the breakpoint controller to handle the changes
182 let mut ptm = PageTableMonitor::new();
183
184 // Pause the VM to avoid race conditions between inserting breakpoints
185 // and monitoring page table entries. The VM resumes when the pause
186 // guard is dropped.
187 let _pause_guard = vmi.pause_guard()?;
188
189 // Insert breakpoint for the `NtCreateFile` function.
190 let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
191 let cx_NtCreateFile = (va_NtCreateFile, root);
192 let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
193 .global()
194 .with_tag("NtCreateFile");
195 bpm.insert(&vmi, bp_NtCreateFile)?;
196 ptm.monitor(&vmi, cx_NtCreateFile, view, "NtCreateFile")?;
197 tracing::info!(%va_NtCreateFile);
198
199 // Insert breakpoint for the `NtWriteFile` function.
200 let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
201 let cx_NtWriteFile = (va_NtWriteFile, root);
202 let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
203 .global()
204 .with_tag("NtWriteFile");
205 bpm.insert(&vmi, bp_NtWriteFile)?;
206 ptm.monitor(&vmi, cx_NtWriteFile, view, "NtWriteFile")?;
207 tracing::info!(%va_NtWriteFile);
208
209 // Insert breakpoint for the `PspInsertProcess` function.
210 let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
211 let cx_PspInsertProcess = (va_PspInsertProcess, root);
212 let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
213 .global()
214 .with_tag("PspInsertProcess");
215 bpm.insert(&vmi, bp_PspInsertProcess)?;
216 ptm.monitor(&vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
217
218 // Insert breakpoint for the `MmCleanProcessAddressSpace` function.
219 let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
220 let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
221 let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
222 .global()
223 .with_tag("MmCleanProcessAddressSpace");
224 bpm.insert(&vmi, bp_MmCleanProcessAddressSpace)?;
225 ptm.monitor(
226 &vmi,
227 cx_MmCleanProcessAddressSpace,
228 view,
229 "MmCleanProcessAddressSpace",
230 )?;
231
232 Ok(Self {
233 terminate_flag,
234 view,
235 bpm,
236 ptm,
237 })
238 }Sourcepub fn monitor_disable(
&self,
option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn monitor_disable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>
injector and utils only.Disables monitoring of specific events.
This method allows you to disable monitoring of specific events that were previously enabled. It can be used to stop tracking certain hardware events or to reduce the overhead of event processing.
The type of event to disable is defined by the architecture-specific
Architecture::EventMonitor type.
Sourcepub fn events_pending(&self) -> usize
Available on crate features injector and utils only.
pub fn events_pending(&self) -> usize
injector and utils only.Returns the number of pending events.
This method provides a count of events that have occurred but have not yet been processed.
Sourcepub fn event_processing_overhead(&self) -> Duration
Available on crate features injector and utils only.
pub fn event_processing_overhead(&self) -> Duration
injector and utils only.Returns the time spent processing events by the driver.
This method provides a measure of the overhead introduced by event processing. It can be useful for performance tuning and understanding the impact of VMI operations on overall system performance.
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 features injector and 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>
injector and utils only.Waits for an event to occur and processes it with the provided handler.
This method blocks until an event occurs or the specified timeout is reached. When an event occurs, it is passed to the provided callback function for processing.
Sourcepub fn pause(&self) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn pause(&self) -> Result<(), VmiError>
injector and utils only.Pauses the virtual machine.
Sourcepub fn resume(&self) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn resume(&self) -> Result<(), VmiError>
injector and utils only.Resumes the virtual machine.
Sourcepub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError>
Available on crate features injector and utils only.
pub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError>
injector and utils only.Pauses the virtual machine and returns a guard that will resume it when dropped.
Examples found in repository?
9fn main() -> Result<(), Box<dyn std::error::Error>> {
10 let domain_id = 'x: {
11 for name in &["win7", "win10", "win11", "ubuntu22"] {
12 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
13 break 'x domain_id;
14 }
15 }
16
17 panic!("Domain not found");
18 };
19
20 // Setup VMI.
21 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
22 let vmi = VmiCore::new(driver)?;
23
24 // Get the interrupt descriptor table for each vCPU and print it.
25 let _pause_guard = vmi.pause_guard()?;
26 let info = vmi.info()?;
27 for vcpu_id in 0..info.vcpus {
28 let registers = vmi.registers(VcpuId(vcpu_id))?;
29 let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
30
31 println!("IDT[{vcpu_id}]: {idt:#?}");
32 }
33
34 Ok(())
35}More examples
525fn main() -> Result<(), Box<dyn std::error::Error>> {
526 tracing_subscriber::fmt()
527 .with_max_level(tracing::Level::DEBUG)
528 .init();
529
530 let domain_id = 'x: {
531 for name in &["win7", "win10", "win11", "ubuntu22"] {
532 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
533 break 'x domain_id;
534 }
535 }
536
537 panic!("Domain not found");
538 };
539
540 tracing::debug!(?domain_id);
541
542 // Setup VMI.
543 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
544 let core = VmiCore::new(driver)?;
545
546 // Try to find the kernel information.
547 // This is necessary in order to load the profile.
548 let kernel_info = {
549 let _pause_guard = core.pause_guard()?;
550 let regs = core.registers(0.into())?;
551
552 WindowsOs::find_kernel(&core, ®s)?.expect("kernel information")
553 };
554
555 // Load the profile.
556 // The profile contains offsets to kernel functions and data structures.
557 let isr = IsrCache::new("cache")?;
558 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
559 let profile = entry.profile()?;
560
561 // Create the VMI session.
562 tracing::info!("Creating VMI session");
563 let terminate_flag = Arc::new(AtomicBool::new(false));
564 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
565 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
566 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
567 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
568
569 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
570 let session = VmiSession::new(&core, &os);
571
572 session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
573
574 Ok(())
575}15pub fn create_vmi_session() -> Result<Session, Box<dyn std::error::Error>> {
16 tracing_subscriber::fmt()
17 .with_max_level(tracing::Level::DEBUG)
18 .with_target(false)
19 .init();
20
21 let domain_id = 'x: {
22 for name in &["win7", "win10", "win11", "ubuntu22"] {
23 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
24 break 'x domain_id;
25 }
26 }
27
28 panic!("Domain not found");
29 };
30
31 tracing::debug!(?domain_id);
32
33 // Setup VMI.
34 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
35 let core = VmiCore::new(driver)?;
36
37 // Try to find the kernel information.
38 // This is necessary in order to load the profile.
39 let kernel_info = {
40 // Pause the vCPU to get consistent state.
41 let _pause_guard = core.pause_guard()?;
42
43 // Get the register state for the first vCPU.
44 let registers = core.registers(VcpuId(0))?;
45
46 // On AMD64 architecture, the kernel is usually found using the
47 // `MSR_LSTAR` register, which contains the address of the system call
48 // handler. This register is set by the operating system during boot
49 // and is left unchanged (unless some rootkits are involved).
50 //
51 // Therefore, we can take an arbitrary registers at any point in time
52 // (as long as the OS has booted and the page tables are set up) and
53 // use them to find the kernel.
54 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
55 };
56
57 // Load the profile.
58 // The profile contains offsets to kernel functions and data structures.
59 let isr = IsrCache::new("cache")?;
60 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
61 let entry = Box::leak(Box::new(entry));
62 let profile = entry.profile()?;
63
64 // Create the VMI session.
65 tracing::info!("Creating VMI session");
66 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
67
68 // Please don't do this in production code.
69 // This is only done for the sake of the example.
70 let core = Box::leak(Box::new(core));
71 let os = Box::leak(Box::new(os));
72
73 Ok((VmiSession::new(core, os), profile))
74}13fn main() -> Result<(), Box<dyn std::error::Error>> {
14 let domain_id = 'x: {
15 for name in &["win7", "win10", "win11", "ubuntu22"] {
16 if let Some(domain_id) = XenStore::new()?.domain_id_from_name(name)? {
17 break 'x domain_id;
18 }
19 }
20
21 panic!("Domain not found");
22 };
23
24 // Setup VMI.
25 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
26 let core = VmiCore::new(driver)?;
27
28 // Try to find the kernel information.
29 // This is necessary in order to load the profile.
30 let kernel_info = {
31 // Pause the VM to get consistent state.
32 let _pause_guard = core.pause_guard()?;
33
34 // Get the register state for the first vCPU.
35 let registers = core.registers(VcpuId(0))?;
36
37 // On AMD64 architecture, the kernel is usually found using the
38 // `MSR_LSTAR` register, which contains the address of the system call
39 // handler. This register is set by the operating system during boot
40 // and is left unchanged (unless some rootkits are involved).
41 //
42 // Therefore, we can take an arbitrary registers at any point in time
43 // (as long as the OS has booted and the page tables are set up) and
44 // use them to find the kernel.
45 WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
46 };
47
48 // Load the profile.
49 // The profile contains offsets to kernel functions and data structures.
50 let isr = IsrCache::new("cache")?;
51 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
52 let profile = entry.profile()?;
53
54 // Create the VMI session.
55 tracing::info!("Creating VMI session");
56 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
57 let session = VmiSession::new(&core, &os);
58
59 // Pause the VM again to get consistent state.
60 let paused = session.pause_guard()?;
61
62 // Create a new `VmiState` with the boot CPU registers.
63 let vmi = paused.state();
64
65 // Get the list of processes and print them.
66 for process in vmi.os().processes()? {
67 let process = process?;
68
69 println!(
70 "{} [{}] {} (root @ {})",
71 process.object()?,
72 process.id()?,
73 process.name()?,
74 process.translation_root()?
75 );
76 }
77
78 Ok(())
79}245fn main() -> Result<(), Error> {
246 let filter = EnvFilter::default()
247 .add_directive(tracing::Level::DEBUG.into())
248 .add_directive("reqwest=warn".parse()?)
249 .add_directive("rustls=warn".parse()?);
250
251 tracing_subscriber::fmt()
252 .with_env_filter(filter)
253 .with_target(false)
254 .init();
255
256 let domain_id = match std::env::var("VMI_XEN_DOMAIN_ID") {
257 Ok(domain_id) => XenDomainId(
258 domain_id
259 .parse()
260 .context("invalid VMI_XEN_DOMAIN_ID environment variable")?,
261 ),
262 Err(_) => {
263 let domain_name = std::env::var("VMI_XEN_DOMAIN_NAME")
264 .context("invalid VMI_XEN_DOMAIN_NAME environment variable")?;
265
266 tracing::info!(%domain_name, "resolving domain ID");
267
268 match XenStore::new()?.domain_id_from_name(&domain_name)? {
269 Some(domain_id) => domain_id,
270 None => return Err(anyhow::anyhow!("domain not found: {domain_name}")),
271 }
272 }
273 };
274
275 // Setup VMI.
276 tracing::info!(%domain_id, "setting up VMI");
277 let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
278 let core = VmiCore::new(driver)?;
279
280 // Try to find the kernel information.
281 // This is necessary in order to load the profile.
282 let kernel_info = {
283 let _pause_guard = core.pause_guard()?;
284 let registers = core.registers(VcpuId(0))?;
285
286 WindowsOs::find_kernel(&core, ®isters)?.context("cannot find kernel information")?
287 };
288
289 // Load the kernel profile.
290 // The profile contains offsets to kernel functions and data structures.
291 tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
292 let isr = IsrCache::new("cache")?;
293 let entry = isr.entry_from_codeview(kernel_info.codeview)?;
294 let profile = entry.profile()?;
295
296 // Create the VMI session.
297 tracing::info!("creating VMI session");
298 let terminate_flag = Arc::new(AtomicBool::new(false));
299 signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
300 signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
301 signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
302 signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
303
304 let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
305 let session = VmiSession::new(&core, &os);
306
307 let handler = NetIo::default();
308
309 //
310 // The following `let ncrypt_* = ...` lines demonstrate how to manually
311 // resolve a module, load its profile (symbols) and add it to the resolver
312 // via `with_module(_in_process)`.
313 //
314 // Note that this is not strictly necessary, as `ModuleResolver::resolve()`
315 // will automatically resolve modules if they are not explicitly added.
316 //
317 // Manually resolving modules can be useful in cases where you want to deal
318 // with the resolved information (base address, profile) in other places.
319 //
320
321 let ncrypt_resolved = {
322 let paused = session.pause_guard()?;
323 let vmi = paused.state();
324
325 // Calling `resolve_user_module(&vmi, &isr, "ncrypt.dll", "lsass.exe")`
326 // would also work, but this demonstrates how to use a custom predicate.
327 //
328 // Also, `match_lsass` is more strict, because it specifically looks
329 // for "lsass.exe" in SessionId 0 (therefore, avoiding potential false
330 // positives or potential malicious processes).
331 vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
332 .context("ncrypt.dll not found in lsass.exe")?
333 };
334
335 let ncrypt_process = ncrypt_resolved
336 .process
337 .context("resolved ncrypt.dll is not associated with a process")?;
338
339 let ncrypt_entry = isr
340 .entry_from_codeview(ncrypt_resolved.debug_signature)
341 .context("cannot find symbols for ncrypt.dll")?;
342
343 let ncrypt_profile = ncrypt_entry
344 .profile()
345 .context("cannot load profile for ncrypt.dll")?;
346
347 // The `SymbolCache` holds the resolved `isr::Entry` items.
348 let mut cache = SymbolCache::default();
349 let modules = ModuleResolver::default()
350 // `with_kernel` MUST be called if `Event` variants reference kernel
351 // symbols - like `NtWriteFile` in this example.
352 //
353 // This is because the "kernel" module is always optional.
354 .with_kernel(kernel_info.base_address, profile)
355 .with_module_in_process(
356 Module::NcryptDll,
357 ncrypt_process,
358 ncrypt_resolved.image_base,
359 ncrypt_profile,
360 )
361 // This will automatically resolve the `netio.sys` module and load
362 // its profile.
363 //
364 // Note that if we hadn't called `with_module_in_process` for
365 // `ncrypt.dll`, it would also be automatically resolved here.
366 .resolve(&session, &isr, &mut cache)?;
367
368 // Finally, we collect the events according to the resolved information
369 // and the metadata.
370 //
371 // For example, if some module/event is marked as `optional` and the
372 // resolver fails to resolve it, then it will simply not be included
373 // in the `events`.
374 let events = modules.into_events()?;
375
376 // And we're ready to create the reactor!
377 session.handle(|session| {
378 Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
379 })?;
380
381 Ok(())
382}Sourcepub fn allocate_gfn(&self) -> Result<Gfn, VmiError>
Available on crate features injector and utils only.
pub fn allocate_gfn(&self) -> Result<Gfn, VmiError>
injector and utils only.Allocates a guest frame number (GFN).
This method allocates a new GFN, with the driver responsible for choosing the specific frame to allocate. It’s useful when you need to allocate new memory pages for the VM without caring about the specific location.
Sourcepub fn allocate_gfn_at(&self, gfn: Gfn) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn allocate_gfn_at(&self, gfn: Gfn) -> Result<(), VmiError>
injector and utils only.Allocates a guest frame number (GFN) at a specific location.
This method allows you to allocate a particular GFN. It’s useful when you need to allocate a specific memory page for the VM.
Sourcepub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
injector and utils only.Frees a previously allocated guest frame number (GFN).
This method deallocates a GFN that was previously allocated. It’s important to free GFNs when they’re no longer needed to prevent memory leaks in the VM.
Sourcepub fn inject_interrupt(
&self,
vcpu: VcpuId,
interrupt: <<Driver as VmiDriver>::Architecture as Architecture>::Interrupt,
) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn inject_interrupt( &self, vcpu: VcpuId, interrupt: <<Driver as VmiDriver>::Architecture as Architecture>::Interrupt, ) -> Result<(), VmiError>
injector and utils only.Injects an interrupt into a specific virtual CPU.
This method allows for the injection of architecture-specific interrupts into a given vCPU. It can be used to simulate hardware events or to manipulate the guest’s execution flow for analysis purposes.
The type of interrupt and its parameters are defined by the
architecture-specific Architecture::Interrupt type.
Examples found in repository?
479 fn dispatch(
480 &mut self,
481 vmi: &VmiContext<WindowsOs<Driver>>,
482 ) -> Result<VmiEventResponse<Amd64>, VmiError> {
483 let event = vmi.event();
484 let result = match event.reason() {
485 EventReason::MemoryAccess(_) => self.memory_access(vmi),
486 EventReason::Interrupt(_) => self.interrupt(vmi),
487 EventReason::Singlestep(_) => self.singlestep(vmi),
488 _ => panic!("Unhandled event: {:?}", event.reason()),
489 };
490
491 // If VMI tries to read from a page that is not present, it will return
492 // a page fault error. In this case, we inject a page fault interrupt
493 // to the guest.
494 //
495 // Once the guest handles the page fault, it will retry to execute the
496 // instruction that caused the page fault.
497 if let Err(VmiError::Translation(pf)) = result {
498 tracing::warn!(?pf, "Page fault, injecting");
499 vmi.inject_interrupt(event.vcpu_id(), Interrupt::page_fault(pf.va, 0))?;
500 return Ok(VmiEventResponse::default());
501 }
502
503 result
504 }Sourcepub fn reset_state(&self) -> Result<(), VmiError>
Available on crate features injector and utils only.
pub fn reset_state(&self) -> Result<(), VmiError>
injector and utils only.Resets the state of the VMI system.
This method clears all event monitors, caches, and any other stateful data maintained by the VMI system. It’s useful for bringing the VMI system back to a known clean state, which can be necessary when switching between different analysis tasks or recovering from error conditions.
Trait Implementations§
impl<Os> Copy for VmiState<'_, Os>where
Os: VmiOs,
Auto Trait Implementations§
impl<'a, Os> !RefUnwindSafe for VmiState<'a, Os>
impl<'a, Os> !Send for VmiState<'a, Os>
impl<'a, Os> !Sync for VmiState<'a, Os>
impl<'a, Os> !UnwindSafe for VmiState<'a, Os>
impl<'a, Os> Freeze for VmiState<'a, Os>
impl<'a, Os> Unpin for VmiState<'a, Os>
impl<'a, Os> UnsafeUnpin for VmiState<'a, Os>
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.