Crates.io | vmi |
lib.rs | vmi |
version | |
source | src |
created_at | 2024-10-29 20:25:22.721337 |
updated_at | 2024-11-09 06:05:55.247615 |
description | A modular and extensible library for Virtual Machine Introspection |
homepage | https://github.com/vmi-rs/vmi |
repository | https://github.com/vmi-rs/vmi |
max_upload_size | |
id | 1427604 |
Cargo.toml error: | TOML parse error at line 20, column 1 | 20 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
A comprehensive framework for Virtual Machine Introspection (VMI) implemented in Rust, providing safe abstractions for analyzing and manipulating virtual machine state from the outside.
VMI is a powerful technique for analyzing and manipulating virtual machines from the outside. It is used in a variety of security applications, including malware analysis, intrusion detection, and digital forensics.
However, VMI is complex and error-prone, requiring low-level interactions with the virtual machine. This framework aims to simplify VMI by providing a high-level, type-safe API for common operations, such as memory access, CPU register manipulation, and OS-specific introspection.
The framework is designed to be modular and extensible, supporting multiple CPU architectures, hypervisors, and operating systems. It includes built-in support for AMD64 architecture, Xen hypervisor, and Windows and Linux operating systems.
VMI involves interacting with a virtual machine at a very low level, often requiring direct manipulation of memory and registers. A common challenge is the semantic gap between these low-level operations (e.g., reading memory) and the higher-level understanding of the guest OS needed for meaningful analysis (e.g., enumerating processes and analyzing their modules).
This framework addresses this gap through a layered architecture,
from raw hypervisor interactions, through OS-specific abstractions
like WindowsOs
and LinuxOs
, to integration with the ISR
library, providing version-agnostic access to OS internals.
Let's be honest, VMI doesn't get the love it deserves. While incredibly useful, it's not as widely supported as it should be. Xen is currently the champion of VMI support among major hypervisors. Other hypervisors, like VMware, Hyper-V, and VirtualBox, haven't quite jumped on the VMI bandwagon yet.
There have been attempts to bring VMI to other platforms, such as the KVM-VMI project. Unfortunately, these efforts haven't been merged into the mainline and the project hasn't been updated in a while.
This project aims to shine a spotlight on VMI and encourage wider adoption. While currently focused on Xen, the framework is designed to be hypervisor-agnostic. We're optimistically waiting for the day when other hypervisors join the VMI party!
This project is still in its early stages and under active development. Expect breaking changes and rough edges. Feedback, bug reports, and contributions are welcome!
Configurable caching mechanisms for physical page lookups and Virtual-to-Physical address translations to improve performance.
ISR library for version-agnostic OS introspection.
Sophisticated error handling, including robust page-fault handling.
Modular architecture allowing for seamless integration of new hypervisor drivers, CPU architectures, and OS support.
Batteries included:
WindowsOs
and LinuxOs
.BreakpointManager
, PageTableMonitor
,
and InjectorHandler
.Add the following to your Cargo.toml
:
[dependencies]
vmi = "0.1"
Basic usage example:
use isr::{cache::JsonCodec, IsrCache};
use vmi::{
arch::amd64::Amd64,
driver::xen::VmiXenDriver,
os::windows::WindowsOs,
VcpuId, VmiCore, VmiSession,
};
use xen::XenDomainId;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Setup VMI.
let driver = VmiXenDriver::<Amd64>::new(XenDomainId(1))?;
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.
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(())
}
But first, you need to install the prerequisites.
The framework has been tested on Ubuntu 22.04 and Xen 4.19. Note that Xen 4.19 is the minimum version required to use the framework, and it is the current version (at the time of writing).
Unfortunately, Xen 4.19 is not available in the official Ubuntu repositories, so it must be built from source.
This guide assumes you have a fresh Ubuntu 22.04 installation.
Sorry, the guide is still under construction. Please check back later.
The framework includes several examples demonstrating various VMI capabilities, from basic operations to more complex scenarios.
Demonstrates fundamental VMI operations like retrieving the Interrupt Descriptor Table (IDT) for each virtual CPU.
Shows how to retrieve and display a list of running processes in the guest VM.
Illustrates the usage of the BreakpointManager
and
PageTableMonitor
to set and manage breakpoints on Windows systems.
A simple example of code injection using a recipe to display a message box in the guest.
Demonstrates injecting code that writes data to a file in the guest.
windows-recipe-writefile-advanced.rs
A more complex example showing how to write to a file in chunks and handle potential errors during injection.
The framework uses distinct types to represent different kinds of memory addresses within the guest:
These types provide type safety and support arithmetic operations, comparisons and formatting.
Example:
use vmi::{
arch::amd64::{Amd64, Cr3},
Architecture as _, Gfn, Pa, Va,
};
let gfn = Gfn(0x1aa);
let pa = Amd64::pa_from_gfn(gfn);
assert_eq!(pa, Pa(0x1aa000));
let pa = Pa(0x1aa000);
let gfn = Amd64::gfn_from_pa(pa);
assert_eq!(gfn, Gfn(0x1aa));
let cr3 = Cr3(0x1aa000);
let va = Va(0xfffff804590c8980);
let pa = Amd64::translate_address(vmi, va, cr3.into())?;
Additionally, two key structures manage address translation:
AddressContext
: Combines a virtual address (Va
) and
a translation root (Pa
, typically the CR3
register) to provide
a complete context for virtual-to-physical address translation.
This structure is used as input for address translation and memory access functions.
Example:
let cr3 = Cr3(0x1aa000);
let va = Va(0xfffff804590c8980);
let address_context = AddressContext::new(va, cr3);
AccessContext
: Defines the context for memory operations,
encapsulating the target address and the TranslationMechanism
.
This allows for both direct physical access and paging-based translation.
Example:
// Direct physical memory access:
let access_context = AccessContext::direct(Pa(0x1fc7980));
assert!(matches!(
access_context.mechanism,
TranslationMechanism::Direct
));
// Paging-based translation:
let cr3 = Cr3(0x1aa000);
let va = Va(0xfffff804590c8980);
let access_context = AccessContext::paging(va, cr3);
assert!(matches!(
access_context.mechanism,
TranslationMechanism::Paging {
root: Some(Pa(0x1aa000))
}
));
The framework is designed to be modular and extensible, supporting multiple CPU architectures, hypervisors, and operating systems.
The core components of the framework are:
Architecture
: A trait abstracting CPU architecture-specific logic,
such as register definitions and address translation.
Currently, the framework includes an Amd64
implementation.
VmiDriver
: A trait defining the interface for interacting with the
hypervisor. This allows the framework to support multiple hypervisors.
Currently, the framework includes a VmiXenDriver
for Xen.
VmiCore
: Provides raw VMI operations, interacting directly with
the VmiDriver
and leveraging the Architecture
. It handles
memory access, address translation, and register manipulation,
but has no inherent OS awareness.
Importantly, VmiCore
does not store register state, requiring it
to be explicitly provided for operations that depend on it.
VmiOs
: A trait defining OS-specific introspection operations.
Implementations of this trait, such as WindowsOs
and LinuxOs
,
provide higher-level functions for interacting with the guest OS,
bridging the semantic gap between raw memory access and meaningful
OS analysis.
VmiSession
: Combines a VmiCore
with a VmiOs
implementation
to provide OS-aware operations. This enables high-level introspection
tasks, but - like VmiCore
- VmiSession
does not store register state.
VmiContext
: Represents a point-in-time state of the virtual CPU
during event handling. Unlike VmiCore
(and VmiSession
), VmiContext
does hold the register state at the time of the event, simplifying
event handler logic.
It provides access to both VmiCore
and VmiOs
functionality within
a specific VmiEvent
.
VmiError
: Represents errors that can occur during VMI operations,
including translation faults (PageFault
).
VmiCore
, VmiSession
, and VmiContext
Each of these structures can be implicitly dereferenced down the hierarchy.
This means that VmiContext
implements Deref
to VmiSession
,
which in turn implements Deref
to VmiCore
.
This design enables convenient access to lower-level functionality:
Access VmiCore
methods directly from a VmiSession
or VmiContext
without explicit dereferencing.
Pass a &VmiContext
to functions expecting a &VmiSession
or &VmiCore
.
Both VmiSession
and VmiContext
provide access to OS-specific
functionality through the os()
method. This method returns a structure
implementing the VmiOs
trait methods, as well as any additional
OS-specific operations.
As pointed out above, VmiCore
and VmiSession
do not store register
state. This means that functions requiring register information (e.g.,
for address translation or OS-specific operations) must be explicitly
provided with the register state.
VmiContext
, on the other hand, does hold the register state at
the time of the event. This difference has important implications for
how you interact with these components:
With VmiCore
and VmiSession
, you must explicitly provide
the translation root (e.g., CR3
) when performing memory operations:
let va = Va(0xfffff804590c8980);
// let vmi: &VmiSession = ...;
let registers = vmi.registers(VcpuId(0))?;
let value = vmi.read_u64((va, registers.cr3.into()))?; // Explicitly pass the translation root (CR3)
With VmiContext
, register state is managed internally:
// let vmi: &VmiContext = ...;
let value = vmi.read_u64(va)?; // No need to pass the translation root
This extends to OS-specific operations as well.
VmiSession
requires explicit register state:// let vmi: &VmiSession = ...;
let registers = vmi.registers(VcpuId(0))?;
let process = vmi.os().current_process(®isters)?;
let process_id = vmi.os().process_id(®isters, process)?;
VmiContext
simplifies this by providing register state implicitly:// let vmi: &VmiContext = ...;
let process = vmi.os().current_process()?;
let process_id = vmi.os().process_id(process)?;
The event system allows responding to guest activities:
VmiEvent
: Represents various guest events (memory access, interrupts,
register changes). Carries event-specific data and register state at the
time of the event.
VmiHandler
: A trait for implementing event handlers.
The handle_event
method defines how your application
responds to specific guest events.
VmiEventResponse
: Controls guest execution after an event.
Options include continuing, single-stepping and modifying registers.
Several utility components are provided to simplify common VMI tasks:
PageTableMonitor
: Tracks page table modifications, generating
PageIn
/PageOut
events.
BreakpointManager
: Manages software breakpoints, handling
PageIn
/PageOut
events.
InjectorHandler
: Provides a high-level interface for code injection,
handling thread hijacking and argument marshalling.
Interceptor
: Low-level breakpoint management.
Use BreakpointManager
instead whenever possible.
Consult the
isr
crate documentation for more information and examples.
The framework leverages Intermediate Symbol Representation (ISR) for version-agnostic OS introspection. It avoids the need for hardcoding offsets and makes the code adaptable to different OS versions.
IsrCache
: Manages symbol files (PDB for Windows, DWARF for Linux).
Automatically downloads and caches PDBs based on CodeView information
(Windows) or kernel version banner (Linux).
symbols!
macro: Defines symbols for lookup.
Example:
use isr::macros::symbols;
symbols! {
pub struct Symbols {
NtCreateFile: u64,
PsActiveProcessHead: u64,
}
}
offsets!
macro: Defines structure offsets.
Example:
use isr::macros::{offsets, Field};
offsets! {
pub struct Offsets {
struct _EPROCESS {
UniqueProcessId: Field,
ActiveProcessLinks: Field,
}
}
}
Architecture Support: Currently only AMD64 is supported. No x86 (32-bit) support, including 32-bit paging or code injection into 32-bit processes. 5-level paging is also not supported.
Hypervisor Support: Only Xen is supported through VmiXenDriver
.
Operating System Support:
If you're new to VMI or looking for more information, check out these amazing projects and resources:
This project is licensed under the MIT license.