vmi

Struct VmiCore

Source
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,

Source

pub fn new(driver: Driver) -> Result<VmiCore<Driver>, VmiError>

Available on crate features 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?
examples/basic.rs (line 19)
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, &registers)?;

        println!("IDT[{vcpu_id}]: {idt:#?}");
    }

    Ok(())
}
More examples
Hide additional examples
examples/basic-process-list.rs (line 21)
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, &registers)?.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(&registers)?;
    println!("Processes: {processes:#?}");

    Ok(())
}
examples/_common.rs (line 39)
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, &registers)?.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))
}
examples/windows-breakpoint-manager.rs (line 568)
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, &regs)?.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(())
}
Source

pub fn with_gfn_cache(self, size: usize) -> VmiCore<Driver>

Available on crate features 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.

Source

pub fn enable_gfn_cache(&mut self)

Available on crate features utils and injector only.

Enables the GFN cache.

See with_gfn_cache for more details.

Source

pub fn disable_gfn_cache(&mut self)

Available on crate features utils and injector only.

Disables the GFN cache.

Subsequent calls to read_page will bypass the cache and read directly from the virtual machine.

Source

pub fn resize_gfn_cache(&mut self, size: usize)

Available on crate features 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.

Source

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

Available on crate features 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.

Source

pub fn flush_gfn_cache(&self)

Available on crate features utils and injector only.

Clears the entire GFN cache.

Source

pub fn with_v2p_cache(self, size: usize) -> VmiCore<Driver>

Available on crate features 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.

Source

pub fn enable_v2p_cache(&mut self)

Available on crate features utils and injector only.

Enables the V2P cache.

See with_v2p_cache for more details.

Source

pub fn disable_v2p_cache(&mut self)

Available on crate features 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.

Source

pub fn resize_v2p_cache(&mut self, size: usize)

Available on crate features 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.

Source

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

Available on crate features 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.

Source

pub fn flush_v2p_cache(&self)

Available on crate features 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.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 539)
534
535
536
537
538
539
540
541
542
    fn handle_event(
        &mut self,
        vmi: VmiContext<'_, Driver, WindowsOs<Driver>>,
    ) -> VmiEventResponse<Amd64> {
        // Flush the V2P cache on every event to avoid stale translations.
        vmi.flush_v2p_cache();

        self.dispatch(&vmi).expect("dispatch")
    }
Source

pub fn with_read_string_length_limit( self, limit_in_bytes: usize, ) -> VmiCore<Driver>

Available on crate features 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.

Source

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

Available on crate features utils and injector only.

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

Source

pub fn set_read_string_length_limit(&self, limit: usize)

Available on crate features 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.

Source

pub fn elapsed(&self) -> Duration

Available on crate features utils and injector only.

Returns the duration since this VmiCore instance was created.

Source

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

Available on crate features utils and injector only.

Retrieves information about the virtual machine.

Examples found in repository?
examples/basic.rs (line 23)
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, &registers)?;

        println!("IDT[{vcpu_id}]: {idt:#?}");
    }

    Ok(())
}
Source

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

Available on crate features utils and injector only.

Pauses the virtual machine.

Source

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

Available on crate features utils and injector only.

Resumes the virtual machine.

Source

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

Available on crate features utils and injector only.

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

Examples found in repository?
examples/basic.rs (line 22)
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, &registers)?;

        println!("IDT[{vcpu_id}]: {idt:#?}");
    }

    Ok(())
}
More examples
Hide additional examples
examples/windows-recipe-messagebox.rs (line 68)
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(&registers)?
    };

    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(())
}
examples/windows-recipe-writefile.rs (line 206)
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(&registers)?
    };

    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(())
}
examples/windows-recipe-writefile-advanced.rs (line 301)
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(&registers)?
    };

    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(())
}
examples/basic-process-list.rs (line 26)
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, &registers)?.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(&registers)?;
    println!("Processes: {processes:#?}");

    Ok(())
}
examples/_common.rs (line 44)
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, &registers)?.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))
}
Source

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

Available on crate features 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?
examples/basic.rs (line 25)
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, &registers)?;

        println!("IDT[{vcpu_id}]: {idt:#?}");
    }

    Ok(())
}
More examples
Hide additional examples
examples/windows-recipe-messagebox.rs (line 70)
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(&registers)?
    };

    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(())
}
examples/windows-recipe-writefile.rs (line 208)
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(&registers)?
    };

    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(())
}
examples/windows-recipe-writefile-advanced.rs (line 303)
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(&registers)?
    };

    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(())
}
examples/basic-process-list.rs (line 27)
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, &registers)?.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(&registers)?;
    println!("Processes: {processes:#?}");

    Ok(())
}
examples/_common.rs (line 45)
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, &registers)?.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))
}
Source

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

Available on crate features utils and injector only.

Sets the registers of a virtual CPU.

Source

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

Available on crate features 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.

Source

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

Available on crate features 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.

Source

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

Available on crate features 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.

Source

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

Available on crate features 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.

Source

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

Available on crate features 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.

Source

pub fn default_view(&self) -> View

Available on crate features 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.

Source

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

Available on crate features 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?
examples/windows-breakpoint-manager.rs (line 118)
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(&registers)?;
        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(&registers)?;
        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(&registers, 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,
        })
    }
Source

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

Available on crate features 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.

Source

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

Available on crate features 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?
examples/windows-breakpoint-manager.rs (line 119)
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(&registers)?;
        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(&registers)?;
        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(&registers, 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,
        })
    }
Source

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

Available on crate features 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”:

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

When a read access occurs:

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

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

Source

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

Available on crate features 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.

Source

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

Available on crate features utils and injector only.

Enables monitoring of specific events.

Examples found in repository?
examples/windows-breakpoint-manager.rs (line 113)
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(&registers)?;
        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(&registers)?;
        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(&registers, 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,
        })
    }
Source

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

Available on crate features utils and injector only.

Disables monitoring of specific events.

Source

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

Available on crate features 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?
examples/windows-breakpoint-manager.rs (line 522)
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
    }
Source

pub fn events_pending(&self) -> usize

Available on crate features 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.

Source

pub fn event_processing_overhead(&self) -> Duration

Available on crate features 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.

Source

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>

Available on crate features 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.

Source

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

Available on crate features 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.

Source

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

Available on crate features utils and injector only.

Reads memory from the virtual machine.

Source

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

Available on crate features utils and injector only.

Writes memory to the virtual machine.

Source

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

Available on crate features utils and injector only.

Reads a single byte from the virtual machine.

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features 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.

Source

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

Available on crate features utils and injector only.

Reads a 32-bit address from the virtual machine.

Source

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

Available on crate features utils and injector only.

Reads a 64-bit address from the virtual machine.

Source

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

Available on crate features utils and injector only.

Reads a virtual address from the virtual machine.

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

Reads a null-terminated string from the virtual machine.

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

Reads a struct from the virtual machine.

Source

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

Available on crate features utils and injector only.

Writes a single byte to the virtual machine.

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

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

Source

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

Available on crate features utils and injector only.

Writes a struct to the virtual machine.

Source

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

Available on crate features utils and injector only.

Translates a virtual address to a physical address.

Source

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

Available on crate features utils and injector only.

Translates an access context to a physical address.

Source

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

Available on crate features utils and injector only.

Reads a page of memory from the virtual machine.

Auto Trait Implementations§

§

impl<Driver> !Freeze for VmiCore<Driver>

§

impl<Driver> !RefUnwindSafe for VmiCore<Driver>

§

impl<Driver> !Send for VmiCore<Driver>

§

impl<Driver> !Sync for VmiCore<Driver>

§

impl<Driver> Unpin for VmiCore<Driver>
where Driver: Unpin,

§

impl<Driver> !UnwindSafe for VmiCore<Driver>

Blanket Implementations§

Source§

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

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

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

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

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

Source§

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

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

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

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

fn in_current_span(self) -> Instrumented<Self>

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

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

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

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

fn with_current_subscriber(self) -> WithDispatch<Self>

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

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> MaybeSendSync for T