use crate::emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
};
pub fn register(manager: &mut HookManager) {
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.GetHINSTANCE")
.match_name("System.Runtime.InteropServices", "Marshal", "GetHINSTANCE")
.pre(marshal_get_hinstance_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.Copy")
.match_name("System.Runtime.InteropServices", "Marshal", "Copy")
.pre(marshal_copy_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.ReadByte")
.match_name("System.Runtime.InteropServices", "Marshal", "ReadByte")
.pre(marshal_read_byte_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.ReadInt32")
.match_name("System.Runtime.InteropServices", "Marshal", "ReadInt32")
.pre(marshal_read_int32_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.WriteByte")
.match_name("System.Runtime.InteropServices", "Marshal", "WriteByte")
.pre(marshal_write_byte_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.WriteInt32")
.match_name("System.Runtime.InteropServices", "Marshal", "WriteInt32")
.pre(marshal_write_int32_pre),
);
manager.register(
Hook::new("System.IntPtr.op_Explicit")
.match_name("System", "IntPtr", "op_Explicit")
.pre(intptr_op_explicit_pre),
);
manager.register(
Hook::new("System.IntPtr.Add")
.match_name("System", "IntPtr", "Add")
.pre(intptr_add_pre),
);
manager.register(
Hook::new("System.IntPtr.ToInt32")
.match_name("System", "IntPtr", "ToInt32")
.pre(intptr_to_int32_pre),
);
manager.register(
Hook::new("System.IntPtr.ToInt64")
.match_name("System", "IntPtr", "ToInt64")
.pre(intptr_to_int64_pre),
);
manager.register(
Hook::new("System.UIntPtr.op_Explicit")
.match_name("System", "UIntPtr", "op_Explicit")
.pre(uintptr_op_explicit_pre),
);
}
fn marshal_get_hinstance_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let image_base = thread
.assembly()
.map_or(0x0040_0000, |asm| asm.file().imagebase());
#[allow(clippy::cast_possible_wrap)]
PreHookResult::Bypass(Some(EmValue::NativeInt(image_base as i64)))
}
fn marshal_copy_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 4 {
return PreHookResult::Bypass(None);
}
let src_addr = match &ctx.args[0] {
EmValue::UnmanagedPtr(a) => Some(*a),
EmValue::NativeInt(a) => Some((*a).cast_unsigned()),
_ => None,
};
if let Some(src_addr) = src_addr {
let dst_ref = match &ctx.args[1] {
EmValue::ObjectRef(r) => *r,
_ => return PreHookResult::Bypass(None),
};
let start_idx = match &ctx.args[2] {
EmValue::I32(v) => (*v).cast_unsigned() as usize,
_ => return PreHookResult::Bypass(None),
};
let length = match &ctx.args[3] {
EmValue::I32(v) => (*v).cast_unsigned() as usize,
_ => return PreHookResult::Bypass(None),
};
if let Ok(bytes) = thread.address_space().read(src_addr, length) {
for (i, &byte) in bytes.iter().enumerate() {
let _ = thread.heap_mut().set_array_element(
dst_ref,
start_idx + i,
EmValue::I32(i32::from(byte)),
);
}
}
return PreHookResult::Bypass(None);
}
let EmValue::ObjectRef(src_ref) = &ctx.args[0] else {
return PreHookResult::Bypass(None);
};
let start_idx = match &ctx.args[1] {
EmValue::I32(v) => (*v).cast_unsigned() as usize,
_ => return PreHookResult::Bypass(None),
};
let dest_addr = match &ctx.args[2] {
EmValue::UnmanagedPtr(a) => *a,
EmValue::NativeInt(a) => (*a).cast_unsigned(),
_ => return PreHookResult::Bypass(None),
};
let length = match &ctx.args[3] {
EmValue::I32(v) => (*v).cast_unsigned() as usize,
_ => return PreHookResult::Bypass(None),
};
let mut bytes = Vec::with_capacity(length);
for i in 0..length {
#[allow(clippy::cast_possible_truncation)]
let byte_val = thread
.heap()
.get_array_element(*src_ref, start_idx + i)
.map(|elem| match elem {
EmValue::I32(v) => v.cast_unsigned() as u8,
_ => 0,
})
.unwrap_or(0);
bytes.push(byte_val);
}
let _ = thread.address_space().write(dest_addr, &bytes);
PreHookResult::Bypass(None)
}
fn marshal_read_byte_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let addr = match ctx.args.first() {
Some(EmValue::UnmanagedPtr(a)) => *a,
Some(EmValue::NativeInt(a)) => (*a).cast_unsigned(),
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
if let Ok(bytes) = thread.address_space().read(addr, 1) {
PreHookResult::Bypass(Some(EmValue::I32(i32::from(bytes[0]))))
} else {
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
}
fn marshal_read_int32_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let addr = match ctx.args.first() {
Some(EmValue::UnmanagedPtr(a)) => *a,
Some(EmValue::NativeInt(a)) => (*a).cast_unsigned(),
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
if let Ok(bytes) = thread.address_space().read(addr, 4) {
let value = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
PreHookResult::Bypass(Some(EmValue::I32(value)))
} else {
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
}
fn marshal_write_byte_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(None);
}
let addr = match &ctx.args[0] {
EmValue::UnmanagedPtr(a) => *a,
EmValue::NativeInt(a) => (*a).cast_unsigned(),
_ => return PreHookResult::Bypass(None),
};
#[allow(clippy::cast_possible_truncation)]
let value = match &ctx.args[1] {
EmValue::I32(v) => (*v).cast_unsigned() as u8,
_ => return PreHookResult::Bypass(None),
};
let _ = thread.address_space().write(addr, &[value]);
PreHookResult::Bypass(None)
}
fn marshal_write_int32_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(None);
}
let addr = match &ctx.args[0] {
EmValue::UnmanagedPtr(a) => *a,
EmValue::NativeInt(a) => (*a).cast_unsigned(),
_ => return PreHookResult::Bypass(None),
};
let value = match &ctx.args[1] {
EmValue::I32(v) => *v,
_ => return PreHookResult::Bypass(None),
};
let _ = thread.address_space().write(addr, &value.to_le_bytes());
PreHookResult::Bypass(None)
}
fn intptr_op_explicit_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let result = if let Some(arg) = ctx.args.first() {
match arg {
EmValue::NativeInt(v) | EmValue::I64(v) => EmValue::NativeInt(*v),
EmValue::NativeUInt(v) | EmValue::UnmanagedPtr(v) => {
EmValue::NativeInt((*v).cast_signed())
}
EmValue::I32(v) => EmValue::NativeInt(i64::from(*v)),
_ => arg.clone(),
}
} else {
EmValue::NativeInt(0)
};
PreHookResult::Bypass(Some(result))
}
fn intptr_add_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::NativeInt(0)));
}
let ptr = match &ctx.args[0] {
EmValue::NativeInt(v) => *v,
EmValue::UnmanagedPtr(v) => (*v).cast_signed(),
_ => return PreHookResult::Bypass(Some(EmValue::NativeInt(0))),
};
let offset = match &ctx.args[1] {
EmValue::I32(v) => i64::from(*v),
EmValue::I64(v) => *v,
_ => return PreHookResult::Bypass(Some(EmValue::NativeInt(ptr))),
};
PreHookResult::Bypass(Some(EmValue::NativeInt(ptr.wrapping_add(offset))))
}
fn intptr_to_int32_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
#[allow(clippy::cast_possible_truncation)]
let value = match ctx.this {
Some(EmValue::NativeInt(v)) => *v as i32,
Some(EmValue::UnmanagedPtr(v)) => *v as i32,
_ => 0,
};
PreHookResult::Bypass(Some(EmValue::I32(value)))
}
fn intptr_to_int64_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let value = match ctx.this {
Some(EmValue::NativeInt(v)) => *v,
Some(EmValue::UnmanagedPtr(v)) => (*v).cast_signed(),
_ => 0,
};
PreHookResult::Bypass(Some(EmValue::I64(value)))
}
fn uintptr_op_explicit_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let result = if let Some(arg) = ctx.args.first() {
match arg {
EmValue::NativeUInt(v) | EmValue::UnmanagedPtr(v) => EmValue::NativeUInt(*v),
EmValue::NativeInt(v) | EmValue::I64(v) => EmValue::NativeUInt((*v).cast_unsigned()),
EmValue::I32(v) => EmValue::NativeUInt((*v).cast_unsigned().into()),
_ => arg.clone(),
}
} else {
EmValue::NativeUInt(0)
};
PreHookResult::Bypass(Some(result))
}
#[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 mut manager = HookManager::new();
register(&mut manager);
assert_eq!(manager.len(), 11);
}
#[test]
fn test_gethinstance_hook() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Runtime.InteropServices",
"Marshal",
"GetHINSTANCE",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = marshal_get_hinstance_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::NativeInt(v))) => {
assert_eq!(v, 0x0040_0000);
}
_ => panic!("Expected Bypass with NativeInt"),
}
}
#[test]
fn test_intptr_op_explicit_hook() {
let args = [EmValue::I32(42)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"op_Explicit",
PointerSize::Bit64,
)
.with_args(&args);
let mut thread = create_test_thread();
let result = intptr_op_explicit_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::NativeInt(v))) => assert_eq!(v, 42),
_ => panic!("Expected Bypass with NativeInt(42)"),
}
}
#[test]
fn test_intptr_add_hook() {
let args = [EmValue::NativeInt(0x1000), EmValue::I32(0x100)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"Add",
PointerSize::Bit64,
)
.with_args(&args);
let mut thread = create_test_thread();
let result = intptr_add_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::NativeInt(v))) => assert_eq!(v, 0x1100),
_ => panic!("Expected Bypass with NativeInt(0x1100)"),
}
}
#[test]
fn test_intptr_to_int32_hook() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"ToInt32",
PointerSize::Bit64,
)
.with_this(Some(&EmValue::NativeInt(42)));
let mut thread = create_test_thread();
let result = intptr_to_int32_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::I32(v))) => assert_eq!(v, 42),
_ => panic!("Expected Bypass with I32(42)"),
}
}
}