mod event;
#[doc(hidden)]
pub mod macros;
mod module;
mod profile;
use std::{
cell::RefCell,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
};
use vmi_arch_amd64::{Amd64, EventMonitor, EventReason, ExceptionVector, Interrupt};
use vmi_core::{
Architecture, MemoryAccess, View, VmiContext, VmiError, VmiEventResponse, VmiHandler, VmiOs,
VmiSession, driver::VmiFullDriver, os::VmiOsProcess as _,
};
use vmi_os_windows::WindowsOs;
pub use self::{
event::{EventMetadata, ReactorEvent, ResolvedEvent},
module::{ModuleMetadata, ModuleMode, ModuleProcessFilter, ReactorModule, ResolvedModule},
profile::ProfileRef,
};
use super::{
bpm::{Breakpoint, BreakpointController, BreakpointManager},
ptm::{self, PageTableMonitor},
};
#[doc(inline)]
pub use crate::{
_private_define_events as define_events, _private_define_modules as define_modules,
};
#[derive(Default)]
pub enum Action<Arch, T = ()>
where
Arch: Architecture,
{
#[default]
Default,
Response(VmiEventResponse<Arch>),
Done(T),
}
pub trait ReactorHandler<Os>
where
Os: VmiOs,
{
type Output;
type Event: ReactorEvent;
fn handle_event(
&mut self,
vmi: &VmiContext<Os>,
event: Self::Event,
) -> Result<Action<Os::Architecture, Self::Output>, VmiError>;
}
pub struct Reactor<Os, Handler>
where
Os: VmiOs + 'static,
Os::Driver: VmiFullDriver,
Os::Architecture: ptm::ArchAdapter<Os::Driver, Handler::Event>,
Handler: ReactorHandler<Os>,
<Handler::Event as ReactorEvent>::Module: ReactorModule<Os>,
{
bpm: BreakpointManager<BreakpointController<Os::Driver>, (), Handler::Event>,
ptm: PageTableMonitor<Os::Driver, Handler::Event>,
view: View,
handler: Handler,
output: RefCell<Option<Handler::Output>>,
termination_flag: Option<Arc<AtomicBool>>,
}
impl<Driver, Handler> Reactor<WindowsOs<Driver>, Handler>
where
Driver: VmiFullDriver<Architecture = Amd64>, Driver::Architecture:
vmi_os_windows::ArchAdapter<Driver> + ptm::ArchAdapter<Driver, Handler::Event>,
Handler: ReactorHandler<WindowsOs<Driver>>,
<Handler::Event as ReactorEvent>::Module: ReactorModule<WindowsOs<Driver>>,
{
pub fn new(
session: &VmiSession<WindowsOs<Driver>>,
handler: Handler,
events: impl AsRef<[ResolvedEvent<Handler::Event>]>,
) -> Result<Self, VmiError> {
let paused = session.pause_guard()?;
let vmi = paused.state();
vmi.monitor_enable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))?;
vmi.monitor_enable(EventMonitor::Singlestep)?;
let view = vmi.create_view(MemoryAccess::RWX)?;
vmi.switch_to_view(view)?;
let system_process = vmi.os().system_process()?;
let system_root = system_process.translation_root()?;
let mut bpm = BreakpointManager::new();
let mut ptm = PageTableMonitor::new();
for event in events.as_ref() {
let root = match event.process {
Some(process) => vmi.os().process(process)?.translation_root()?,
None => system_root,
};
let cx = (event.address, root);
let bp = Breakpoint::new(cx, view).global().with_tag(event.event);
bpm.insert(&vmi, bp)?;
ptm.monitor(&vmi, cx, view, event.event)?;
}
Ok(Self {
bpm,
ptm,
view,
handler,
output: RefCell::new(None),
termination_flag: None,
})
}
pub fn with_termination_flag(self, termination_flag: Arc<AtomicBool>) -> Self {
Self {
termination_flag: Some(termination_flag),
..self
}
}
#[tracing::instrument(
skip_all,
fields(
pa = %vmi.event().reason().as_memory_access().pa,
va = %vmi.event().reason().as_memory_access().va,
access = %vmi.event().reason().as_memory_access().access,
)
)]
fn memory_access(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Driver::Architecture>, VmiError> {
let memory_access = vmi.event().reason().as_memory_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(skip_all)]
fn interrupt(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Driver::Architecture>, VmiError> {
let tag = match self.bpm.get_by_event(vmi.event(), ()) {
Some(mut breakpoints) => {
assert!(
breakpoints.len() == 1,
"multiple breakpoints for the same event"
);
breakpoints.next().expect("breakpoint").tag()
}
None => {
if BreakpointController::is_breakpoint(vmi, vmi.event())? {
tracing::debug!("unknown breakpoint, reinjecting");
return Ok(VmiEventResponse::reinject_interrupt());
}
tracing::debug!("ignoring old breakpoint event");
return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
}
};
if self.output.borrow().is_some() {
return Ok(VmiEventResponse::fast_singlestep(vmi.default_view()));
}
match self.handler.handle_event(vmi, tag)? {
Action::Default => (),
Action::Response(response) => return Ok(response),
Action::Done(output) => {
self.output.borrow_mut().replace(output);
}
}
Ok(VmiEventResponse::fast_singlestep(vmi.default_view()))
}
#[tracing::instrument(skip_all)]
fn singlestep(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Driver::Architecture>, VmiError> {
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 = "reactor",
skip_all,
fields(
vcpu = %vmi.event().vcpu_id(),
view = vmi_core::trace::event_view(vmi.event()),
pid = vmi_core::trace::current_process_id(vmi),
tid = vmi_core::trace::current_thread_id(vmi),
//name = vmi_core::trace::current_process_name(vmi),
)
)]
fn dispatch(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<VmiEventResponse<Driver::Architecture>, 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 let Err(VmiError::Translation(pf)) = result {
tracing::debug!(va = %pf.va, "page fault");
vmi.inject_interrupt(event.vcpu_id(), Interrupt::page_fault(pf.va, 0))?;
return Ok(VmiEventResponse::default());
}
result
}
}
impl<Driver, Handler> VmiHandler<WindowsOs<Driver>> for Reactor<WindowsOs<Driver>, Handler>
where
Driver: VmiFullDriver<Architecture = Amd64>,
Handler: ReactorHandler<WindowsOs<Driver>>,
<Handler::Event as ReactorEvent>::Module: ReactorModule<WindowsOs<Driver>>,
{
type Output = Option<Handler::Output>;
fn handle_event(
&mut self,
vmi: VmiContext<WindowsOs<Driver>>,
) -> VmiEventResponse<Driver::Architecture> {
vmi.flush_v2p_cache();
self.dispatch(&vmi).expect("dispatch")
}
fn cleanup(&mut self, vmi: &VmiSession<WindowsOs<Driver>>) {
if let Err(err) = vmi.switch_to_view(vmi.default_view()) {
tracing::error!(%err, "failed to switch to default view");
}
if let Err(err) = vmi.monitor_disable(EventMonitor::Singlestep) {
tracing::error!(%err, "failed to disable singlestep");
}
if let Err(err) = vmi.monitor_disable(EventMonitor::Interrupt(ExceptionVector::Breakpoint))
{
tracing::error!(%err, "failed to disable breakpoint interrupt");
}
match self.bpm.remove_by_view(vmi, self.view) {
Ok(true) => {}
Ok(false) => tracing::warn!("no breakpoints to remove"),
Err(err) => tracing::error!(%err, "failed to remove breakpoints"),
}
self.ptm.unmonitor_all(vmi);
if let Err(err) = vmi.destroy_view(self.view) {
tracing::error!(%err, "failed to destroy view");
}
}
fn poll(&self) -> Option<Self::Output> {
if let Some(terminate_flag) = &self.termination_flag
&& terminate_flag.load(Ordering::Relaxed)
{
Some(None)
}
else {
self.output.borrow_mut().take().map(Some)
}
}
}