#[doc(hidden)]
pub mod __private {
pub mod vmi_core {
pub use vmi_core::*;
}
pub mod zerocopy {
pub use zerocopy::*;
}
use vmi_core::{
Va, VmiError, VmiOs, VmiState,
os::{
VmiOsImage as _, VmiOsMapped as _, VmiOsModule as _, VmiOsProcess as _,
VmiOsRegion as _, VmiOsRegionKind,
},
};
use super::super::RecipeContext;
use crate::injector::recipe::SymbolCache;
#[derive(Debug)]
pub enum SearchKind {
Module,
Region,
}
pub fn find_module<Os>(vmi: &VmiState<'_, Os>, filename: &str) -> Result<Option<Va>, VmiError>
where
Os: VmiOs,
{
for module in vmi.os().modules()? {
let module = module?;
if module.name()?.eq_ignore_ascii_case(filename) {
return Ok(Some(module.base_address()?));
}
}
Ok(None)
}
pub fn find_region<Os>(vmi: &VmiState<'_, Os>, filename: &str) -> Result<Option<Va>, VmiError>
where
Os: VmiOs,
{
let current_process = vmi.os().current_process()?;
for region in current_process.regions()? {
let region = region?;
let mapped = match region.kind()? {
VmiOsRegionKind::MappedImage(mapped) => mapped,
_ => continue,
};
let path = match mapped.path() {
Ok(Some(path)) => path,
_ => continue,
};
if path.to_ascii_lowercase().ends_with(filename) {
return Ok(Some(region.start()?));
}
}
Ok(None)
}
pub fn exported_symbols<Os>(
vmi: &VmiState<'_, Os>,
filename: &str,
kind: SearchKind,
) -> Result<Option<SymbolCache>, VmiError>
where
Os: VmiOs,
{
let image_base = match kind {
SearchKind::Module => find_module(vmi, filename)?,
SearchKind::Region => find_region(vmi, filename)?,
};
let image_base = match image_base {
Some(image_base) => image_base,
None => return Ok(None),
};
let image = vmi.os().image(image_base)?;
let symbols = image.exports()?;
tracing::trace!(
va = %image_base,
symbols = symbols.len(),
"image found"
);
Ok(Some(
symbols
.into_iter()
.map(|symbol| (symbol.name, symbol.address))
.collect(),
))
}
#[tracing::instrument(skip(ctx), err)]
pub fn lookup_symbol<Os, T>(
ctx: &mut RecipeContext<'_, Os, T>,
filename: &str,
symbol: &str,
kind: SearchKind,
) -> Result<Option<Va>, VmiError>
where
Os: VmiOs,
{
use std::collections::hash_map::Entry;
match ctx.cache.entry(filename.to_owned()) {
Entry::Occupied(entry) => match entry.into_mut().get(symbol).copied() {
Some(va) => {
tracing::trace!(cache_hit = true, %va, "symbol found");
Ok(Some(va))
}
None => {
tracing::error!(cache_hit = true, "Symbol not found");
Ok(None)
}
},
Entry::Vacant(entry) => {
let symbols = match exported_symbols(ctx.vmi, filename, kind)? {
Some(symbols) => symbols,
None => {
tracing::error!(cache_hit = false, "Image not found");
return Ok(None);
}
};
let va = match symbols.get(symbol).copied() {
Some(va) => va,
None => {
tracing::error!(cache_hit = false, "Symbol not found");
return Ok(None);
}
};
tracing::trace!(cache_hit = false, %va, "symbol found");
entry.insert(symbols);
Ok(Some(va))
}
}
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! _private_recipe {
[
$recipe:expr,
$( { $($step:tt)* } ),* $(,)?
] => {
$recipe
$(
.step($crate::_private_recipe! { @step $($step)* })
)*
};
(@expand $($body:tt)*) => {
macro_rules! __with_dollar_sign { $($body)* }
__with_dollar_sign!($);
};
(@step $($body:tt)*) => {
move |ctx| {
$crate::_private_recipe! { @expand
($d:tt) => {
#[expect(unused_macros)]
macro_rules! vmi {
() => {
ctx.vmi
};
}
#[expect(unused_macros)]
macro_rules! registers {
() => {
ctx.registers
};
}
#[expect(unused_macros)]
macro_rules! data {
($d($d name:tt)*) => {
ctx.data.$d($d name)*
};
}
#[expect(unused_macros)]
macro_rules! inject {
($image:ident!$function:ident($d($d arg:expr),*)) => {
$crate::_private_recipe!(@inject ctx, $image!$function($d($d arg),*))
};
($function:ident($d($d arg:expr),*)) => {
$crate::_private_recipe!(@inject ctx, $function($d($d arg),*))
};
}
#[expect(unused_macros)]
macro_rules! copy_to_stack {
($d($d name:tt)*) => {{
use $crate::injector::{
macros::__private::{
vmi_core::{Architecture, Va, VmiCore, VmiDriver, VmiError},
zerocopy::{Immutable, IntoBytes},
},
ArchAdapter as _,
};
fn __copy_to_stack<Driver, T>(
vmi: &VmiCore<Driver>,
registers: &mut <Driver::Architecture as Architecture>::Registers,
data: T,
) -> Result<Va, VmiError>
where
Driver: VmiDriver,
Driver::Architecture: vmi::utils::injector::ArchAdapter<Driver>,
T: IntoBytes + Immutable,
{
Driver::Architecture::copy_to_stack(vmi, registers, data)
}
__copy_to_stack(vmi!(), registers!(), $d($d name)*)
}};
}
}
}
$($body)*
}
};
(@inject $ctx:expr, nt!$function:ident($($arg:expr),*)) => {
'm: {
use $crate::injector::macros::__private::{self, vmi_core::VmiError};
let function = match __private::lookup_symbol(
$ctx,
"ntoskrnl.exe",
stringify!($function),
__private::SearchKind::Module,
) {
Ok(Some(function)) => function,
Ok(None) => break 'm Err(VmiError::Other(concat!(stringify!($function), " not found"))),
Err(err) => break 'm Err(err),
};
tracing::trace!(
function = stringify!($function),
$(arg = ?$arg,)*
"preparing function call"
);
$crate::_private_recipe!(@inject $ctx, function($($arg),*))
}
};
(@inject $ctx:expr, $image:ident!$function:ident($($arg:expr),*)) => {
'm: {
use $crate::injector::macros::__private::{self, vmi_core::VmiError};
let function = match __private::lookup_symbol(
$ctx,
concat!(stringify!($image), ".dll"),
stringify!($function),
__private::SearchKind::Region,
) {
Ok(Some(function)) => function,
Ok(None) => break 'm Err(VmiError::Other(concat!(stringify!($function), " not found"))),
Err(err) => break 'm Err(err),
};
tracing::trace!(
function = stringify!($function),
$(arg = ?$arg,)*
"preparing function call"
);
$crate::_private_recipe!(@inject $ctx, function($($arg),*))
}
};
(@inject $ctx:expr, $function:ident($($arg:expr),*)) => {
'm: {
use $crate::injector::{
CallBuilder, OsAdapter as _, RecipeControlFlow,
macros::__private::{self, vmi_core::VmiError}
};
let call = CallBuilder::new($function)
$(.with_argument(&$arg))*;
if let Err(err) = $ctx.vmi.underlying_os().prepare_function_call($ctx.vmi, $ctx.registers, call) {
break 'm Err(err);
}
Ok(RecipeControlFlow::Continue)
}
};
}