pub struct VmiCore<Driver>where
Driver: VmiDriver,{ /* private fields */ }Expand description
The core functionality for Virtual Machine Introspection (VMI).
Implementations§
Source§impl<Driver> VmiCore<Driver>where
Driver: VmiDriver,
impl<Driver> VmiCore<Driver>where
Driver: VmiDriver,
Sourcepub fn new(driver: Driver) -> Result<VmiCore<Driver>, VmiError>
Available on crate features utils and injector only.
pub fn new(driver: Driver) -> Result<VmiCore<Driver>, VmiError>
utils and injector only.Creates a new VmiCore instance with the given driver.
Both the GFN cache and the V2P cache are enabled by default, each with a capacity of 8192 entries.
Examples found in repository?
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let vmi = VmiCore::new(driver)?;
// Get the interrupt descriptor table for each VCPU and print it.
let _pause_guard = vmi.pause_guard()?;
let info = vmi.info()?;
for vcpu_id in 0..info.vcpus {
let registers = vmi.registers(VcpuId(vcpu_id))?;
let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
println!("IDT[{vcpu_id}]: {idt:#?}");
}
Ok(())
}More examples
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
// Try to find the kernel information.
// This is necessary in order to load the profile.
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let registers = core.registers(VcpuId(0))?;
WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
};
// Load the profile.
// The profile contains offsets to kernel functions and data structures.
let isr = IsrCache::<JsonCodec>::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = entry.profile()?;
// Create the VMI session.
tracing::info!("Creating VMI session");
let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
let session = VmiSession::new(core, os);
// Get the list of processes and print them.
let _pause_guard = session.pause_guard()?;
let registers = session.registers(VcpuId(0))?;
let processes = session.os().processes(®isters)?;
println!("Processes: {processes:#?}");
Ok(())
}13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
pub fn create_vmi_session() -> Result<
(
VmiSession<VmiXenDriver<Amd64>, WindowsOs<VmiXenDriver<Amd64>>>,
Profile<'static>,
),
Box<dyn std::error::Error>,
> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_target(false)
.init();
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
tracing::debug!(?domain_id);
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
// Try to find the kernel information.
// This is necessary in order to load the profile.
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let registers = core.registers(VcpuId(0))?;
WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
};
// Load the profile.
// The profile contains offsets to kernel functions and data structures.
//
// The entry is loaded into a static variable to enable returning
// `Profile<'static>` to the caller.
static ENTRY: OnceLock<Entry<JsonCodec>> = OnceLock::new();
let isr = IsrCache::<JsonCodec>::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = ENTRY.get_or_init(|| entry).profile()?;
// Create the VMI session.
tracing::info!("Creating VMI session");
let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
Ok((VmiSession::new(core, os), profile))
}549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
tracing::debug!(?domain_id);
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
// Try to find the kernel information.
// This is necessary in order to load the profile.
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let regs = core.registers(0.into())?;
WindowsOs::find_kernel(&core, ®s)?.expect("kernel information")
};
// Load the profile.
// The profile contains offsets to kernel functions and data structures.
let isr = IsrCache::<JsonCodec>::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = entry.profile()?;
// Create the VMI session.
tracing::info!("Creating VMI session");
let terminate_flag = Arc::new(AtomicBool::new(false));
signal_hook::flag::register(signal_hook::consts::SIGHUP, terminate_flag.clone())?;
signal_hook::flag::register(signal_hook::consts::SIGINT, terminate_flag.clone())?;
signal_hook::flag::register(signal_hook::consts::SIGALRM, terminate_flag.clone())?;
signal_hook::flag::register(signal_hook::consts::SIGTERM, terminate_flag.clone())?;
let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
let session = VmiSession::new(core, os);
session.handle(|session| Monitor::new(session, &profile, terminate_flag))?;
Ok(())
}Sourcepub fn with_gfn_cache(self, size: usize) -> VmiCore<Driver>
Available on crate features utils and injector only.
pub fn with_gfn_cache(self, size: usize) -> VmiCore<Driver>
utils and injector only.Enables the Guest Frame Number (GFN) cache.
The GFN cache stores the contents of recently accessed memory pages, indexed by their GFN. This can significantly improve performance when repeatedly accessing the same memory regions, as it avoids redundant reads from the virtual machine.
When enabled, subsequent calls to read_page will first check
the cache before querying the driver.
§Panics
Panics if size is zero.
Sourcepub fn enable_gfn_cache(&mut self)
Available on crate features utils and injector only.
pub fn enable_gfn_cache(&mut self)
utils and injector only.Enables the GFN cache.
See with_gfn_cache for more details.
Sourcepub fn disable_gfn_cache(&mut self)
Available on crate features utils and injector only.
pub fn disable_gfn_cache(&mut self)
utils and injector only.Disables the GFN cache.
Subsequent calls to read_page will bypass the cache and read
directly from the virtual machine.
Sourcepub fn resize_gfn_cache(&mut self, size: usize)
Available on crate features utils and injector only.
pub fn resize_gfn_cache(&mut self, size: usize)
utils and injector only.Resizes the GFN cache.
This allows you to adjust the cache size dynamically based on your performance needs. A larger cache can improve performance for workloads with high memory locality, but consumes more memory.
§Panics
Panics if size is zero.
Sourcepub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage>
Available on crate features utils and injector only.
pub fn flush_gfn_cache_entry(&self, gfn: Gfn) -> Option<VmiMappedPage>
utils and injector 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 utils and injector only.
pub fn flush_gfn_cache(&self)
utils and injector only.Clears the entire GFN cache.
Sourcepub fn with_v2p_cache(self, size: usize) -> VmiCore<Driver>
Available on crate features utils and injector only.
pub fn with_v2p_cache(self, size: usize) -> VmiCore<Driver>
utils and injector only.Enables the virtual-to-physical (V2P) address translation cache.
The V2P cache stores the results of recent address translations,
mapping virtual addresses (represented by AccessContext) to their
corresponding physical addresses (Pa). This can significantly
speed up memory access operations, as address translation can be a
relatively expensive operation.
When enabled, translate_access_context will consult the cache
before performing a full translation.
§Panics
Panics if size is zero.
Sourcepub fn enable_v2p_cache(&mut self)
Available on crate features utils and injector only.
pub fn enable_v2p_cache(&mut self)
utils and injector only.Enables the V2P cache.
See with_v2p_cache for more details.
Sourcepub fn disable_v2p_cache(&mut self)
Available on crate features utils and injector only.
pub fn disable_v2p_cache(&mut self)
utils and injector only.Disables the V2P cache.
Subsequent calls to translate_access_context will bypass the cache
and perform a full address translation every time.
Sourcepub fn resize_v2p_cache(&mut self, size: usize)
Available on crate features utils and injector only.
pub fn resize_v2p_cache(&mut self, size: usize)
utils and injector only.Resizes the V2P cache.
This allows dynamic adjustment of the cache size to balance performance and memory usage. A larger cache can lead to better performance if address translations are frequent and exhibit good locality.
§Panics
Panics if size is zero.
Sourcepub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa>
Available on crate features utils and injector only.
pub fn flush_v2p_cache_entry(&self, ctx: AccessContext) -> Option<Pa>
utils and injector 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 utils and injector only.
pub fn flush_v2p_cache(&self)
utils and injector 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 with_read_string_length_limit(
self,
limit_in_bytes: usize,
) -> VmiCore<Driver>
Available on crate features utils and injector only.
pub fn with_read_string_length_limit( self, limit_in_bytes: usize, ) -> VmiCore<Driver>
utils and injector only.Sets a limit on the length of strings read by the read_string methods.
If the limit is reached, the string will be truncated.
Sourcepub fn read_string_length_limit(&self) -> Option<usize>
Available on crate features utils and injector only.
pub fn read_string_length_limit(&self) -> Option<usize>
utils and injector 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 utils and injector only.
pub fn set_read_string_length_limit(&self, limit: usize)
utils and injector 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 elapsed(&self) -> Duration
Available on crate features utils and injector only.
pub fn elapsed(&self) -> Duration
utils and injector only.Returns the duration since this VmiCore instance was created.
Sourcepub fn info(&self) -> Result<VmiInfo, VmiError>
Available on crate features utils and injector only.
pub fn info(&self) -> Result<VmiInfo, VmiError>
utils and injector only.Retrieves information about the virtual machine.
Examples found in repository?
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let vmi = VmiCore::new(driver)?;
// Get the interrupt descriptor table for each VCPU and print it.
let _pause_guard = vmi.pause_guard()?;
let info = vmi.info()?;
for vcpu_id in 0..info.vcpus {
let registers = vmi.registers(VcpuId(vcpu_id))?;
let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
println!("IDT[{vcpu_id}]: {idt:#?}");
}
Ok(())
}Sourcepub fn pause(&self) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn pause(&self) -> Result<(), VmiError>
utils and injector only.Pauses the virtual machine.
Sourcepub fn resume(&self) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn resume(&self) -> Result<(), VmiError>
utils and injector only.Resumes the virtual machine.
Sourcepub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError>
Available on crate features utils and injector only.
pub fn pause_guard(&self) -> Result<VmiPauseGuard<'_, Driver>, VmiError>
utils and injector only.Pauses the virtual machine and returns a guard that will resume it when dropped.
Examples found in repository?
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let vmi = VmiCore::new(driver)?;
// Get the interrupt descriptor table for each VCPU and print it.
let _pause_guard = vmi.pause_guard()?;
let info = vmi.info()?;
for vcpu_id in 0..info.vcpus {
let registers = vmi.registers(VcpuId(vcpu_id))?;
let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
println!("IDT[{vcpu_id}]: {idt:#?}");
}
Ok(())
}More examples
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (vmi, profile) = _common::create_vmi_session()?;
let processes = {
let _pause_guard = vmi.pause_guard()?;
let registers = vmi.registers(VcpuId(0))?;
vmi.os().processes(®isters)?
};
let explorer = processes
.iter()
.find(|process| process.name.to_lowercase() == "explorer.exe")
.expect("explorer.exe");
tracing::info!(
pid = %explorer.id,
object = %explorer.object,
"found explorer.exe"
);
vmi.handle(|vmi| {
InjectorHandler::new(
vmi,
&profile,
explorer.id,
recipe_factory(MessageBox::new(
"Hello, World!",
"This is a message box from the VMI!",
)),
)
})?;
Ok(())
}202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (vmi, profile) = _common::create_vmi_session()?;
let processes = {
let _pause_guard = vmi.pause_guard()?;
let registers = vmi.registers(VcpuId(0))?;
vmi.os().processes(®isters)?
};
let explorer = processes
.iter()
.find(|process| process.name.to_lowercase() == "explorer.exe")
.expect("explorer.exe");
tracing::info!(
pid = %explorer.id,
object = %explorer.object,
"found explorer.exe"
);
vmi.handle(|vmi| {
InjectorHandler::new(
vmi,
&profile,
explorer.id,
recipe_factory(GuestFile::new(
"C:\\Users\\John\\Desktop\\test.txt",
"Hello, World!".as_bytes(),
)),
)
})?;
Ok(())
}297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (vmi, profile) = _common::create_vmi_session()?;
let processes = {
let _pause_guard = vmi.pause_guard()?;
let registers = vmi.registers(VcpuId(0))?;
vmi.os().processes(®isters)?
};
let explorer = processes
.iter()
.find(|process| process.name.to_lowercase() == "explorer.exe")
.expect("explorer.exe");
tracing::info!(
pid = %explorer.id,
object = %explorer.object,
"found explorer.exe"
);
let mut content = Vec::new();
for c in 'A'..='Z' {
content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
}
vmi.handle(|vmi| {
InjectorHandler::new(
vmi,
&profile,
explorer.id,
recipe_factory(GuestFile::new(
"C:\\Users\\John\\Desktop\\test.txt",
content,
)),
)
})?;
Ok(())
}8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
// Try to find the kernel information.
// This is necessary in order to load the profile.
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let registers = core.registers(VcpuId(0))?;
WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
};
// Load the profile.
// The profile contains offsets to kernel functions and data structures.
let isr = IsrCache::<JsonCodec>::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = entry.profile()?;
// Create the VMI session.
tracing::info!("Creating VMI session");
let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
let session = VmiSession::new(core, os);
// Get the list of processes and print them.
let _pause_guard = session.pause_guard()?;
let registers = session.registers(VcpuId(0))?;
let processes = session.os().processes(®isters)?;
println!("Processes: {processes:#?}");
Ok(())
}13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
pub fn create_vmi_session() -> Result<
(
VmiSession<VmiXenDriver<Amd64>, WindowsOs<VmiXenDriver<Amd64>>>,
Profile<'static>,
),
Box<dyn std::error::Error>,
> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_target(false)
.init();
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
tracing::debug!(?domain_id);
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
// Try to find the kernel information.
// This is necessary in order to load the profile.
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let registers = core.registers(VcpuId(0))?;
WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
};
// Load the profile.
// The profile contains offsets to kernel functions and data structures.
//
// The entry is loaded into a static variable to enable returning
// `Profile<'static>` to the caller.
static ENTRY: OnceLock<Entry<JsonCodec>> = OnceLock::new();
let isr = IsrCache::<JsonCodec>::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = ENTRY.get_or_init(|| entry).profile()?;
// Create the VMI session.
tracing::info!("Creating VMI session");
let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
Ok((VmiSession::new(core, os), profile))
}Sourcepub fn registers(
&self,
vcpu: VcpuId,
) -> Result<<<Driver as VmiDriver>::Architecture as Architecture>::Registers, VmiError>
Available on crate features utils and injector only.
pub fn registers( &self, vcpu: VcpuId, ) -> Result<<<Driver as VmiDriver>::Architecture as Architecture>::Registers, VmiError>
utils and injector 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?
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let vmi = VmiCore::new(driver)?;
// Get the interrupt descriptor table for each VCPU and print it.
let _pause_guard = vmi.pause_guard()?;
let info = vmi.info()?;
for vcpu_id in 0..info.vcpus {
let registers = vmi.registers(VcpuId(vcpu_id))?;
let idt = Amd64::interrupt_descriptor_table(&vmi, ®isters)?;
println!("IDT[{vcpu_id}]: {idt:#?}");
}
Ok(())
}More examples
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (vmi, profile) = _common::create_vmi_session()?;
let processes = {
let _pause_guard = vmi.pause_guard()?;
let registers = vmi.registers(VcpuId(0))?;
vmi.os().processes(®isters)?
};
let explorer = processes
.iter()
.find(|process| process.name.to_lowercase() == "explorer.exe")
.expect("explorer.exe");
tracing::info!(
pid = %explorer.id,
object = %explorer.object,
"found explorer.exe"
);
vmi.handle(|vmi| {
InjectorHandler::new(
vmi,
&profile,
explorer.id,
recipe_factory(MessageBox::new(
"Hello, World!",
"This is a message box from the VMI!",
)),
)
})?;
Ok(())
}202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (vmi, profile) = _common::create_vmi_session()?;
let processes = {
let _pause_guard = vmi.pause_guard()?;
let registers = vmi.registers(VcpuId(0))?;
vmi.os().processes(®isters)?
};
let explorer = processes
.iter()
.find(|process| process.name.to_lowercase() == "explorer.exe")
.expect("explorer.exe");
tracing::info!(
pid = %explorer.id,
object = %explorer.object,
"found explorer.exe"
);
vmi.handle(|vmi| {
InjectorHandler::new(
vmi,
&profile,
explorer.id,
recipe_factory(GuestFile::new(
"C:\\Users\\John\\Desktop\\test.txt",
"Hello, World!".as_bytes(),
)),
)
})?;
Ok(())
}297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (vmi, profile) = _common::create_vmi_session()?;
let processes = {
let _pause_guard = vmi.pause_guard()?;
let registers = vmi.registers(VcpuId(0))?;
vmi.os().processes(®isters)?
};
let explorer = processes
.iter()
.find(|process| process.name.to_lowercase() == "explorer.exe")
.expect("explorer.exe");
tracing::info!(
pid = %explorer.id,
object = %explorer.object,
"found explorer.exe"
);
let mut content = Vec::new();
for c in 'A'..='Z' {
content.extend((0..2049).map(|_| c as u8).collect::<Vec<_>>());
}
vmi.handle(|vmi| {
InjectorHandler::new(
vmi,
&profile,
explorer.id,
recipe_factory(GuestFile::new(
"C:\\Users\\John\\Desktop\\test.txt",
content,
)),
)
})?;
Ok(())
}8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
fn main() -> Result<(), Box<dyn std::error::Error>> {
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
// Try to find the kernel information.
// This is necessary in order to load the profile.
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let registers = core.registers(VcpuId(0))?;
WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
};
// Load the profile.
// The profile contains offsets to kernel functions and data structures.
let isr = IsrCache::<JsonCodec>::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = entry.profile()?;
// Create the VMI session.
tracing::info!("Creating VMI session");
let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
let session = VmiSession::new(core, os);
// Get the list of processes and print them.
let _pause_guard = session.pause_guard()?;
let registers = session.registers(VcpuId(0))?;
let processes = session.os().processes(®isters)?;
println!("Processes: {processes:#?}");
Ok(())
}13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
pub fn create_vmi_session() -> Result<
(
VmiSession<VmiXenDriver<Amd64>, WindowsOs<VmiXenDriver<Amd64>>>,
Profile<'static>,
),
Box<dyn std::error::Error>,
> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_target(false)
.init();
let domain_id = 'x: {
for name in &["win7", "win10", "win11", "ubuntu22"] {
if let Some(domain_id) = XenStore::domain_id_from_name(name)? {
break 'x domain_id;
}
}
panic!("Domain not found");
};
tracing::debug!(?domain_id);
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
// Try to find the kernel information.
// This is necessary in order to load the profile.
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let registers = core.registers(VcpuId(0))?;
WindowsOs::find_kernel(&core, ®isters)?.expect("kernel information")
};
// Load the profile.
// The profile contains offsets to kernel functions and data structures.
//
// The entry is loaded into a static variable to enable returning
// `Profile<'static>` to the caller.
static ENTRY: OnceLock<Entry<JsonCodec>> = OnceLock::new();
let isr = IsrCache::<JsonCodec>::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = ENTRY.get_or_init(|| entry).profile()?;
// Create the VMI session.
tracing::info!("Creating VMI session");
let os = WindowsOs::<VmiXenDriver<Amd64>>::new(&profile)?;
Ok((VmiSession::new(core, os), profile))
}Sourcepub fn set_registers(
&self,
vcpu: VcpuId,
registers: <<Driver as VmiDriver>::Architecture as Architecture>::Registers,
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn set_registers( &self, vcpu: VcpuId, registers: <<Driver as VmiDriver>::Architecture as Architecture>::Registers, ) -> Result<(), VmiError>
utils and injector only.Sets the registers of a virtual CPU.
Sourcepub fn memory_access(
&self,
gfn: Gfn,
view: View,
) -> Result<MemoryAccess, VmiError>
Available on crate features utils and injector only.
pub fn memory_access( &self, gfn: Gfn, view: View, ) -> Result<MemoryAccess, VmiError>
utils and injector 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 utils and injector only.
pub fn set_memory_access( &self, gfn: Gfn, view: View, access: MemoryAccess, ) -> Result<(), VmiError>
utils and injector 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 allocate_next_available_gfn(&self) -> Result<Gfn, VmiError>
Available on crate features utils and injector only.
pub fn allocate_next_available_gfn(&self) -> Result<Gfn, VmiError>
utils and injector only.Allocates the next available guest frame number (GFN).
This method finds and allocates the next free GFN after the current maximum GFN. It’s useful when you need to allocate new memory pages for the VM.
Sourcepub fn allocate_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn allocate_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
utils and injector only.Allocates a specific guest frame number (GFN).
This method allows you to allocate a particular GFN. It’s useful when you need to allocate a specific memory page for the VM.
Sourcepub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn free_gfn(&self, gfn: Gfn) -> Result<(), VmiError>
utils and injector only.Frees a previously allocated guest frame number (GFN).
This method deallocates a GFN that was previously allocated. It’s important to free GFNs when they’re no longer needed to prevent memory leaks in the VM.
Sourcepub fn default_view(&self) -> View
Available on crate features utils and injector only.
pub fn default_view(&self) -> View
utils and injector only.Returns the default view for the virtual machine.
The default view typically represents the normal, unmodified state of the VM’s memory.
Sourcepub fn create_view(
&self,
default_access: MemoryAccess,
) -> Result<View, VmiError>
Available on crate features utils and injector only.
pub fn create_view( &self, default_access: MemoryAccess, ) -> Result<View, VmiError>
utils and injector only.Creates a new view with the specified default access permissions.
Views allow for creating different perspectives of the VM’s memory, which can be useful for analysis or isolation purposes. The default access permissions apply to memory pages not explicitly modified within this view.
Examples found in repository?
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
pub fn new(
vmi: &VmiSession<Driver, WindowsOs<Driver>>,
profile: &Profile,
terminate_flag: Arc<AtomicBool>,
) -> Result<Self, VmiError> {
// Get the base address of the kernel.
// This base address is essential to correctly offset monitored
// functions.
//
// NOTE: `kernel_image_base` tries to find the kernel in the memory
// with the help of the CPU registers. On AMD64 architecture,
// the kernel image base is usually found using the `MSR_LSTAR`
// register, which contains the address of the system call
// handler. This register is set by the operating system during
// boot and is left unchanged (unless some rootkits are involved).
//
// Therefore, what we are doing here is querying the registers
// of the first VCPU and trying to find the kernel image base
// address using them.
let registers = vmi.registers(VcpuId(0))?;
let kernel_image_base = vmi.os().kernel_image_base(®isters)?;
tracing::info!(%kernel_image_base);
// Get the system process.
//
// The system process is the first process created by the kernel.
// In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
// To monitor page table entries, we need to locate the translation root
// of this process.
let system_process = vmi.os().system_process(®isters)?;
tracing::info!(%system_process);
// Get the translation root of the system process.
// This is effectively "the CR3 of the kernel".
//
// The translation root is the root of the page table hierarchy (also
// known as the Directory Table Base or PML4).
let root = vmi
.os()
.process_translation_root(®isters, system_process)?;
tracing::info!(%root);
// Load the symbols from the profile.
let symbols = Symbols::new(profile)?;
// Enable monitoring of the INT3 and singlestep events.
//
// INT3 is used to monitor the execution of specific functions.
// Singlestep is used to monitor the modifications of page table
// entries.
vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
vmi.monitor_enable(EventMonitor::Singlestep)?;
// Create a new view for the monitor.
// This view is used for monitoring function calls and memory accesses.
let view = vmi.create_view(MemoryAccess::RWX)?;
vmi.switch_to_view(view)?;
// Create a new breakpoint controller.
//
// The breakpoint controller is used to insert breakpoints for specific
// functions.
//
// From the guest's perspective, these breakpoints are "hidden", since
// the breakpoint controller will unset the read/write access to the
// physical memory page where the breakpoint is inserted, while keeping
// the execute access.
//
// This way, the guest will be able to execute the code, but attempts to
// read or write the memory will trigger the `memory_access` callback.
//
// When a VCPU tries to execute the breakpoint instruction:
// - an `interrupt` callback will be triggered
// - the breakpoint will be handled (e.g., log the function call)
// - a fast-singlestep[1] will be performed over the INT3 instruction
//
// When a VCPU tries to read from this page (e.g., a PatchGuard check):
// - `memory_access` callback will be triggered (with the `MemoryAccess::R`
// access type)
// - fast-singlestep[1] will be performed over the instruction that tried to
// read the memory
//
// This way, the instruction will read the original memory content.
//
// [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
// to a different view, execute a single instruction, and then
// switch back to the original view. In this case, the view is
// switched to the `default_view` (which is unmodified).
let mut bpm = BreakpointManager::new();
// Create a new page table monitor.
//
// The page table monitor is used to monitor the page table entries of
// the hooked functions.
//
// More specifically, it is used to monitor the pages that the breakpoint
// was inserted into. This is necessary to handle the case when the
// page containing the breakpoint is paged out (and then paged in
// again).
//
// `PageTableMonitor` works by unsetting the write access to the page
// tables of the hooked functions. When the page is paged out, the
// `PRESENT` bit in the page table entry is unset and, conversely, when
// the page is paged in, the `PRESENT` bit is set again.
//
// When that happens:
// - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
// access type)
// - the callback will mark the page as dirty in the page table monitor
// - a singlestep will be performed over the instruction that tried to modify
// the memory containing the page table entry
// - the `singlestep` handler will process the dirty page table entries and
// inform the breakpoint controller to handle the changes
let mut ptm = PageTableMonitor::new();
// Pause the VM to avoid race conditions between inserting breakpoints
// and monitoring page table entries. The VM resumes when the pause
// guard is dropped.
let _pause_guard = vmi.pause_guard()?;
// Insert breakpoint for the `NtCreateFile` function.
let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
let cx_NtCreateFile = (va_NtCreateFile, root);
let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
.global()
.with_tag("NtCreateFile");
bpm.insert(vmi, bp_NtCreateFile)?;
ptm.monitor(vmi, cx_NtCreateFile, view, "NtCreateFile")?;
tracing::info!(%va_NtCreateFile);
// Insert breakpoint for the `NtWriteFile` function.
let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
let cx_NtWriteFile = (va_NtWriteFile, root);
let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
.global()
.with_tag("NtWriteFile");
bpm.insert(vmi, bp_NtWriteFile)?;
ptm.monitor(vmi, cx_NtWriteFile, view, "NtWriteFile")?;
tracing::info!(%va_NtWriteFile);
// Insert breakpoint for the `PspInsertProcess` function.
let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
let cx_PspInsertProcess = (va_PspInsertProcess, root);
let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
.global()
.with_tag("PspInsertProcess");
bpm.insert(vmi, bp_PspInsertProcess)?;
ptm.monitor(vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
// Insert breakpoint for the `MmCleanProcessAddressSpace` function.
let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
.global()
.with_tag("MmCleanProcessAddressSpace");
bpm.insert(vmi, bp_MmCleanProcessAddressSpace)?;
ptm.monitor(
vmi,
cx_MmCleanProcessAddressSpace,
view,
"MmCleanProcessAddressSpace",
)?;
Ok(Self {
terminate_flag,
view,
bpm,
ptm,
})
}Sourcepub fn destroy_view(&self, view: View) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn destroy_view(&self, view: View) -> Result<(), VmiError>
utils and injector 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 utils and injector only.
pub fn switch_to_view(&self, view: View) -> Result<(), VmiError>
utils and injector only.Switches to a different view for all virtual CPUs.
This method changes the current active view for all vCPUs, affecting subsequent memory operations across the entire VM. It allows for quick transitions between different memory perspectives globally.
Note the difference between this method and
VmiEventResponse::set_view():
switch_to_view()changes the view for all vCPUs immediately.VmiEventResponse::set_view()sets the view only for the specific vCPU that received the event, and the change is applied when the event handler returns.
Use switch_to_view() for global view changes, and
VmiEventResponse::set_view() for targeted, event-specific view
modifications on individual vCPUs.
Examples found in repository?
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
pub fn new(
vmi: &VmiSession<Driver, WindowsOs<Driver>>,
profile: &Profile,
terminate_flag: Arc<AtomicBool>,
) -> Result<Self, VmiError> {
// Get the base address of the kernel.
// This base address is essential to correctly offset monitored
// functions.
//
// NOTE: `kernel_image_base` tries to find the kernel in the memory
// with the help of the CPU registers. On AMD64 architecture,
// the kernel image base is usually found using the `MSR_LSTAR`
// register, which contains the address of the system call
// handler. This register is set by the operating system during
// boot and is left unchanged (unless some rootkits are involved).
//
// Therefore, what we are doing here is querying the registers
// of the first VCPU and trying to find the kernel image base
// address using them.
let registers = vmi.registers(VcpuId(0))?;
let kernel_image_base = vmi.os().kernel_image_base(®isters)?;
tracing::info!(%kernel_image_base);
// Get the system process.
//
// The system process is the first process created by the kernel.
// In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
// To monitor page table entries, we need to locate the translation root
// of this process.
let system_process = vmi.os().system_process(®isters)?;
tracing::info!(%system_process);
// Get the translation root of the system process.
// This is effectively "the CR3 of the kernel".
//
// The translation root is the root of the page table hierarchy (also
// known as the Directory Table Base or PML4).
let root = vmi
.os()
.process_translation_root(®isters, system_process)?;
tracing::info!(%root);
// Load the symbols from the profile.
let symbols = Symbols::new(profile)?;
// Enable monitoring of the INT3 and singlestep events.
//
// INT3 is used to monitor the execution of specific functions.
// Singlestep is used to monitor the modifications of page table
// entries.
vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
vmi.monitor_enable(EventMonitor::Singlestep)?;
// Create a new view for the monitor.
// This view is used for monitoring function calls and memory accesses.
let view = vmi.create_view(MemoryAccess::RWX)?;
vmi.switch_to_view(view)?;
// Create a new breakpoint controller.
//
// The breakpoint controller is used to insert breakpoints for specific
// functions.
//
// From the guest's perspective, these breakpoints are "hidden", since
// the breakpoint controller will unset the read/write access to the
// physical memory page where the breakpoint is inserted, while keeping
// the execute access.
//
// This way, the guest will be able to execute the code, but attempts to
// read or write the memory will trigger the `memory_access` callback.
//
// When a VCPU tries to execute the breakpoint instruction:
// - an `interrupt` callback will be triggered
// - the breakpoint will be handled (e.g., log the function call)
// - a fast-singlestep[1] will be performed over the INT3 instruction
//
// When a VCPU tries to read from this page (e.g., a PatchGuard check):
// - `memory_access` callback will be triggered (with the `MemoryAccess::R`
// access type)
// - fast-singlestep[1] will be performed over the instruction that tried to
// read the memory
//
// This way, the instruction will read the original memory content.
//
// [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
// to a different view, execute a single instruction, and then
// switch back to the original view. In this case, the view is
// switched to the `default_view` (which is unmodified).
let mut bpm = BreakpointManager::new();
// Create a new page table monitor.
//
// The page table monitor is used to monitor the page table entries of
// the hooked functions.
//
// More specifically, it is used to monitor the pages that the breakpoint
// was inserted into. This is necessary to handle the case when the
// page containing the breakpoint is paged out (and then paged in
// again).
//
// `PageTableMonitor` works by unsetting the write access to the page
// tables of the hooked functions. When the page is paged out, the
// `PRESENT` bit in the page table entry is unset and, conversely, when
// the page is paged in, the `PRESENT` bit is set again.
//
// When that happens:
// - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
// access type)
// - the callback will mark the page as dirty in the page table monitor
// - a singlestep will be performed over the instruction that tried to modify
// the memory containing the page table entry
// - the `singlestep` handler will process the dirty page table entries and
// inform the breakpoint controller to handle the changes
let mut ptm = PageTableMonitor::new();
// Pause the VM to avoid race conditions between inserting breakpoints
// and monitoring page table entries. The VM resumes when the pause
// guard is dropped.
let _pause_guard = vmi.pause_guard()?;
// Insert breakpoint for the `NtCreateFile` function.
let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
let cx_NtCreateFile = (va_NtCreateFile, root);
let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
.global()
.with_tag("NtCreateFile");
bpm.insert(vmi, bp_NtCreateFile)?;
ptm.monitor(vmi, cx_NtCreateFile, view, "NtCreateFile")?;
tracing::info!(%va_NtCreateFile);
// Insert breakpoint for the `NtWriteFile` function.
let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
let cx_NtWriteFile = (va_NtWriteFile, root);
let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
.global()
.with_tag("NtWriteFile");
bpm.insert(vmi, bp_NtWriteFile)?;
ptm.monitor(vmi, cx_NtWriteFile, view, "NtWriteFile")?;
tracing::info!(%va_NtWriteFile);
// Insert breakpoint for the `PspInsertProcess` function.
let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
let cx_PspInsertProcess = (va_PspInsertProcess, root);
let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
.global()
.with_tag("PspInsertProcess");
bpm.insert(vmi, bp_PspInsertProcess)?;
ptm.monitor(vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
// Insert breakpoint for the `MmCleanProcessAddressSpace` function.
let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
.global()
.with_tag("MmCleanProcessAddressSpace");
bpm.insert(vmi, bp_MmCleanProcessAddressSpace)?;
ptm.monitor(
vmi,
cx_MmCleanProcessAddressSpace,
view,
"MmCleanProcessAddressSpace",
)?;
Ok(Self {
terminate_flag,
view,
bpm,
ptm,
})
}Sourcepub fn change_view_gfn(
&self,
view: View,
old_gfn: Gfn,
new_gfn: Gfn,
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn change_view_gfn( &self, view: View, old_gfn: Gfn, new_gfn: Gfn, ) -> Result<(), VmiError>
utils and injector 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 utils and injector only.
pub fn reset_view_gfn(&self, view: View, gfn: Gfn) -> Result<(), VmiError>
utils and injector 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 utils and injector only.
pub fn monitor_enable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>
utils and injector only.Enables monitoring of specific events.
Examples found in repository?
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
pub fn new(
vmi: &VmiSession<Driver, WindowsOs<Driver>>,
profile: &Profile,
terminate_flag: Arc<AtomicBool>,
) -> Result<Self, VmiError> {
// Get the base address of the kernel.
// This base address is essential to correctly offset monitored
// functions.
//
// NOTE: `kernel_image_base` tries to find the kernel in the memory
// with the help of the CPU registers. On AMD64 architecture,
// the kernel image base is usually found using the `MSR_LSTAR`
// register, which contains the address of the system call
// handler. This register is set by the operating system during
// boot and is left unchanged (unless some rootkits are involved).
//
// Therefore, what we are doing here is querying the registers
// of the first VCPU and trying to find the kernel image base
// address using them.
let registers = vmi.registers(VcpuId(0))?;
let kernel_image_base = vmi.os().kernel_image_base(®isters)?;
tracing::info!(%kernel_image_base);
// Get the system process.
//
// The system process is the first process created by the kernel.
// In Windows, it is referenced by the kernel symbol `PsInitialSystemProcess`.
// To monitor page table entries, we need to locate the translation root
// of this process.
let system_process = vmi.os().system_process(®isters)?;
tracing::info!(%system_process);
// Get the translation root of the system process.
// This is effectively "the CR3 of the kernel".
//
// The translation root is the root of the page table hierarchy (also
// known as the Directory Table Base or PML4).
let root = vmi
.os()
.process_translation_root(®isters, system_process)?;
tracing::info!(%root);
// Load the symbols from the profile.
let symbols = Symbols::new(profile)?;
// Enable monitoring of the INT3 and singlestep events.
//
// INT3 is used to monitor the execution of specific functions.
// Singlestep is used to monitor the modifications of page table
// entries.
vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
vmi.monitor_enable(EventMonitor::Singlestep)?;
// Create a new view for the monitor.
// This view is used for monitoring function calls and memory accesses.
let view = vmi.create_view(MemoryAccess::RWX)?;
vmi.switch_to_view(view)?;
// Create a new breakpoint controller.
//
// The breakpoint controller is used to insert breakpoints for specific
// functions.
//
// From the guest's perspective, these breakpoints are "hidden", since
// the breakpoint controller will unset the read/write access to the
// physical memory page where the breakpoint is inserted, while keeping
// the execute access.
//
// This way, the guest will be able to execute the code, but attempts to
// read or write the memory will trigger the `memory_access` callback.
//
// When a VCPU tries to execute the breakpoint instruction:
// - an `interrupt` callback will be triggered
// - the breakpoint will be handled (e.g., log the function call)
// - a fast-singlestep[1] will be performed over the INT3 instruction
//
// When a VCPU tries to read from this page (e.g., a PatchGuard check):
// - `memory_access` callback will be triggered (with the `MemoryAccess::R`
// access type)
// - fast-singlestep[1] will be performed over the instruction that tried to
// read the memory
//
// This way, the instruction will read the original memory content.
//
// [1] Fast-singlestep is a VMI feature that allows to switch the VCPU
// to a different view, execute a single instruction, and then
// switch back to the original view. In this case, the view is
// switched to the `default_view` (which is unmodified).
let mut bpm = BreakpointManager::new();
// Create a new page table monitor.
//
// The page table monitor is used to monitor the page table entries of
// the hooked functions.
//
// More specifically, it is used to monitor the pages that the breakpoint
// was inserted into. This is necessary to handle the case when the
// page containing the breakpoint is paged out (and then paged in
// again).
//
// `PageTableMonitor` works by unsetting the write access to the page
// tables of the hooked functions. When the page is paged out, the
// `PRESENT` bit in the page table entry is unset and, conversely, when
// the page is paged in, the `PRESENT` bit is set again.
//
// When that happens:
// - the `memory_access` callback will be triggered (with the `MemoryAccess::R`
// access type)
// - the callback will mark the page as dirty in the page table monitor
// - a singlestep will be performed over the instruction that tried to modify
// the memory containing the page table entry
// - the `singlestep` handler will process the dirty page table entries and
// inform the breakpoint controller to handle the changes
let mut ptm = PageTableMonitor::new();
// Pause the VM to avoid race conditions between inserting breakpoints
// and monitoring page table entries. The VM resumes when the pause
// guard is dropped.
let _pause_guard = vmi.pause_guard()?;
// Insert breakpoint for the `NtCreateFile` function.
let va_NtCreateFile = kernel_image_base + symbols.NtCreateFile;
let cx_NtCreateFile = (va_NtCreateFile, root);
let bp_NtCreateFile = Breakpoint::new(cx_NtCreateFile, view)
.global()
.with_tag("NtCreateFile");
bpm.insert(vmi, bp_NtCreateFile)?;
ptm.monitor(vmi, cx_NtCreateFile, view, "NtCreateFile")?;
tracing::info!(%va_NtCreateFile);
// Insert breakpoint for the `NtWriteFile` function.
let va_NtWriteFile = kernel_image_base + symbols.NtWriteFile;
let cx_NtWriteFile = (va_NtWriteFile, root);
let bp_NtWriteFile = Breakpoint::new(cx_NtWriteFile, view)
.global()
.with_tag("NtWriteFile");
bpm.insert(vmi, bp_NtWriteFile)?;
ptm.monitor(vmi, cx_NtWriteFile, view, "NtWriteFile")?;
tracing::info!(%va_NtWriteFile);
// Insert breakpoint for the `PspInsertProcess` function.
let va_PspInsertProcess = kernel_image_base + symbols.PspInsertProcess;
let cx_PspInsertProcess = (va_PspInsertProcess, root);
let bp_PspInsertProcess = Breakpoint::new(cx_PspInsertProcess, view)
.global()
.with_tag("PspInsertProcess");
bpm.insert(vmi, bp_PspInsertProcess)?;
ptm.monitor(vmi, cx_PspInsertProcess, view, "PspInsertProcess")?;
// Insert breakpoint for the `MmCleanProcessAddressSpace` function.
let va_MmCleanProcessAddressSpace = kernel_image_base + symbols.MmCleanProcessAddressSpace;
let cx_MmCleanProcessAddressSpace = (va_MmCleanProcessAddressSpace, root);
let bp_MmCleanProcessAddressSpace = Breakpoint::new(cx_MmCleanProcessAddressSpace, view)
.global()
.with_tag("MmCleanProcessAddressSpace");
bpm.insert(vmi, bp_MmCleanProcessAddressSpace)?;
ptm.monitor(
vmi,
cx_MmCleanProcessAddressSpace,
view,
"MmCleanProcessAddressSpace",
)?;
Ok(Self {
terminate_flag,
view,
bpm,
ptm,
})
}Sourcepub fn monitor_disable(
&self,
option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor,
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn monitor_disable( &self, option: <<Driver as VmiDriver>::Architecture as Architecture>::EventMonitor, ) -> Result<(), VmiError>
utils and injector only.Disables monitoring of specific events.
Sourcepub fn inject_interrupt(
&self,
vcpu: VcpuId,
interrupt: <<Driver as VmiDriver>::Architecture as Architecture>::Interrupt,
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn inject_interrupt( &self, vcpu: VcpuId, interrupt: <<Driver as VmiDriver>::Architecture as Architecture>::Interrupt, ) -> Result<(), VmiError>
utils and injector 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?
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
fn dispatch(
&mut self,
vmi: &VmiContext<'_, Driver, WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Amd64>, VmiError> {
let event = vmi.event();
let result = match event.reason() {
EventReason::MemoryAccess(_) => self.memory_access(vmi),
EventReason::Interrupt(_) => self.interrupt(vmi),
EventReason::Singlestep(_) => self.singlestep(vmi),
_ => panic!("Unhandled event: {:?}", event.reason()),
};
// If VMI tries to read from a page that is not present, it will return
// a page fault error. In this case, we inject a page fault interrupt
// to the guest.
//
// Once the guest handles the page fault, it will try to retry the
// instruction that caused the page fault.
if let Err(VmiError::PageFault(pfs)) = result {
tracing::warn!(?pfs, "Page fault, injecting");
vmi.inject_interrupt(event.vcpu_id(), Interrupt::page_fault(pfs[0].address, 0))?;
return Ok(VmiEventResponse::default());
}
result
}Sourcepub fn events_pending(&self) -> usize
Available on crate features utils and injector only.
pub fn events_pending(&self) -> usize
utils and injector 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 utils and injector only.
pub fn event_processing_overhead(&self) -> Duration
utils and injector 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<'a>(
&'a self,
timeout: Duration,
handler: Box<dyn FnMut(&VmiEvent<<Driver as VmiDriver>::Architecture>) -> VmiEventResponse<<Driver as VmiDriver>::Architecture> + 'a>,
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn wait_for_event<'a>( &'a self, timeout: Duration, handler: Box<dyn FnMut(&VmiEvent<<Driver as VmiDriver>::Architecture>) -> VmiEventResponse<<Driver as VmiDriver>::Architecture> + 'a>, ) -> Result<(), VmiError>
utils and injector only.Waits for an event to occur and processes it with the provided handler.
This method blocks until an event occurs or the specified timeout is reached. When an event occurs, it is passed to the provided callback function for processing.
Sourcepub fn reset_state(&self) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn reset_state(&self) -> Result<(), VmiError>
utils and injector only.Resets the state of the VMI system.
This method clears all event monitors, caches, and any other stateful data maintained by the VMI system. It’s useful for bringing the VMI system back to a known clean state, which can be necessary when switching between different analysis tasks or recovering from error conditions.
Sourcepub fn read(
&self,
ctx: impl Into<AccessContext>,
buffer: &mut [u8],
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn read( &self, ctx: impl Into<AccessContext>, buffer: &mut [u8], ) -> Result<(), VmiError>
utils and injector only.Reads memory from the virtual machine.
Sourcepub fn write(
&self,
ctx: impl Into<AccessContext>,
buffer: &[u8],
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn write( &self, ctx: impl Into<AccessContext>, buffer: &[u8], ) -> Result<(), VmiError>
utils and injector only.Writes memory to the virtual machine.
Sourcepub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
Available on crate features utils and injector only.
pub fn read_u8(&self, ctx: impl Into<AccessContext>) -> Result<u8, VmiError>
utils and injector 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 utils and injector only.
pub fn read_u16(&self, ctx: impl Into<AccessContext>) -> Result<u16, VmiError>
utils and injector 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 utils and injector only.
pub fn read_u32(&self, ctx: impl Into<AccessContext>) -> Result<u32, VmiError>
utils and injector 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 utils and injector only.
pub fn read_u64(&self, ctx: impl Into<AccessContext>) -> Result<u64, VmiError>
utils and injector only.Reads a 64-bit unsigned integer from the virtual machine.
Sourcepub fn read_address(
&self,
ctx: impl Into<AccessContext>,
address_width: usize,
) -> Result<u64, VmiError>
Available on crate features utils and injector only.
pub fn read_address( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<u64, VmiError>
utils and injector 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 utils and injector only.
pub fn read_address32( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils and injector 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 utils and injector only.
pub fn read_address64( &self, ctx: impl Into<AccessContext>, ) -> Result<u64, VmiError>
utils and injector 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 utils and injector only.
pub fn read_va( &self, ctx: impl Into<AccessContext>, address_width: usize, ) -> Result<Va, VmiError>
utils and injector 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 utils and injector only.
pub fn read_va32(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
utils and injector 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 utils and injector only.
pub fn read_va64(&self, ctx: impl Into<AccessContext>) -> Result<Va, VmiError>
utils and injector 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 utils and injector only.
pub fn read_string_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u8>, VmiError>
utils and injector 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 utils and injector only.
pub fn read_string_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u8>, VmiError>
utils and injector only.Reads a null-terminated string of bytes from the virtual machine.
Sourcepub fn read_wstring_bytes_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<Vec<u16>, VmiError>
Available on crate features utils and injector only.
pub fn read_wstring_bytes_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<Vec<u16>, VmiError>
utils and injector only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring_bytes(
&self,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError>
Available on crate features utils and injector only.
pub fn read_wstring_bytes( &self, ctx: impl Into<AccessContext>, ) -> Result<Vec<u16>, VmiError>
utils and injector 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 utils and injector only.
pub fn read_string_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
utils and injector 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 utils and injector only.
pub fn read_string( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
utils and injector only.Reads a null-terminated string from the virtual machine.
Sourcepub fn read_wstring_limited(
&self,
ctx: impl Into<AccessContext>,
limit: usize,
) -> Result<String, VmiError>
Available on crate features utils and injector only.
pub fn read_wstring_limited( &self, ctx: impl Into<AccessContext>, limit: usize, ) -> Result<String, VmiError>
utils and injector only.Reads a null-terminated wide string (UTF-16) from the virtual machine with a specified limit.
Sourcepub fn read_wstring(
&self,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError>
Available on crate features utils and injector only.
pub fn read_wstring( &self, ctx: impl Into<AccessContext>, ) -> Result<String, VmiError>
utils and injector 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 utils and injector only.
pub fn read_struct<T>( &self, ctx: impl Into<AccessContext>, ) -> Result<T, VmiError>
utils and injector only.Reads a struct from the virtual machine.
Sourcepub fn write_u8(
&self,
ctx: impl Into<AccessContext>,
value: u8,
) -> Result<(), VmiError>
Available on crate features utils and injector only.
pub fn write_u8( &self, ctx: impl Into<AccessContext>, value: u8, ) -> Result<(), VmiError>
utils and injector 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 utils and injector only.
pub fn write_u16( &self, ctx: impl Into<AccessContext>, value: u16, ) -> Result<(), VmiError>
utils and injector 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 utils and injector only.
pub fn write_u32( &self, ctx: impl Into<AccessContext>, value: u32, ) -> Result<(), VmiError>
utils and injector 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 utils and injector only.
pub fn write_u64( &self, ctx: impl Into<AccessContext>, value: u64, ) -> Result<(), VmiError>
utils and injector 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 utils and injector only.
pub fn write_struct<T>( &self, ctx: impl Into<AccessContext>, value: T, ) -> Result<(), VmiError>
utils and injector only.Writes a struct to the virtual machine.
Sourcepub fn translate_address(
&self,
ctx: impl Into<AddressContext>,
) -> Result<Pa, VmiError>
Available on crate features utils and injector only.
pub fn translate_address( &self, ctx: impl Into<AddressContext>, ) -> Result<Pa, VmiError>
utils and injector only.Translates a virtual address to a physical address.
Sourcepub fn translate_access_context(
&self,
ctx: AccessContext,
) -> Result<Pa, VmiError>
Available on crate features utils and injector only.
pub fn translate_access_context( &self, ctx: AccessContext, ) -> Result<Pa, VmiError>
utils and injector only.Translates an access context to a physical address.