use vmi_arch_amd64::{Amd64, EventMonitor, EventReason, ExceptionVector, Interrupt};
use vmi_core::{
Hex, MemoryAccess, Registers as _, Va, VcpuId, View, VmiContext, VmiError, VmiEventResponse,
VmiHandler, VmiSession,
driver::{
VmiDriver, VmiEventControl, VmiQueryRegisters, VmiRead, VmiSetProtection, VmiViewControl,
VmiVmControl, VmiWrite,
},
os::{ProcessId, ThreadId, VmiOsProcess, VmiOsThread},
};
use vmi_os_windows::{WindowsOs, WindowsOsExt as _};
use super::super::super::{
InjectorHandlerAdapter, InjectorResultCode, KernelMode, Recipe, RecipeExecutor,
};
use crate::{
bpm::{Breakpoint, BreakpointController, BreakpointManager},
bridge::{BridgeDispatch, BridgePacket},
ptm::PageTableMonitor,
};
enum InjectorState {
PreHijack,
Executing,
Teardown(VcpuId),
Bridge,
Complete(Result<InjectorResultCode, BridgePacket>),
}
pub struct KernelInjectorHandler<Driver, T, Bridge>
where
Driver: VmiDriver<Architecture = Amd64>
+ VmiRead
+ VmiWrite
+ VmiSetProtection
+ VmiViewControl
+ VmiVmControl,
Bridge: BridgeDispatch<WindowsOs<Driver>, InjectorResultCode>,
{
pid: Option<ProcessId>,
tid: Option<ThreadId>,
view: View,
bpm: BreakpointManager<BreakpointController<Driver>>,
ptm: PageTableMonitor<Driver>,
recipe: RecipeExecutor<WindowsOs<Driver>, T>,
bridge: Bridge,
state: InjectorState,
}
impl<Driver, T, Bridge> InjectorHandlerAdapter<WindowsOs<Driver>, KernelMode, T, Bridge>
for KernelInjectorHandler<Driver, T, Bridge>
where
Driver: VmiDriver<Architecture = Amd64>
+ VmiRead
+ VmiWrite
+ VmiSetProtection
+ VmiQueryRegisters
+ VmiEventControl
+ VmiViewControl
+ VmiVmControl,
Bridge: BridgeDispatch<WindowsOs<Driver>, InjectorResultCode>,
{
#[expect(non_snake_case)]
fn with_bridge(
vmi: &VmiSession<WindowsOs<Driver>>,
bridge: Bridge,
recipe: Recipe<WindowsOs<Driver>, T>,
) -> Result<Self, VmiError> {
let view = vmi.create_view(MemoryAccess::RWX)?;
vmi.switch_to_view(view)?;
vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
vmi.monitor_enable(EventMonitor::Singlestep)?;
let mut bpm = BreakpointManager::new();
let mut ptm = PageTableMonitor::new();
let _pause_guard = vmi.pause_guard()?;
let registers = vmi.registers(VcpuId(0))?;
let vmi = vmi.with_registers(®isters);
let kernel_image_base = vmi.os().kernel_image_base()?;
tracing::info!(%kernel_image_base);
let system_process = vmi.os().system_process()?;
tracing::info!(system_process = %system_process.object()?);
let root = system_process.translation_root()?;
tracing::info!(%root);
let va_SeAccessCheck = kernel_image_base + vmi.os().symbols().SeAccessCheck.unwrap();
let cx_SeAccessCheck = (va_SeAccessCheck, root);
let bp_SeAccessCheck = Breakpoint::new(cx_SeAccessCheck, view)
.global()
.with_tag("SeAccessCheck");
bpm.insert(&vmi, bp_SeAccessCheck)?;
ptm.monitor(&vmi, cx_SeAccessCheck, view, "SeAccessCheck")?;
tracing::info!(%va_SeAccessCheck);
Ok(Self {
pid: None,
tid: None,
view,
bpm,
ptm,
recipe: RecipeExecutor::new(recipe),
bridge,
state: InjectorState::PreHijack,
})
}
fn with_pid(self, pid: ProcessId) -> Result<Self, VmiError> {
Ok(Self {
pid: Some(pid),
..self
})
}
}
impl<Driver, T, Bridge> KernelInjectorHandler<Driver, T, Bridge>
where
Driver: VmiDriver<Architecture = Amd64>
+ VmiRead
+ VmiWrite
+ VmiSetProtection
+ VmiEventControl
+ VmiViewControl
+ VmiVmControl,
Bridge: BridgeDispatch<WindowsOs<Driver>, InjectorResultCode>,
{
#[tracing::instrument(
name = "injector",
skip_all,
fields(
vcpu = %vmi.event().vcpu_id(),
rip = %Va(vmi.registers().rip),
)
)]
fn dispatch(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Amd64>, VmiError> {
tracing::trace!(reason = ?vmi.event().reason(), "handling event");
match vmi.event().reason() {
EventReason::MemoryAccess(_) => self.on_memory_access(vmi),
EventReason::Interrupt(_) => self.on_interrupt(vmi),
EventReason::Singlestep(_) => self.on_singlestep(vmi),
EventReason::Hypercall(_) => self.on_hypercall(vmi),
_ => panic!("Unhandled event: {:?}", vmi.event().reason()),
}
}
#[tracing::instrument(name = "memory_access", skip_all)]
fn on_memory_access(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Amd64>, VmiError> {
let memory_access = vmi.event().reason().as_memory_access();
tracing::trace!(
pa = %memory_access.pa,
va = %memory_access.va,
access = %memory_access.access,
);
if memory_access.access.contains(MemoryAccess::W) {
self.ptm
.mark_dirty_entry(memory_access.pa, self.view, vmi.event().vcpu_id());
Ok(VmiEventResponse::singlestep().with_view(vmi.default_view()))
}
else if memory_access.access.contains(MemoryAccess::R) {
Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
}
else {
panic!("Unhandled memory access: {memory_access:?}");
}
}
#[tracing::instrument(name = "interrupt", skip_all)]
fn on_interrupt(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Amd64>, VmiError> {
match self.bpm.get_by_event(vmi.event(), ()) {
Some(breakpoints) => {
let first_breakpoint = breakpoints.into_iter().next().expect("breakpoint");
debug_assert_eq!(first_breakpoint.tag(), "SeAccessCheck");
}
None => {
if BreakpointController::is_breakpoint(vmi, vmi.event())? {
tracing::warn!("Unknown breakpoint, reinjecting");
return Ok(VmiEventResponse::reinject_interrupt());
}
else {
tracing::warn!("Ignoring old breakpoint event");
return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
}
}
};
let current_process = vmi.os().current_process()?;
if current_process.peb()?.is_none() {
return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
}
let current_pid = current_process.id()?;
if self.pid.is_none() {
tracing::trace!(%current_pid, "hijacking process");
self.pid = Some(current_pid);
}
else if Some(current_pid) != self.pid {
return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
}
let current_thread = vmi.os().current_thread()?;
let current_tid = current_thread.id()?;
if self.tid.is_none() {
self.tid = Some(current_tid);
self.state = InjectorState::Executing;
tracing::debug!(
session_id = current_process
.session()
.ok()
.flatten()
.and_then(|session| session.id().ok())
.unwrap_or(0),
%current_pid,
%current_tid,
filename = current_process.name().unwrap_or_else(|_| String::from("<unknown>")),
"thread hijacked"
);
}
else if Some(current_tid) != self.tid {
return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
}
let new_registers = match self.recipe.execute(vmi)? {
Some(registers) => registers,
None => {
return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
}
};
if !self.recipe.done() {
return Ok(VmiEventResponse::default().with_registers(new_registers.gp_registers()));
}
self.state = InjectorState::Teardown(vmi.event().vcpu_id());
Ok(VmiEventResponse::singlestep()
.with_registers(new_registers.gp_registers())
.with_view(vmi.default_view()))
}
#[tracing::instrument(name = "singlestep", skip_all)]
fn on_singlestep(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Amd64>, VmiError> {
if let InjectorState::Teardown(vcpu) = self.state
&& vcpu == vmi.event().vcpu_id()
{
debug_assert_eq!(vmi.event().view(), Some(vmi.default_view()));
vmi.switch_to_view(vmi.default_view())?;
vmi.monitor_disable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
self.bpm.remove_by_view(vmi, self.view)?;
self.ptm.unmonitor_view(vmi, self.view);
vmi.destroy_view(self.view)?;
if Bridge::EMPTY {
self.state = InjectorState::Complete(Ok(0));
}
else {
self.state = InjectorState::Bridge;
vmi.monitor_enable(EventMonitor::Hypercall {
allow_userspace: false,
})?;
}
return Ok(VmiEventResponse::default());
}
let ptm_events = self.ptm.process_dirty_entries(vmi, vmi.event().vcpu_id())?;
self.bpm.handle_ptm_events(vmi, ptm_events)?;
Ok(VmiEventResponse::default().with_view(self.view))
}
#[tracing::instrument(name = "hypercall", skip_all)]
fn on_hypercall(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Amd64>, VmiError> {
let hypercall = vmi.event().reason().as_hypercall();
let mut registers = vmi.registers().gp_registers();
registers.rip += hypercall.instruction_length as u64;
tracing::trace!(
magic = %Hex(registers.rbp as u32),
request = %Hex((registers.rcx & 0xFFFF) as u16),
method = %Hex((registers.rcx >> 16) as u16),
);
if let Some(result) = self.bridge.dispatch(vmi, BridgePacket::from(vmi)) {
let complete = match result {
Ok(response) => {
response.write_to(&mut registers);
response.into_result().map(Ok)
}
Err(packet) => {
tracing::error!(
request = packet.request(),
method = packet.method(),
"Empty bridge response"
);
Some(Err(packet))
}
};
if let Some(complete) = complete {
self.state = InjectorState::Complete(complete);
vmi.monitor_disable(EventMonitor::Hypercall {
allow_userspace: false,
})?;
}
}
Ok(VmiEventResponse::default().with_registers(registers))
}
}
impl<Driver, T, Bridge> VmiHandler<WindowsOs<Driver>> for KernelInjectorHandler<Driver, T, Bridge>
where
Driver: VmiDriver<Architecture = Amd64>
+ VmiRead
+ VmiWrite
+ VmiSetProtection
+ VmiEventControl
+ VmiViewControl
+ VmiVmControl,
Bridge: BridgeDispatch<WindowsOs<Driver>, InjectorResultCode>,
{
type Output = Result<InjectorResultCode, BridgePacket>;
fn handle_event(&mut self, vmi: VmiContext<WindowsOs<Driver>>) -> VmiEventResponse<Amd64> {
vmi.flush_v2p_cache();
match self.dispatch(&vmi) {
Ok(response) => response,
Err(VmiError::Translation(pfs)) => {
let pf = pfs[0];
tracing::debug!(va = %pf.va, "injecting page fault");
let _ =
vmi.inject_interrupt(vmi.event().vcpu_id(), Interrupt::page_fault(pf.va, 0));
VmiEventResponse::default()
}
Err(err) => panic!("Unhandled error: {err:?}"),
}
}
fn cleanup(&mut self, vmi: &VmiSession<WindowsOs<Driver>>) {
let mut disable_interrupt = false;
let mut disable_hypercall = false;
match self.state {
InjectorState::PreHijack | InjectorState::Executing | InjectorState::Teardown(_) => {
disable_interrupt = true;
}
InjectorState::Bridge => {
disable_hypercall = true;
}
_ => {}
}
if disable_interrupt {
if let Err(err) =
vmi.monitor_disable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))
{
tracing::error!(%err, "failed to disable breakpoint monitor");
}
if let Err(err) = self.bpm.remove_by_view(vmi, self.view) {
tracing::error!(%err, "failed to remove breakpoints");
}
self.ptm.unmonitor_view(vmi, self.view);
if let Err(err) = vmi.destroy_view(self.view) {
tracing::error!(%err, "failed to destroy view");
}
}
if disable_hypercall
&& let Err(err) = vmi.monitor_disable(EventMonitor::Hypercall {
allow_userspace: false,
})
{
tracing::error!(%err, "failed to disable hypercall monitor");
}
if let Err(err) = vmi.monitor_disable(EventMonitor::Singlestep) {
tracing::error!(%err, "failed to disable singlestep monitor");
}
}
fn poll(&self) -> Option<Self::Output> {
match self.state {
InjectorState::Complete(result) => Some(result),
_ => None,
}
}
}