use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
tokens, EmValue,
},
metadata::{tables::ModuleRaw, typesystem::CilFlavor},
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.Diagnostics.Process.GetCurrentProcess")
.match_name("System.Diagnostics", "Process", "GetCurrentProcess")
.pre(process_get_current_process_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.Process.get_MainModule")
.match_name("System.Diagnostics", "Process", "get_MainModule")
.pre(process_get_main_module_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.Process.get_Modules")
.match_name("System.Diagnostics", "Process", "get_Modules")
.pre(process_get_modules_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.ProcessModule.get_FileName")
.match_name("System.Diagnostics", "ProcessModule", "get_FileName")
.pre(process_module_get_file_name_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.ProcessModule.get_BaseAddress")
.match_name("System.Diagnostics", "ProcessModule", "get_BaseAddress")
.pre(process_module_get_base_address_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.ProcessModule.get_ModuleMemorySize")
.match_name(
"System.Diagnostics",
"ProcessModule",
"get_ModuleMemorySize",
)
.pre(process_module_get_module_memory_size_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.ProcessModule.get_ModuleName")
.match_name("System.Diagnostics", "ProcessModule", "get_ModuleName")
.pre(process_module_get_module_name_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.Process.get_Id")
.match_name("System.Diagnostics", "Process", "get_Id")
.pre(process_get_id_pre),
)?;
Ok(())
}
fn process_get_current_process_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let module_name = thread
.assembly()
.and_then(|asm| {
let tables = asm.tables()?;
let strings = asm.strings()?;
let module_table = tables.table::<ModuleRaw>()?;
let module_row = module_table.iter().next()?;
strings.get(module_row.name as usize).ok().map(String::from)
})
.unwrap_or_else(|| "module.exe".to_string());
let (image_base, size_of_image) = thread
.assembly()
.map(|asm| {
let file = asm.file();
#[allow(clippy::cast_possible_wrap)]
let base = file.imagebase() as i64;
#[allow(clippy::cast_possible_wrap)]
let size = file
.pe()
.optional_header
.as_ref()
.map_or(0, |oh| oh.windows_fields.size_of_image as i32);
(base, size)
})
.unwrap_or((
crate::emulation::tokens::native_addresses::CURRENT_MODULE,
0,
));
let base = &thread.config().environment.assembly_location_base;
let path = format!("{base}\\{module_name}");
let filename_ref = try_hook!(thread.heap_mut().alloc_string(&path));
let modname_ref = try_hook!(thread.heap_mut().alloc_string(&module_name));
let pm_ref = try_hook!(thread
.heap_mut()
.alloc_object(tokens::system::PROCESS_MODULE));
try_hook!(thread.heap_mut().set_field(
pm_ref,
tokens::process_fields::FILE_NAME,
EmValue::ObjectRef(filename_ref),
));
try_hook!(thread.heap_mut().set_field(
pm_ref,
tokens::process_fields::BASE_ADDRESS,
EmValue::NativeInt(image_base),
));
try_hook!(thread.heap_mut().set_field(
pm_ref,
tokens::process_fields::MODULE_MEMORY_SIZE,
EmValue::I32(size_of_image),
));
try_hook!(thread.heap_mut().set_field(
pm_ref,
tokens::process_fields::MODULE_NAME,
EmValue::ObjectRef(modname_ref),
));
let proc_ref = try_hook!(thread.heap_mut().alloc_object(tokens::system::PROCESS));
try_hook!(thread.heap_mut().set_field(
proc_ref,
tokens::process_fields::MAIN_MODULE,
EmValue::ObjectRef(pm_ref),
));
PreHookResult::Bypass(Some(EmValue::ObjectRef(proc_ref)))
}
fn process_get_main_module_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(proc_ref)) = ctx.this {
if let Ok(val) = thread
.heap()
.get_field(*proc_ref, tokens::process_fields::MAIN_MODULE)
{
return PreHookResult::Bypass(Some(val));
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn process_get_modules_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(proc_ref)) = ctx.this {
if let Ok(main_module) = thread
.heap()
.get_field(*proc_ref, tokens::process_fields::MAIN_MODULE)
{
let array_ref = try_hook!(thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, vec![main_module]));
return PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref)));
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn process_module_get_file_name_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(pm_ref)) = ctx.this {
if let Ok(val) = thread
.heap()
.get_field(*pm_ref, tokens::process_fields::FILE_NAME)
{
return PreHookResult::Bypass(Some(val));
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn process_module_get_base_address_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(pm_ref)) = ctx.this {
if let Ok(val) = thread
.heap()
.get_field(*pm_ref, tokens::process_fields::BASE_ADDRESS)
{
return PreHookResult::Bypass(Some(val));
}
}
let image_base = thread.assembly().map_or(
crate::emulation::tokens::native_addresses::CURRENT_MODULE as u64,
|asm| asm.file().imagebase(),
);
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::NativeInt(image_base as i64)))
}
fn process_module_get_module_memory_size_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(pm_ref)) = ctx.this {
if let Ok(val) = thread
.heap()
.get_field(*pm_ref, tokens::process_fields::MODULE_MEMORY_SIZE)
{
return PreHookResult::Bypass(Some(val));
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn process_module_get_module_name_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(pm_ref)) = ctx.this {
if let Ok(val) = thread
.heap()
.get_field(*pm_ref, tokens::process_fields::MODULE_NAME)
{
return PreHookResult::Bypass(Some(val));
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn process_get_id_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(1234)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::hook::HookManager,
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
#[test]
fn test_register_hooks() {
let manager = HookManager::new();
register(&manager).unwrap();
assert_eq!(manager.len(), 8);
}
#[test]
fn test_process_chain() {
let mut thread = create_test_thread();
let args: [EmValue; 0] = [];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Diagnostics",
"Process",
"GetCurrentProcess",
PointerSize::Bit64,
)
.with_args(&args);
let result = process_get_current_process_pre(&ctx, &mut thread);
let proc_ref = match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => r,
other => panic!("Expected Bypass with ObjectRef, got {other:?}"),
};
let this = EmValue::ObjectRef(proc_ref);
let ctx2 = HookContext::new(
Token::new(0x0A000002),
"System.Diagnostics",
"Process",
"get_MainModule",
PointerSize::Bit64,
)
.with_this(Some(&this));
let result2 = process_get_main_module_pre(&ctx2, &mut thread);
let pm_ref = match result2 {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => r,
other => panic!("Expected Bypass with ObjectRef, got {other:?}"),
};
let this2 = EmValue::ObjectRef(pm_ref);
let ctx3 = HookContext::new(
Token::new(0x0A000003),
"System.Diagnostics",
"ProcessModule",
"get_FileName",
PointerSize::Bit64,
)
.with_this(Some(&this2));
let result3 = process_module_get_file_name_pre(&ctx3, &mut thread);
match result3 {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => {
let s = thread.heap().get_string(r).unwrap();
assert!(
s.contains("module.exe"),
"Expected path with module.exe, got: {s}"
);
}
other => panic!("Expected Bypass with ObjectRef, got {other:?}"),
}
let ctx4 = HookContext::new(
Token::new(0x0A000004),
"System.Diagnostics",
"ProcessModule",
"get_BaseAddress",
PointerSize::Bit64,
)
.with_this(Some(&this2));
let result4 = process_module_get_base_address_pre(&ctx4, &mut thread);
match result4 {
PreHookResult::Bypass(Some(EmValue::NativeInt(addr))) => {
assert!(addr > 0, "BaseAddress should be non-zero, got {addr}");
}
other => panic!("Expected Bypass with NativeInt, got {other:?}"),
}
}
}