#![allow(
non_snake_case, // example: AlpcpSendMessage
non_upper_case_globals, // example: StandbyPageList
)]
use std::{cell::RefCell, collections::HashMap};
use isr_core::Profile;
use once_cell::unsync::OnceCell;
use vmi_core::{
AccessContext, Architecture, Gfn, Pa, Registers as _, Va, VcpuId, VmiCore, VmiDriver, VmiError,
VmiState,
driver::{VmiRead, VmiWrite},
os::{ProcessObject, ThreadObject, VmiOs, VmiOsThread},
trace::Hex,
};
use vmi_macros::derive_trait_from_impl;
use zerocopy::{FromBytes, IntoBytes};
mod arch;
pub use self::arch::{
ArchAdapter, CONTEXT_AMD64, CONTEXT_X86, FLOATING_SAVE_AREA, KDESCRIPTOR_AMD64,
KDESCRIPTOR_X86, KSPECIAL_REGISTERS_AMD64, KSPECIAL_REGISTERS_X86, M128A,
MAXIMUM_SUPPORTED_EXTENSION, SIZE_OF_80387_REGISTERS, StructLayout, StructLayout32,
StructLayout64, WindowsContext, WindowsExceptionVector, WindowsInterrupt,
WindowsPageTableEntry, WindowsRegistersAdapter, WindowsSpecialRegisters, XSAVE_FORMAT,
};
mod error;
pub use self::error::WindowsError;
mod iter;
pub use self::iter::{
DirectoryObjectIterator, HandleTableEntryIterator, KeyControlBlockIterator, KeyNodeIterator,
KeyValueIterator, ListEntry, ListEntryIterator, ListEntryIteratorBase, ListEntryLayout,
TreeNodeIterator,
};
pub mod pe;
pub use self::pe::{CodeView, PeError, PeFile, PeHeader, PeImage, PeImageExt};
pub mod unwind;
mod offsets;
pub use self::offsets::{Offsets, OffsetsExt, Symbols};
mod comps;
pub use self::comps::{
CurDir, CurDirLayout, FromWindowsObject, LdrDataTableEntry, LdrDataTableEntryLayout,
ParseObjectTypeError, Peb, PebLayout, PebLdrData, PebLdrDataLayout, RtlUserProcessParameters,
RtlUserProcessParametersLayout, Teb, TebLayout, WOW64_TLS_APCLIST, WOW64_TLS_CPURESERVED,
WOW64_TLS_FILESYSREDIR, WOW64_TLS_TEMPLIST, WOW64_TLS_USERCALLBACKDATA, WOW64_TLS_WOW64INFO,
WindowsControlArea, WindowsDirectoryObject, WindowsFileObject, WindowsHandleTable,
WindowsHandleTableEntry, WindowsHive, WindowsHiveBaseBlock, WindowsHiveCellIndex,
WindowsHiveMapDirectory, WindowsHiveMapEntry, WindowsHiveMapTable, WindowsHiveStorageType,
WindowsImage, WindowsImpersonationLevel, WindowsKernelProcessorBlock, WindowsKeyControlBlock,
WindowsKeyIndex, WindowsKeyNode, WindowsKeyValue, WindowsKeyValueData, WindowsKeyValueFlags,
WindowsKeyValueType, WindowsLuid, WindowsModule, WindowsObject, WindowsObjectAttributes,
WindowsObjectHeaderNameInfo, WindowsObjectType, WindowsObjectTypeKind, WindowsPeb,
WindowsPebBase, WindowsPebLdrData, WindowsPebLdrDataBase, WindowsPrivilege, WindowsProcess,
WindowsProcessParameters, WindowsProcessParametersBase, WindowsProcessorMode, WindowsRegion,
WindowsSectionObject, WindowsSegment, WindowsSession, WindowsSid, WindowsSidAndAttributes,
WindowsSidAttributes, WindowsTeb, WindowsTebBase, WindowsThread, WindowsThreadState,
WindowsThreadWaitReason, WindowsToken, WindowsTokenFlags, WindowsTokenPrivilege,
WindowsTokenSource, WindowsTokenType, WindowsTrapFrame, WindowsUnloadedDriver,
WindowsUserModule, WindowsUserModuleBase, WindowsWow64Kind,
};
pub struct WindowsOs<Driver>
where
Driver: VmiDriver,
{
offsets: Offsets,
symbols: Symbols,
kernel_image_base: OnceCell<Va>,
highest_user_address: OnceCell<Va>,
object_root_directory: OnceCell<Va>, object_header_cookie: OnceCell<u8>,
object_type_cache: RefCell<HashMap<WindowsObjectTypeKind, Va>>,
object_type_rcache: RefCell<HashMap<Va, WindowsObjectTypeKind>>,
object_type_name_cache: RefCell<HashMap<Va, String>>,
ke_number_processors: OnceCell<u16>, ki_processor_block: OnceCell<Vec<Va>>, ki_kva_shadow: OnceCell<bool>,
mm_pfn_database: OnceCell<Va>, mm_unloaded_drivers: OnceCell<Va>, nt_build_number: OnceCell<u32>, nt_build_lab: OnceCell<String>,
nt_build_lab_ex: OnceCell<String>,
ps_idle_process: OnceCell<Va>,
_marker: std::marker::PhantomData<Driver>,
}
#[derive(Debug)]
pub struct WindowsKernelInformation {
pub base_address: Va,
pub version_major: u16,
pub version_minor: u16,
pub codeview: CodeView,
}
#[derive(Debug)]
pub struct WindowsExceptionRecord {
pub code: u32,
pub flags: u32,
pub record: Va,
pub address: Va,
pub information: Vec<u64>,
}
macro_rules! offset {
($vmi:expr, $field:ident) => {
&$vmi.underlying_os().offsets.$field
};
}
pub(crate) use offset;
macro_rules! offset_ext_v1 {
($vmi:expr, $field:ident) => {
match $vmi.underlying_os().offsets.ext() {
Some($crate::offsets::OffsetsExt::V1(offsets)) => &offsets.$field,
_ => unreachable!(),
}
};
}
pub(crate) use offset_ext_v1;
macro_rules! offset_ext_v2 {
($vmi:expr, $field:ident) => {
match $vmi.underlying_os().offsets.ext() {
Some($crate::offsets::OffsetsExt::V2(offsets)) => &offsets.$field,
_ => unreachable!(),
}
};
}
pub(crate) use offset_ext_v2;
macro_rules! symbol {
($vmi:expr, $field:ident) => {
$vmi.underlying_os().symbols.$field
};
}
pub(crate) use symbol;
macro_rules! this {
($vmi:expr) => {
$vmi.underlying_os()
};
}
#[derive_trait_from_impl(WindowsOsExt)]
#[expect(non_snake_case, non_upper_case_globals)]
impl<Driver> WindowsOs<Driver>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
pub const NtCurrentProcess32: u64 = 0xffff_ffff;
pub const NtCurrentProcess64: u64 = 0xffff_ffff_ffff_ffff;
pub const NtCurrentThread32: u64 = 0xffff_fffe;
pub const NtCurrentThread64: u64 = 0xffff_ffff_ffff_fffe;
pub fn new(profile: &Profile) -> Result<Self, VmiError> {
Self::create(profile, OnceCell::new())
}
pub fn with_kernel_base(profile: &Profile, kernel_base: Va) -> Result<Self, VmiError> {
Self::create(profile, OnceCell::with_value(kernel_base))
}
fn create(profile: &Profile, kernel_image_base: OnceCell<Va>) -> Result<Self, VmiError> {
Ok(Self {
offsets: Offsets::new(profile)?,
symbols: Symbols::new(profile)?,
kernel_image_base,
highest_user_address: OnceCell::new(),
object_root_directory: OnceCell::new(),
object_header_cookie: OnceCell::new(),
object_type_cache: RefCell::new(HashMap::new()),
object_type_rcache: RefCell::new(HashMap::new()),
object_type_name_cache: RefCell::new(HashMap::new()),
ke_number_processors: OnceCell::new(),
ki_processor_block: OnceCell::new(),
ki_kva_shadow: OnceCell::new(),
mm_pfn_database: OnceCell::new(),
mm_unloaded_drivers: OnceCell::new(),
nt_build_number: OnceCell::new(),
nt_build_lab: OnceCell::new(),
nt_build_lab_ex: OnceCell::new(),
ps_idle_process: OnceCell::new(),
_marker: std::marker::PhantomData,
})
}
pub fn offsets(vmi: VmiState<'_, Self>) -> &Offsets {
&this!(vmi).offsets
}
pub fn symbols(vmi: VmiState<'_, Self>) -> &Symbols {
&this!(vmi).symbols
}
pub fn find_kernel(
vmi: &VmiCore<Driver>,
registers: &<Driver::Architecture as Architecture>::Registers,
) -> Result<Option<WindowsKernelInformation>, VmiError> {
Driver::Architecture::find_kernel(vmi, registers)
}
pub fn kernel_build_number(vmi: VmiState<Self>) -> Result<u32, VmiError> {
this!(vmi)
.nt_build_number
.get_or_try_init(|| {
let NtBuildNumber = symbol!(vmi, NtBuildNumber);
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_u32(kernel_image_base + NtBuildNumber)
})
.cloned()
}
pub fn kernel_information_string_ex(vmi: VmiState<Self>) -> Result<Option<String>, VmiError> {
let NtBuildLabEx = match symbol!(vmi, NtBuildLabEx) {
Some(offset) => offset,
None => return Ok(None),
};
Ok(Some(
this!(vmi)
.nt_build_lab_ex
.get_or_try_init(|| {
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_string(kernel_image_base + NtBuildLabEx)
})
.cloned()?,
))
}
pub fn is_kernel_handle(vmi: VmiState<Self>, handle: u64) -> Result<bool, VmiError> {
const KERNEL_HANDLE_MASK32: u64 = 0x8000_0000;
const KERNEL_HANDLE_MASK64: u64 = 0xffff_ffff_8000_0000;
match vmi.registers().address_width() {
4 => Ok(handle & KERNEL_HANDLE_MASK32 == KERNEL_HANDLE_MASK32),
8 => Ok(handle & KERNEL_HANDLE_MASK64 == KERNEL_HANDLE_MASK64),
_ => Err(VmiError::InvalidAddressWidth),
}
}
pub fn lowest_user_address(_vmi: VmiState<Self>) -> Result<Va, VmiError> {
Ok(Va(0x10000))
}
pub fn highest_user_address(vmi: VmiState<Self>) -> Result<Va, VmiError> {
this!(vmi)
.highest_user_address
.get_or_try_init(|| {
let MmHighestUserAddress = symbol!(vmi, MmHighestUserAddress);
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_va_native(kernel_image_base + MmHighestUserAddress)
})
.copied()
}
pub fn is_valid_user_address(vmi: VmiState<Self>, address: Va) -> Result<bool, VmiError> {
let lowest_user_address = Self::lowest_user_address(vmi)?;
let highest_user_address = Self::highest_user_address(vmi)?;
Ok(address >= lowest_user_address && address <= highest_user_address)
}
pub fn unloaded_modules<'a>(
vmi: VmiState<'a, Self>,
) -> Result<
impl Iterator<Item = Result<WindowsUnloadedDriver<'a, Driver>, VmiError>> + use<'a, Driver>,
VmiError,
> {
let MmLastUnloadedDriver = symbol!(vmi, MmLastUnloadedDriver);
let kernel_image_base = Self::kernel_image_base(vmi)?;
let mm_last_unloaded_driver = vmi.read_u32(kernel_image_base + MmLastUnloadedDriver)?;
let mm_unloaded_drivers = this!(vmi)
.mm_unloaded_drivers
.get_or_try_init(|| {
let MmUnloadedDrivers = symbol!(vmi, MmUnloadedDrivers);
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_va_native(kernel_image_base + MmUnloadedDrivers)
})
.copied()?;
const MI_UNLOADED_DRIVERS: u32 = 50;
const UNLOADED_DRIVERS_Name_Buffer: u64 = 0x08;
const UNLOADED_DRIVERS_sizeof: u64 = 0x28;
let mut index = mm_last_unloaded_driver;
let mut count = 0;
Ok(std::iter::from_fn(move || {
if count >= MI_UNLOADED_DRIVERS {
return None;
}
index = index.checked_sub(1).unwrap_or(MI_UNLOADED_DRIVERS - 1);
count += 1;
let va = mm_unloaded_drivers + index as u64 * UNLOADED_DRIVERS_sizeof;
let name_buffer = match vmi.read_va_native(va + UNLOADED_DRIVERS_Name_Buffer) {
Ok(name_buffer) => name_buffer,
Err(err) => return Some(Err(err)),
};
if name_buffer.is_null() {
return None;
}
Some(Ok(WindowsUnloadedDriver::new(vmi, va)))
}))
}
pub fn number_of_processors(vmi: VmiState<Self>) -> Result<u16, VmiError> {
this!(vmi)
.ke_number_processors
.get_or_try_init(|| {
let KeNumberProcessors = symbol!(vmi, KeNumberProcessors);
let kernel_image_base = Self::kernel_image_base(vmi)?;
Ok(vmi.read_u8(kernel_image_base + KeNumberProcessors)? as u16)
})
.copied()
}
pub fn current_kpcr(vmi: VmiState<Self>) -> Va {
Driver::Architecture::current_kpcr(vmi)
}
pub fn kprcb<'a>(
vmi: VmiState<'a, Self>,
vcpu_id: VcpuId,
) -> Result<WindowsKernelProcessorBlock<'a, Driver>, VmiError> {
let number_of_processors = Self::number_of_processors(vmi)? as u64;
let ki_processor_block = this!(vmi).ki_processor_block.get_or_try_init(|| {
let KiProcessorBlock = symbol!(vmi, KiProcessorBlock);
let kernel_image_base = Self::kernel_image_base(vmi)?;
let mut blocks = Vec::new();
for cpu in 0..number_of_processors {
let offset = cpu * vmi.registers().address_width() as u64;
let addr = vmi.read_va_native(kernel_image_base + KiProcessorBlock + offset)?;
blocks.push(addr);
}
Ok::<_, VmiError>(blocks)
})?;
if vcpu_id.0 as usize >= ki_processor_block.len() {
return Err(WindowsError::InvalidProcessor(vcpu_id).into());
}
let prcb = ki_processor_block[vcpu_id.0 as usize];
if prcb.is_null() {
return Err(WindowsError::CorruptedStruct("KiProcessorBlock").into());
}
Ok(WindowsKernelProcessorBlock::new(vmi, prcb))
}
pub fn current_kprcb<'a>(
vmi: VmiState<'a, Self>,
) -> Result<WindowsKernelProcessorBlock<'a, Driver>, VmiError> {
let KPCR = offset!(vmi, _KPCR);
let kpcr = Self::current_kpcr(vmi);
if kpcr.is_null() {
return Err(WindowsError::CorruptedStruct("KPCR").into());
}
let prcb = kpcr + KPCR.Prcb.offset();
Ok(WindowsKernelProcessorBlock::new(vmi, prcb))
}
pub fn exception_record(
vmi: VmiState<Self>,
address: Va,
) -> Result<WindowsExceptionRecord, VmiError> {
#[repr(C)]
#[derive(Debug, Copy, Clone, FromBytes, IntoBytes)]
#[allow(non_camel_case_types, non_snake_case)]
struct _EXCEPTION_RECORD {
ExceptionCode: u32,
ExceptionFlags: u32,
ExceptionRecord: u64,
ExceptionAddress: u64,
NumberParameters: u64,
ExceptionInformation: [u64; 15],
}
let record = vmi.read_struct::<_EXCEPTION_RECORD>(address)?;
Ok(WindowsExceptionRecord {
code: record.ExceptionCode,
flags: record.ExceptionFlags,
record: record.ExceptionRecord.into(),
address: record.ExceptionAddress.into(),
information: record.ExceptionInformation
[..u64::min(record.NumberParameters, 15) as usize]
.to_vec(),
})
}
pub fn last_status(vmi: VmiState<Self>) -> Result<Option<u32>, VmiError> {
let KTHREAD = offset!(vmi, _KTHREAD);
let TEB = offset!(vmi, _TEB);
let current_thread = Self::current_thread(vmi)?.object()?;
let teb = vmi.read_va_native(current_thread.0 + KTHREAD.Teb.offset())?;
if teb.is_null() {
return Ok(None);
}
let result = vmi.read_u32(teb + TEB.LastStatusValue.offset())?;
Ok(Some(result))
}
pub fn idle_process<'a>(
vmi: VmiState<'a, Self>,
) -> Result<WindowsProcess<'a, Driver>, VmiError> {
let ps_idle_process = this!(vmi)
.ps_idle_process
.get_or_try_init(|| {
let PsIdleProcess = symbol!(vmi, PsIdleProcess);
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_va_native(kernel_image_base + PsIdleProcess)
})
.copied()?;
Ok(WindowsProcess::new(vmi, ProcessObject(ps_idle_process)))
}
pub fn idle_thread<'a>(vmi: VmiState<'a, Self>) -> Result<WindowsThread<'a, Driver>, VmiError> {
Self::current_kprcb(vmi)?.idle_thread()
}
fn pfn_database(vmi: VmiState<Self>) -> Result<Va, VmiError> {
this!(vmi)
.mm_pfn_database
.get_or_try_init(|| {
let MmPfnDatabase = symbol!(vmi, MmPfnDatabase);
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_va_native(kernel_image_base + MmPfnDatabase)
})
.copied()
}
pub fn object<'a>(
vmi: VmiState<'a, Self>,
va: Va,
) -> Result<WindowsObject<'a, Driver>, VmiError> {
Ok(WindowsObject::new(vmi, va))
}
pub fn object_type<'a>(
vmi: VmiState<'a, Self>,
kind: WindowsObjectTypeKind,
) -> Result<WindowsObjectType<'a, Driver>, VmiError> {
if let Some(va) = this!(vmi).object_type_cache.borrow().get(&kind).copied() {
return Ok(WindowsObjectType::new(vmi, va));
}
let symbol = match kind {
WindowsObjectTypeKind::File => symbol!(vmi, IoFileObjectType),
WindowsObjectTypeKind::Job => symbol!(vmi, PsJobType),
WindowsObjectTypeKind::Key => symbol!(vmi, CmKeyObjectType),
WindowsObjectTypeKind::Process => symbol!(vmi, PsProcessType),
WindowsObjectTypeKind::Thread => symbol!(vmi, PsThreadType),
WindowsObjectTypeKind::Token => symbol!(vmi, SeTokenObjectType),
_ => return Err(VmiError::NotSupported),
};
let va = vmi.read_va(Self::kernel_image_base(vmi)? + symbol)?;
this!(vmi).object_type_cache.borrow_mut().insert(kind, va);
Ok(WindowsObjectType::new(vmi, va))
}
pub fn object_root_directory<'a>(
vmi: VmiState<'a, Self>,
) -> Result<WindowsDirectoryObject<'a, Driver>, VmiError> {
let object_root_directory = this!(vmi)
.object_root_directory
.get_or_try_init(|| {
let ObpRootDirectoryObject = symbol!(vmi, ObpRootDirectoryObject);
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_va_native(kernel_image_base + ObpRootDirectoryObject)
})
.copied()?;
Ok(WindowsDirectoryObject::new(vmi, object_root_directory))
}
pub fn object_header_cookie(vmi: VmiState<Self>) -> Result<Option<u8>, VmiError> {
let ObHeaderCookie = match symbol!(vmi, ObHeaderCookie) {
Some(cookie) => cookie,
None => return Ok(None),
};
Ok(Some(
this!(vmi)
.object_header_cookie
.get_or_try_init(|| {
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_u8(kernel_image_base + ObHeaderCookie)
})
.copied()?,
))
}
pub fn object_attributes<'a>(
vmi: VmiState<'a, Self>,
object_attributes: Va,
) -> Result<WindowsObjectAttributes<'a, Driver>, VmiError> {
Ok(WindowsObjectAttributes::new(vmi, object_attributes))
}
pub fn lookup_object<'a>(
vmi: VmiState<'a, Self>,
path: impl AsRef<str>,
) -> Result<Option<WindowsObject<'a, Driver>>, VmiError> {
Self::object_root_directory(vmi)?.lookup(path)
}
pub fn hives<'a>(
vmi: VmiState<'a, Self>,
) -> Result<
impl Iterator<Item = Result<WindowsHive<'a, Driver>, VmiError>> + use<'a, Driver>,
VmiError,
> {
let CmpHiveListHead = Self::kernel_image_base(vmi)? + symbol!(vmi, CmpHiveListHead);
let CMHIVE = offset!(vmi, _CMHIVE);
Ok(
ListEntryIterator::new(vmi, CmpHiveListHead, CMHIVE.HiveList.offset())
.map(move |result| result.map(|entry| WindowsHive::new(vmi, entry))),
)
}
pub fn lookup_key<'a>(
vmi: VmiState<'a, Self>,
path: impl AsRef<str>,
) -> Result<Option<WindowsKeyNode<'a, Driver>>, VmiError> {
let path = path.as_ref();
let path_components = path
.split('\\')
.filter(|component| !component.is_empty())
.collect::<Vec<_>>();
let mut best = None;
for hive in Self::hives(vmi)? {
let hive = match hive {
Ok(hive) => hive,
Err(err) => {
tracing::trace!(%err, "skipping unreadable hive entry");
continue;
}
};
let root = match hive.hive_root_path() {
Ok(root) => root,
Err(err) => {
tracing::trace!(%err, "skipping hive with unreadable HiveRootPath");
continue;
}
};
let mut depth = 0;
let mut matched = true;
for root_component in root.split('\\').filter(|component| !component.is_empty()) {
let path_component = match path_components.get(depth) {
Some(path_component) => path_component,
None => {
matched = false;
break;
}
};
if !path_component.eq_ignore_ascii_case(root_component) {
matched = false;
break;
}
depth += 1;
}
if !matched {
continue;
}
let beats_best = match &best {
Some((_, best_depth)) => depth > *best_depth,
None => true,
};
if beats_best {
best = Some((hive, depth));
}
}
match best {
Some((hive, depth)) => {
let rest = path_components[depth..].join("\\");
hive.lookup(rest)
}
None => Ok(None),
}
}
pub fn read_fast_ref(vmi: VmiState<Self>, va: Va) -> Result<Va, VmiError> {
let EX_FAST_REF = offset!(vmi, _EX_FAST_REF);
debug_assert_eq!(EX_FAST_REF.RefCnt.offset(), 0);
debug_assert_eq!(EX_FAST_REF.RefCnt.bit_position(), 0);
let object = vmi.read_va_native(va)?;
Ok(object & !((1 << EX_FAST_REF.RefCnt.bit_length()) - 1))
}
pub fn read_ansi_string_bytes(vmi: VmiState<Self>, va: Va) -> Result<Vec<u8>, VmiError> {
Self::read_ansi_string_bytes_in(vmi, vmi.access_context(va))
}
pub fn read_ansi_string32_bytes(vmi: VmiState<Self>, va: Va) -> Result<Vec<u8>, VmiError> {
Self::read_ansi_string32_bytes_in(vmi, vmi.access_context(va))
}
pub fn read_ansi_string64_bytes(vmi: VmiState<Self>, va: Va) -> Result<Vec<u8>, VmiError> {
Self::read_ansi_string64_bytes_in(vmi, vmi.access_context(va))
}
pub fn read_ansi_string(vmi: VmiState<Self>, va: Va) -> Result<String, VmiError> {
Self::read_ansi_string_in(vmi, vmi.access_context(va))
}
pub fn read_ansi_string32(vmi: VmiState<Self>, va: Va) -> Result<String, VmiError> {
Self::read_ansi_string32_in(vmi, vmi.access_context(va))
}
pub fn read_ansi_string64(vmi: VmiState<Self>, va: Va) -> Result<String, VmiError> {
Self::read_ansi_string64_in(vmi, vmi.access_context(va))
}
pub fn read_unicode_string_bytes(vmi: VmiState<Self>, va: Va) -> Result<Vec<u16>, VmiError> {
Self::read_unicode_string_bytes_in(vmi, vmi.access_context(va))
}
pub fn read_unicode_string32_bytes(vmi: VmiState<Self>, va: Va) -> Result<Vec<u16>, VmiError> {
Self::read_unicode_string32_bytes_in(vmi, vmi.access_context(va))
}
pub fn read_unicode_string64_bytes(vmi: VmiState<Self>, va: Va) -> Result<Vec<u16>, VmiError> {
Self::read_unicode_string64_bytes_in(vmi, vmi.access_context(va))
}
pub fn read_unicode_string(vmi: VmiState<Self>, va: Va) -> Result<String, VmiError> {
Self::read_unicode_string_in(vmi, vmi.access_context(va))
}
pub fn read_unicode_string32(vmi: VmiState<Self>, va: Va) -> Result<String, VmiError> {
Self::read_unicode_string32_in(vmi, vmi.access_context(va))
}
pub fn read_unicode_string64(vmi: VmiState<Self>, va: Va) -> Result<String, VmiError> {
Self::read_unicode_string64_in(vmi, vmi.access_context(va))
}
fn read_string32_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u8>, VmiError> {
let mut ctx = ctx.into();
let mut buffer = [0u8; 8];
vmi.read_in(ctx, &mut buffer)?;
let string_length = u16::from_le_bytes([buffer[0], buffer[1]]);
if string_length == 0 {
return Ok(Vec::new());
}
let string_buffer = u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]);
if string_buffer == 0 {
tracing::warn!(
addr = %Hex(ctx.address),
len = string_length,
"string buffer is NULL"
);
return Ok(Vec::new());
}
ctx.address = string_buffer as u64;
let mut buffer = vec![0u8; string_length as usize];
vmi.read_in(ctx, &mut buffer)?;
Ok(buffer)
}
fn read_string64_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u8>, VmiError> {
let mut ctx = ctx.into();
let mut buffer = [0u8; 16];
vmi.read_in(ctx, &mut buffer)?;
let string_length = u16::from_le_bytes([buffer[0], buffer[1]]);
if string_length == 0 {
return Ok(Vec::new());
}
#[rustfmt::skip]
let string_buffer = u64::from_le_bytes([
buffer[ 8], buffer[ 9], buffer[10], buffer[11],
buffer[12], buffer[13], buffer[14], buffer[15],
]);
if string_buffer == 0 {
tracing::warn!(
addr = %Hex(ctx.address),
len = string_length,
"string buffer is NULL"
);
return Ok(Vec::new());
}
ctx.address = string_buffer;
let mut buffer = vec![0u8; string_length as usize];
vmi.read_in(ctx, &mut buffer)?;
Ok(buffer)
}
pub fn read_ansi_string_bytes_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u8>, VmiError> {
match vmi.registers().address_width() {
4 => Self::read_ansi_string32_bytes_in(vmi, ctx),
8 => Self::read_ansi_string64_bytes_in(vmi, ctx),
_ => Err(VmiError::InvalidAddressWidth),
}
}
pub fn read_ansi_string32_bytes_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u8>, VmiError> {
Self::read_string32_in(vmi, ctx)
}
pub fn read_ansi_string64_bytes_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u8>, VmiError> {
Self::read_string64_in(vmi, ctx)
}
pub fn read_ansi_string_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError> {
match vmi.registers().address_width() {
4 => Self::read_ansi_string32_in(vmi, ctx),
8 => Self::read_ansi_string64_in(vmi, ctx),
_ => Err(VmiError::InvalidAddressWidth),
}
}
pub fn read_ansi_string32_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError> {
Ok(String::from_utf8_lossy(&Self::read_ansi_string32_bytes_in(vmi, ctx)?).into())
}
pub fn read_ansi_string64_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError> {
Ok(String::from_utf8_lossy(&Self::read_ansi_string64_bytes_in(vmi, ctx)?).into())
}
pub fn read_unicode_string_bytes_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError> {
match vmi.registers().address_width() {
4 => Self::read_unicode_string32_bytes_in(vmi, ctx),
8 => Self::read_unicode_string64_bytes_in(vmi, ctx),
_ => Err(VmiError::InvalidAddressWidth),
}
}
pub fn read_unicode_string32_bytes_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError> {
let buffer = Self::read_string32_in(vmi, ctx)?;
Ok(buffer
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect::<Vec<_>>())
}
pub fn read_unicode_string64_bytes_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<Vec<u16>, VmiError> {
let buffer = Self::read_string64_in(vmi, ctx)?;
Ok(buffer
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect::<Vec<_>>())
}
pub fn read_unicode_string_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError> {
match vmi.registers().address_width() {
4 => Self::read_unicode_string32_in(vmi, ctx),
8 => Self::read_unicode_string64_in(vmi, ctx),
_ => Err(VmiError::InvalidAddressWidth),
}
}
pub fn read_unicode_string32_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError> {
Ok(String::from_utf16_lossy(
&Self::read_unicode_string32_bytes_in(vmi, ctx)?,
))
}
pub fn read_unicode_string64_in(
vmi: VmiState<Self>,
ctx: impl Into<AccessContext>,
) -> Result<String, VmiError> {
Ok(String::from_utf16_lossy(
&Self::read_unicode_string64_bytes_in(vmi, ctx)?,
))
}
pub fn linked_list<'a>(
vmi: VmiState<'a, Self>,
list_head: Va,
offset: u64,
) -> Result<impl Iterator<Item = Result<Va, VmiError>> + use<'a, Driver>, VmiError> {
Ok(ListEntryIterator::new(vmi, list_head, offset))
}
fn modify_pfn_reference_count(
vmi: VmiState<Self>,
pfn: Gfn,
increment: i16,
) -> Result<Option<u16>, VmiError>
where
Driver: VmiWrite,
{
let MMPFN = offset!(vmi, _MMPFN);
const StandbyPageList: u16 = 2; const ModifiedPageList: u16 = 3;
const ModifiedNoWritePageList: u16 = 4;
const ActiveAndValid: u16 = 6;
let pfn = Self::pfn_database(vmi)? + u64::from(pfn) * MMPFN.len() as u64;
debug_assert_eq!(MMPFN.ReferenceCount.size(), 2);
debug_assert_eq!(
MMPFN.ReferenceCount.offset() + MMPFN.ReferenceCount.size(),
MMPFN.PageLocation.offset()
);
debug_assert_eq!(MMPFN.PageLocation.bit_position(), 0);
debug_assert_eq!(MMPFN.PageLocation.bit_length(), 3);
let pfn_value = vmi.read_u32(pfn + MMPFN.ReferenceCount.offset())?;
let flags = (pfn_value >> 16) as u16;
let ref_count = (pfn_value & 0xFFFF) as u16;
let page_location = flags & 7;
tracing::debug!(
%pfn,
ref_count,
flags = %Hex(flags),
page_location,
increment,
"modifying PFN reference count"
);
if !matches!(
page_location,
StandbyPageList | ModifiedPageList | ModifiedNoWritePageList | ActiveAndValid
) {
tracing::warn!(
%pfn,
ref_count,
flags = %Hex(flags),
page_location,
increment,
"page is not active and valid"
);
return Ok(None);
}
if ref_count == 0 {
tracing::warn!(
%pfn,
ref_count,
flags = %Hex(flags),
page_location,
increment,
"page is not initialized"
);
return Ok(None);
}
let new_ref_count = match ref_count.checked_add_signed(increment) {
Some(new_ref_count) => new_ref_count,
None => {
tracing::warn!(
%pfn,
ref_count,
flags = %Hex(flags),
page_location,
increment,
"page is at maximum reference count"
);
return Ok(None);
}
};
vmi.write_u16(pfn + MMPFN.ReferenceCount.offset(), new_ref_count)?;
Ok(Some(new_ref_count))
}
pub fn lock_pfn(vmi: VmiState<Self>, pfn: Gfn) -> Result<Option<u16>, VmiError>
where
Driver: VmiWrite,
{
Self::modify_pfn_reference_count(vmi, pfn, 1)
}
pub fn unlock_pfn(vmi: VmiState<Self>, pfn: Gfn) -> Result<Option<u16>, VmiError>
where
Driver: VmiWrite,
{
Self::modify_pfn_reference_count(vmi, pfn, -1)
}
}
#[expect(non_snake_case)]
impl<Driver> VmiOs for WindowsOs<Driver>
where
Driver: VmiRead,
Driver::Architecture: ArchAdapter<Driver>,
{
type Architecture = Driver::Architecture;
type Driver = Driver;
type Process<'a> = WindowsProcess<'a, Driver>;
type Thread<'a> = WindowsThread<'a, Driver>;
type Image<'a> = WindowsImage<'a, Driver>;
type Module<'a> = WindowsModule<'a, Driver>;
type UserModule<'a> = WindowsUserModule<'a, Driver>;
type Region<'a> = WindowsRegion<'a, Driver>;
type Mapped<'a> = WindowsControlArea<'a, Driver>;
fn kernel_image_base(vmi: VmiState<Self>) -> Result<Va, VmiError> {
Driver::Architecture::kernel_image_base(vmi)
}
fn kernel_information_string(vmi: VmiState<Self>) -> Result<String, VmiError> {
this!(vmi)
.nt_build_lab
.get_or_try_init(|| {
let NtBuildLab = symbol!(vmi, NtBuildLab);
let kernel_image_base = Self::kernel_image_base(vmi)?;
vmi.read_string(kernel_image_base + NtBuildLab)
})
.cloned()
}
fn kpti_enabled(vmi: VmiState<Self>) -> Result<bool, VmiError> {
this!(vmi)
.ki_kva_shadow
.get_or_try_init(|| {
let KiKvaShadow = symbol!(vmi, KiKvaShadow);
let KiKvaShadow = match KiKvaShadow {
Some(KiKvaShadow) => KiKvaShadow,
None => return Ok(false),
};
let kernel_image_base = Self::kernel_image_base(vmi)?;
Ok(vmi.read_u8(kernel_image_base + KiKvaShadow)? != 0)
})
.copied()
}
fn modules<'a>(
vmi: VmiState<'a, Self>,
) -> Result<impl Iterator<Item = Result<Self::Module<'a>, VmiError>> + use<'a, Driver>, VmiError>
{
let PsLoadedModuleList = Self::kernel_image_base(vmi)? + symbol!(vmi, PsLoadedModuleList);
let KLDR_DATA_TABLE_ENTRY = offset!(vmi, _KLDR_DATA_TABLE_ENTRY);
Ok(ListEntryIterator::new(
vmi,
PsLoadedModuleList,
KLDR_DATA_TABLE_ENTRY.InLoadOrderLinks.offset(),
)
.map(move |result| result.map(|entry| WindowsModule::new(vmi, entry))))
}
fn processes<'a>(
vmi: VmiState<'a, Self>,
) -> Result<impl Iterator<Item = Result<Self::Process<'a>, VmiError>> + use<'a, Driver>, VmiError>
{
let PsActiveProcessHead = Self::kernel_image_base(vmi)? + symbol!(vmi, PsActiveProcessHead);
let EPROCESS = offset!(vmi, _EPROCESS);
Ok(ListEntryIterator::new(
vmi,
PsActiveProcessHead,
EPROCESS.ActiveProcessLinks.offset(),
)
.map(move |result| result.map(|entry| WindowsProcess::new(vmi, ProcessObject(entry)))))
}
fn process(
vmi: VmiState<'_, Self>,
process: ProcessObject,
) -> Result<Self::Process<'_>, VmiError> {
Ok(WindowsProcess::new(vmi, process))
}
fn current_process(vmi: VmiState<'_, Self>) -> Result<Self::Process<'_>, VmiError> {
Self::current_thread(vmi)?.current_process()
}
fn system_process(vmi: VmiState<'_, Self>) -> Result<Self::Process<'_>, VmiError> {
let PsInitialSystemProcess =
Self::kernel_image_base(vmi)? + symbol!(vmi, PsInitialSystemProcess);
let process = vmi.read_va_native(PsInitialSystemProcess)?;
Ok(WindowsProcess::new(vmi, ProcessObject(process)))
}
fn thread(vmi: VmiState<'_, Self>, thread: ThreadObject) -> Result<Self::Thread<'_>, VmiError> {
Ok(WindowsThread::new(vmi, thread))
}
fn current_thread(vmi: VmiState<'_, Self>) -> Result<Self::Thread<'_>, VmiError> {
Self::current_kprcb(vmi)?.current_thread()
}
fn image(vmi: VmiState<'_, Self>, image_base: Va) -> Result<Self::Image<'_>, VmiError> {
Ok(WindowsImage::new(vmi, image_base))
}
fn module(vmi: VmiState<'_, Self>, module: Va) -> Result<Self::Module<'_>, VmiError> {
Ok(WindowsModule::new(vmi, module))
}
fn user_module(
vmi: VmiState<'_, Self>,
module: Va,
root: Pa,
) -> Result<Self::UserModule<'_>, VmiError> {
Ok(WindowsUserModule::new(vmi, module, root))
}
fn region(vmi: VmiState<'_, Self>, region: Va) -> Result<Self::Region<'_>, VmiError> {
Ok(WindowsRegion::new(vmi, region))
}
fn syscall_argument(vmi: VmiState<Self>, index: u64) -> Result<u64, VmiError> {
Driver::Architecture::syscall_argument(vmi, index)
}
fn function_argument(vmi: VmiState<Self>, index: u64) -> Result<u64, VmiError> {
Driver::Architecture::function_argument(vmi, index)
}
fn function_return_value(vmi: VmiState<Self>) -> Result<u64, VmiError> {
Driver::Architecture::function_return_value(vmi)
}
fn last_error(vmi: VmiState<Self>) -> Result<Option<u32>, VmiError> {
let KTHREAD = offset!(vmi, _KTHREAD);
let TEB = offset!(vmi, _TEB);
let current_thread = Self::current_thread(vmi)?.object()?;
let teb = vmi.read_va_native(current_thread.0 + KTHREAD.Teb.offset())?;
if teb.is_null() {
return Ok(None);
}
let result = vmi.read_u32(teb + TEB.LastErrorValue.offset())?;
Ok(Some(result))
}
}