#![expect(non_snake_case)]
mod ncrypt;
mod netio;
mod ntoskrnl;
use std::sync::{Arc, atomic::AtomicBool};
use anyhow::{Context as _, Error};
use isr::cache::IsrCache;
use tracing_subscriber::EnvFilter;
use vmi::{
VcpuId, VmiContext, VmiCore, VmiError, VmiOs, VmiRead, VmiSession,
arch::amd64::Amd64,
driver::xen::VmiXenDriver,
os::{
VmiOsProcess as _,
windows::{ArchAdapter, WindowsOs, WindowsProcess},
},
utils::reactor::{Action, Reactor, ReactorHandler, define_events, define_modules},
};
use xen::{XenDomainId, XenStore};
fn match_lsass<Driver>(process: &WindowsProcess<Driver>) -> Result<bool, VmiError>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
Ok(
matches!(process.session()?, Some(session) if session.id()? == 0)
&& process.name()?.eq_ignore_ascii_case("lsass.exe"),
)
}
define_modules! {
#[os(
<Driver: VmiRead> WindowsOs<Driver>
where Driver::Architecture: ArchAdapter<Driver>
)]
enum Module {
#[module(name = "netio.sys")]
NetioSys,
#[module(name = "ncrypt.dll", mode(user, process = match_lsass))]
NcryptDll,
}
#[resolver]
struct ModuleResolver;
#[cache]
struct SymbolCache;
}
define_events! {
enum Event in Module {
NtWriteFile,
NetioSys {
KfdClassify,
KfdIsLayerEmpty,
},
NcryptDll {
SslGenerateSessionKeys,
},
}
}
#[derive(Default)]
struct NetIo {
NtWriteFile_counter: u64,
KfdClassify_counter: u64,
KfdIsLayerEmpty_counter: u64,
SslGenerateSessionKeys_counter: u64,
}
impl NetIo {
#[tracing::instrument(skip_all)]
fn NtWriteFile<Driver>(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
self.NtWriteFile_counter += 1;
ntoskrnl::NtWriteFile(vmi)
}
#[tracing::instrument(skip_all)]
fn KfdClassify<Driver>(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
self.KfdClassify_counter += 1;
netio::KfdClassify(vmi)
}
#[tracing::instrument(skip_all)]
fn KfdIsLayerEmpty<Driver>(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
self.KfdIsLayerEmpty_counter += 1;
netio::KfdIsLayerEmpty(vmi)
}
#[tracing::instrument(skip_all)]
fn SslGenerateSessionKeys<Driver>(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture>, VmiError>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
self.SslGenerateSessionKeys_counter += 1;
ncrypt::SslGenerateSessionKeys(vmi)
}
}
impl Drop for NetIo {
fn drop(&mut self) {
tracing::info!(
NtWriteFile = self.NtWriteFile_counter,
KfdClassify = self.KfdClassify_counter,
KfdIsLayerEmpty = self.KfdIsLayerEmpty_counter,
SslGenerateSessionKeys = self.SslGenerateSessionKeys_counter,
"hit counts"
);
}
}
impl<Driver> ReactorHandler<WindowsOs<Driver>> for NetIo
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
type Output = ();
type Event = Event;
fn handle_event(
&mut self,
vmi: &VmiContext<WindowsOs<Driver>>,
event: Self::Event,
) -> Result<Action<<WindowsOs<Driver> as VmiOs>::Architecture, Self::Output>, VmiError> {
match event {
Self::Event::NtWriteFile => self.NtWriteFile(vmi),
Self::Event::KfdClassify => self.KfdClassify(vmi),
Self::Event::KfdIsLayerEmpty => self.KfdIsLayerEmpty(vmi),
Self::Event::SslGenerateSessionKeys => self.SslGenerateSessionKeys(vmi),
}
}
}
fn main() -> Result<(), Error> {
let filter = EnvFilter::default()
.add_directive(tracing::Level::DEBUG.into())
.add_directive("reqwest=warn".parse()?)
.add_directive("rustls=warn".parse()?);
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_target(false)
.init();
let domain_id = match std::env::var("VMI_XEN_DOMAIN_ID") {
Ok(domain_id) => XenDomainId(
domain_id
.parse()
.context("invalid VMI_XEN_DOMAIN_ID environment variable")?,
),
Err(_) => {
let domain_name = std::env::var("VMI_XEN_DOMAIN_NAME")
.context("invalid VMI_XEN_DOMAIN_NAME environment variable")?;
tracing::info!(%domain_name, "resolving domain ID");
match XenStore::new()?.domain_id_from_name(&domain_name)? {
Some(domain_id) => domain_id,
None => return Err(anyhow::anyhow!("domain not found: {domain_name}")),
}
}
};
tracing::info!(%domain_id, "setting up VMI");
let driver = VmiXenDriver::<Amd64>::new(domain_id)?;
let core = VmiCore::new(driver)?;
let kernel_info = {
let _pause_guard = core.pause_guard()?;
let registers = core.registers(VcpuId(0))?;
WindowsOs::find_kernel(&core, ®isters)?.context("cannot find kernel information")?
};
tracing::info!(codeview = ?kernel_info.codeview, "loading kernel profile");
let isr = IsrCache::new("cache")?;
let entry = isr.entry_from_codeview(kernel_info.codeview)?;
let profile = entry.profile()?;
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);
let handler = NetIo::default();
let ncrypt_resolved = {
let paused = session.pause_guard()?;
let vmi = paused.state();
vmi::utils::resolver::resolve_user_module(&vmi, &isr, "ncrypt.dll", match_lsass)?
.context("ncrypt.dll not found in lsass.exe")?
};
let ncrypt_process = ncrypt_resolved
.process
.context("resolved ncrypt.dll is not associated with a process")?;
let ncrypt_entry = isr
.entry_from_codeview(ncrypt_resolved.debug_signature)
.context("cannot find symbols for ncrypt.dll")?;
let ncrypt_profile = ncrypt_entry
.profile()
.context("cannot load profile for ncrypt.dll")?;
let mut cache = SymbolCache::default();
let modules = ModuleResolver::default()
.with_kernel(kernel_info.base_address, profile)
.with_module_in_process(
Module::NcryptDll,
ncrypt_process,
ncrypt_resolved.image_base,
ncrypt_profile,
)
.resolve(&session, &isr, &mut cache)?;
let events = modules.into_events()?;
session.handle(|session| {
Ok(Reactor::new(session, handler, events)?.with_termination_flag(terminate_flag))
})?;
Ok(())
}