use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
tokens, EmValue, HeapObject,
},
metadata::token::Token,
Result,
};
fn resolve_address(arg: &EmValue, thread: &EmulationThread) -> Option<u64> {
match arg {
EmValue::UnmanagedPtr(a) => Some(*a),
EmValue::NativeInt(a) => Some(a.cast_unsigned()),
EmValue::NativeUInt(a) => Some(*a),
EmValue::I32(a) => Some(a.cast_unsigned() as u64),
EmValue::ObjectRef(href) => {
match thread.heap().get(*href) {
Ok(HeapObject::BoxedValue { value, .. }) => match value.as_ref() {
EmValue::NativeInt(a) => Some(a.cast_unsigned()),
EmValue::NativeUInt(a) => Some(*a),
EmValue::I32(a) => Some(a.cast_unsigned() as u64),
EmValue::I64(a) => Some(a.cast_unsigned()),
_ => None,
},
Ok(HeapObject::Object { fields, .. }) => {
let intptr_field = Token::new(0x04FF_FF01);
fields.get(&intptr_field).and_then(|v| match v {
EmValue::NativeInt(a) => Some(a.cast_unsigned()),
EmValue::NativeUInt(a) => Some(*a),
EmValue::I32(a) => Some(a.cast_unsigned() as u64),
EmValue::I64(a) => Some(a.cast_unsigned()),
_ => None,
})
}
_ => None,
}
}
_ => None,
}
}
fn resolve_managed_ptr_as_i64(
ptr: &crate::emulation::value::ManagedPointer,
thread: &EmulationThread,
) -> Option<i64> {
use crate::emulation::value::PointerTarget;
let value = match &ptr.target {
PointerTarget::Local(idx) => thread.get_local(*idx as usize).ok().cloned(),
PointerTarget::StaticField(token) => {
thread.address_space().statics().get(*token).ok().flatten()
}
_ => None,
}?;
match value {
EmValue::NativeInt(n) => Some(n),
EmValue::NativeUInt(n) => Some(n.cast_signed()),
EmValue::I32(n) => Some(i64::from(n)),
EmValue::I64(n) => Some(n),
_ => None,
}
}
pub fn register(manager: &HookManager) -> Result<()> {
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.Runtime.InteropServices.Marshal.ReadInt64")
.match_name("System.Runtime.InteropServices", "Marshal", "ReadInt64")
.pre(marshal_read_int64_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.ReadInt16")
.match_name("System.Runtime.InteropServices", "Marshal", "ReadInt16")
.pre(marshal_read_int16_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.ReadIntPtr")
.match_name("System.Runtime.InteropServices", "Marshal", "ReadIntPtr")
.pre(marshal_read_intptr_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.WriteInt64")
.match_name("System.Runtime.InteropServices", "Marshal", "WriteInt64")
.pre(marshal_write_int64_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.WriteInt16")
.match_name("System.Runtime.InteropServices", "Marshal", "WriteInt16")
.pre(marshal_write_int16_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.WriteIntPtr")
.match_name("System.Runtime.InteropServices", "Marshal", "WriteIntPtr")
.pre(marshal_write_intptr_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.AllocCoTaskMem")
.match_name(
"System.Runtime.InteropServices",
"Marshal",
"AllocCoTaskMem",
)
.pre(marshal_alloc_cotaskmem_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.FreeCoTaskMem")
.match_name("System.Runtime.InteropServices", "Marshal", "FreeCoTaskMem")
.pre(marshal_free_cotaskmem_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.AllocHGlobal")
.match_name("System.Runtime.InteropServices", "Marshal", "AllocHGlobal")
.pre(marshal_alloc_hglobal_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.FreeHGlobal")
.match_name("System.Runtime.InteropServices", "Marshal", "FreeHGlobal")
.pre(marshal_free_hglobal_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.SizeOf")
.match_name("System.Runtime.InteropServices", "Marshal", "SizeOf")
.pre(marshal_sizeof_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer")
.match_name(
"System.Runtime.InteropServices",
"Marshal",
"GetDelegateForFunctionPointer",
)
.pre(marshal_get_delegate_for_function_pointer_pre),
)?;
manager.register(
Hook::new("System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate")
.match_name(
"System.Runtime.InteropServices",
"Marshal",
"GetFunctionPointerForDelegate",
)
.pre(marshal_get_function_pointer_for_delegate_pre),
)?;
manager.register(
Hook::new("System.IntPtr..ctor")
.match_name("System", "IntPtr", ".ctor")
.pre(intptr_ctor_pre),
)?;
manager.register(
Hook::new("System.IntPtr.get_Size")
.match_name("System", "IntPtr", "get_Size")
.pre(intptr_get_size_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.IntPtr.op_Equality")
.match_name("System", "IntPtr", "op_Equality")
.pre(intptr_op_equality_pre),
)?;
manager.register(
Hook::new("System.IntPtr.op_Inequality")
.match_name("System", "IntPtr", "op_Inequality")
.pre(intptr_op_inequality_pre),
)?;
manager.register(
Hook::new("System.IntPtr.get_Zero")
.match_name("System", "IntPtr", "get_Zero")
.pre(intptr_get_zero_pre),
)?;
manager.register(
Hook::new("System.UIntPtr.op_Explicit")
.match_name("System", "UIntPtr", "op_Explicit")
.pre(uintptr_op_explicit_pre),
)?;
Ok(())
}
fn write_with_auto_alloc(thread: &EmulationThread, addr: u64, data: &[u8]) {
if thread.address_space().write(addr, data).is_err() {
let page_base = addr & !0xFFFF;
let page_size = 0x1_0000usize; log::debug!(
"Auto-allocating 0x{page_size:X} bytes at 0x{page_base:X} for write to 0x{addr:X}"
);
if thread
.address_space()
.map_data(page_base, &vec![0u8; page_size], "auto-alloc")
.is_ok()
{
let _ = thread.address_space().write(addr, data);
}
}
}
fn marshal_get_hinstance_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let image_base = thread
.assembly()
.map_or(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 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() {
try_hook!(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);
}
write_with_auto_alloc(thread, dest_addr, &bytes);
PreHookResult::Bypass(None)
}
fn marshal_read_byte_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(addr) = ctx.args.first().and_then(|a| resolve_address(a, thread)) else {
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),
};
write_with_auto_alloc(thread, 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 Some(addr) = resolve_address(&ctx.args[0], thread) else {
return PreHookResult::Bypass(None);
};
let value = match &ctx.args[1] {
EmValue::I32(v) => *v,
_ => return PreHookResult::Bypass(None),
};
write_with_auto_alloc(thread, addr, &value.to_le_bytes());
PreHookResult::Bypass(None)
}
fn marshal_read_int64_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(base_addr) = ctx.args.first().and_then(|a| resolve_address(a, thread)) else {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
};
let offset = ctx
.args
.get(1)
.and_then(EmValue::as_i32)
.map(i64::from)
.unwrap_or(0);
let addr = (base_addr as i64).wrapping_add(offset).cast_unsigned();
if let Ok(bytes) = thread.address_space().read(addr, 8) {
let value = i64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]);
PreHookResult::Bypass(Some(EmValue::I64(value)))
} else {
PreHookResult::Bypass(Some(EmValue::I64(0)))
}
}
fn marshal_read_int16_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let base_addr = match ctx.args.first() {
Some(EmValue::UnmanagedPtr(a)) => *a,
Some(EmValue::NativeInt(a)) => (*a).cast_unsigned(),
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let offset = ctx
.args
.get(1)
.and_then(EmValue::as_i32)
.map(i64::from)
.unwrap_or(0);
let addr = (base_addr as i64).wrapping_add(offset).cast_unsigned();
if let Ok(bytes) = thread.address_space().read(addr, 2) {
let value = i16::from_le_bytes([bytes[0], bytes[1]]);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(value))))
} else {
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
}
fn marshal_read_intptr_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let base_addr = match ctx.args.first() {
Some(EmValue::UnmanagedPtr(a)) => *a,
Some(EmValue::NativeInt(a)) => (*a).cast_unsigned(),
_ => return PreHookResult::Bypass(Some(EmValue::NativeInt(0))),
};
let offset = ctx
.args
.get(1)
.and_then(EmValue::as_i32)
.map(i64::from)
.unwrap_or(0);
let addr = (base_addr as i64).wrapping_add(offset).cast_unsigned();
let ptr_size = ctx.pointer_size.bytes();
if let Ok(bytes) = thread.address_space().read(addr, ptr_size) {
let value = if ptr_size == 8 {
i64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
} else {
i64::from(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
};
PreHookResult::Bypass(Some(EmValue::NativeInt(value)))
} else {
PreHookResult::Bypass(Some(EmValue::NativeInt(0)))
}
}
fn marshal_write_int64_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(None);
}
let Some(addr) = resolve_address(&ctx.args[0], thread) else {
return PreHookResult::Bypass(None);
};
let (offset, value) = if ctx.args.len() >= 3 {
let ofs = ctx.args[1].as_i32().map(i64::from).unwrap_or(0);
let val = match &ctx.args[2] {
EmValue::I64(v) | EmValue::NativeInt(v) => *v,
_ => return PreHookResult::Bypass(None),
};
(ofs, val)
} else {
let val = match &ctx.args[1] {
EmValue::I64(v) | EmValue::NativeInt(v) => *v,
_ => return PreHookResult::Bypass(None),
};
(0, val)
};
let final_addr = (addr as i64).wrapping_add(offset).cast_unsigned();
write_with_auto_alloc(thread, final_addr, &value.to_le_bytes());
PreHookResult::Bypass(None)
}
fn marshal_write_int16_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(None);
}
let Some(addr) = resolve_address(&ctx.args[0], thread) else {
return PreHookResult::Bypass(None);
};
let (offset, value_arg) = if ctx.args.len() >= 3 {
let off = match &ctx.args[1] {
EmValue::I32(v) => i64::from(*v),
_ => 0,
};
(off, &ctx.args[2])
} else {
(0i64, &ctx.args[1])
};
#[allow(clippy::cast_possible_truncation)]
let value = match value_arg {
EmValue::I32(v) => *v as i16,
_ => return PreHookResult::Bypass(None),
};
let final_addr = (addr as i64).wrapping_add(offset).cast_unsigned();
write_with_auto_alloc(thread, final_addr, &value.to_le_bytes());
PreHookResult::Bypass(None)
}
fn marshal_write_intptr_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(None);
}
let Some(addr) = resolve_address(&ctx.args[0], thread) else {
return PreHookResult::Bypass(None);
};
let (offset, value) = if ctx.args.len() >= 3 {
let ofs = ctx.args[1].as_i32().map(i64::from).unwrap_or(0);
let val = match &ctx.args[2] {
EmValue::NativeInt(v) | EmValue::I64(v) => *v,
EmValue::NativeUInt(v) | EmValue::UnmanagedPtr(v) => (*v).cast_signed(),
EmValue::I32(v) => i64::from(*v),
_ => return PreHookResult::Bypass(None),
};
(ofs, val)
} else {
let val = match &ctx.args[1] {
EmValue::NativeInt(v) | EmValue::I64(v) => *v,
EmValue::NativeUInt(v) | EmValue::UnmanagedPtr(v) => (*v).cast_signed(),
EmValue::I32(v) => i64::from(*v),
_ => return PreHookResult::Bypass(None),
};
(0, val)
};
let final_addr = (addr as i64).wrapping_add(offset).cast_unsigned();
let ptr_size = ctx.pointer_size.bytes();
if ptr_size == 8 {
write_with_auto_alloc(thread, final_addr, &value.to_le_bytes());
} else {
#[allow(clippy::cast_possible_truncation)]
let val32 = value as i32;
write_with_auto_alloc(thread, final_addr, &val32.to_le_bytes());
}
PreHookResult::Bypass(None)
}
fn marshal_alloc_cotaskmem_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let size = ctx
.args
.first()
.and_then(EmValue::as_i32)
.unwrap_or(0)
.max(0) as usize;
let size = size.max(1); match thread.address_space().alloc_unmanaged(size) {
Ok(addr) => PreHookResult::Bypass(Some(EmValue::NativeInt(addr as i64))),
Err(_) => PreHookResult::Bypass(Some(EmValue::NativeInt(0))),
}
}
fn marshal_free_cotaskmem_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn marshal_alloc_hglobal_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let size = match ctx.args.first() {
Some(EmValue::I32(v)) => (*v).max(0) as usize,
Some(EmValue::NativeInt(v)) => (*v).max(0) as usize,
_ => 0,
};
let size = size.max(1);
match thread.address_space().alloc_unmanaged(size) {
Ok(addr) => PreHookResult::Bypass(Some(EmValue::NativeInt(addr as i64))),
Err(_) => PreHookResult::Bypass(Some(EmValue::NativeInt(0))),
}
}
fn marshal_free_hglobal_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn marshal_sizeof_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let ptr_size = ctx.pointer_size;
let type_token = ctx.args.first().and_then(|arg| {
if let EmValue::ObjectRef(href) = arg {
thread
.heap()
.get_reflection_type_token(*href)
.ok()
.flatten()
} else {
None
}
});
let size = if let Some(token) = type_token {
thread
.assembly()
.and_then(|asm| asm.types().get(&token))
.and_then(|ty| ty.flavor().byte_size(ptr_size))
.unwrap_or(ptr_size.bytes())
} else {
ptr_size.bytes()
};
PreHookResult::Bypass(Some(EmValue::I32(size as i32)))
}
fn marshal_get_delegate_for_function_pointer_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let type_token = if let Some(EmValue::ObjectRef(type_ref)) = ctx.args.get(1) {
thread
.heap()
.get_reflection_type_token(*type_ref)
.unwrap_or(None)
} else {
None
};
let delegate_type = type_token.unwrap_or(Token::new(0x0200_0001));
let func_addr = ctx.args.first().and_then(|v| match v {
EmValue::NativeInt(a) => Some(*a as u64),
EmValue::NativeUInt(a) => Some(*a),
EmValue::I32(a) => Some(*a as u64),
EmValue::I64(a) => Some(*a as u64),
_ => None,
});
let native_target = if let Some(addr) = func_addr {
let runtime = thread.runtime_state().read().ok();
runtime
.as_ref()
.and_then(|rt| {
let name = rt.native_functions().lookup_by_address(addr)?;
let token = rt.native_functions().allocate_token(&name);
log::debug!(
"GetDelegateForFunctionPointer(0x{addr:X}) → {name} → token 0x{:08X}",
token.value()
);
Some(token)
})
.unwrap_or(tokens::native::NATIVE_FUNCTION_POINTER)
} else {
tokens::native::NATIVE_FUNCTION_POINTER
};
match thread
.heap_mut()
.alloc_delegate(delegate_type, None, native_target)
{
Ok(obj_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(obj_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn marshal_get_function_pointer_for_delegate_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let delegate_ref = ctx.args.first().and_then(|v| match v {
EmValue::ObjectRef(href) => Some(*href),
_ => None,
});
if let Some(href) = delegate_ref {
if let Ok(crate::emulation::HeapObject::Delegate {
invocation_list, ..
}) = thread.heap().get(href)
{
if let Some(entry) = invocation_list.first() {
let method_token = entry.method_token;
if tokens::is_native_function_pointer(method_token) {
if let Ok(rt) = thread.runtime_state().read() {
if let Some(name) = rt.native_functions().lookup_by_token(method_token) {
if let Some(addr) = rt.native_functions().lookup_address_by_name(&name)
{
log::debug!("GetFunctionPointerForDelegate → {name} at 0x{addr:X}");
return PreHookResult::Bypass(Some(EmValue::NativeInt(
addr as i64,
)));
}
}
}
}
return PreHookResult::Bypass(Some(EmValue::NativeInt(i64::from(
method_token.value(),
))));
}
}
}
PreHookResult::Bypass(Some(EmValue::NativeInt(
tokens::native_addresses::DELEGATE_FUNCTION_POINTER_FALLBACK,
)))
}
fn intptr_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let value = match ctx.args.first() {
Some(EmValue::I32(v)) => EmValue::NativeInt(i64::from(*v)),
Some(EmValue::I64(v)) | Some(EmValue::NativeInt(v)) => EmValue::NativeInt(*v),
Some(EmValue::NativeUInt(v)) | Some(EmValue::UnmanagedPtr(v)) => {
EmValue::NativeInt((*v).cast_signed())
}
_ => EmValue::NativeInt(0),
};
match ctx.this {
Some(EmValue::ManagedPtr(ptr)) => {
let _ = thread.store_through_pointer(ptr, value);
}
Some(EmValue::ObjectRef(href)) => {
let field_token = Token::new(0x04FF_FF01);
let _ = thread.heap_mut().set_field(*href, field_token, value);
}
_ => {}
}
PreHookResult::Bypass(None)
}
fn intptr_get_size_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let size = ctx.pointer_size.bytes() as i32;
PreHookResult::Bypass(Some(EmValue::I32(size)))
}
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,
Some(EmValue::ManagedPtr(ptr)) => {
resolve_managed_ptr_as_i64(ptr, thread).unwrap_or(0) 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(),
Some(EmValue::ManagedPtr(ptr)) => {
resolve_managed_ptr_as_i64(ptr, thread).unwrap_or(0)
}
_ => 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))
}
fn coerce_to_native(val: &EmValue) -> i64 {
match val {
EmValue::NativeInt(v) | EmValue::I64(v) => *v,
EmValue::NativeUInt(v) | EmValue::UnmanagedPtr(v) => *v as i64,
EmValue::I32(v) => i64::from(*v),
EmValue::Null => 0,
_ => 0,
}
}
fn intptr_op_equality_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let lhs = ctx.args.first().map(coerce_to_native).unwrap_or(0);
let rhs = ctx.args.get(1).map(coerce_to_native).unwrap_or(0);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(lhs == rhs))))
}
fn intptr_op_inequality_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let lhs = ctx.args.first().map(coerce_to_native).unwrap_or(0);
let rhs = ctx.args.get(1).map(coerce_to_native).unwrap_or(0);
PreHookResult::Bypass(Some(EmValue::I32(i32::from(lhs != rhs))))
}
fn intptr_get_zero_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::NativeInt(0)))
}
#[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(), 29);
}
#[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, tokens::native_addresses::CURRENT_MODULE);
}
_ => 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_int64_hook() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"ToInt64",
PointerSize::Bit64,
)
.with_this(Some(&EmValue::NativeInt(0x1_0000_0000)));
let mut thread = create_test_thread();
let result = intptr_to_int64_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::I64(v))) => assert_eq!(v, 0x1_0000_0000),
_ => panic!("Expected Bypass with I64"),
}
}
#[test]
fn test_intptr_get_zero_hook() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"get_Zero",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = intptr_get_zero_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::NativeInt(0)))
));
}
#[test]
fn test_intptr_op_equality_same() {
let args = [EmValue::NativeInt(42), EmValue::NativeInt(42)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"op_Equality",
PointerSize::Bit64,
)
.with_args(&args);
let mut thread = create_test_thread();
let result = intptr_op_equality_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_intptr_op_equality_different() {
let args = [EmValue::NativeInt(1), EmValue::NativeInt(2)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"op_Equality",
PointerSize::Bit64,
)
.with_args(&args);
let mut thread = create_test_thread();
let result = intptr_op_equality_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_intptr_op_inequality() {
let args = [EmValue::NativeInt(1), EmValue::NativeInt(2)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"IntPtr",
"op_Inequality",
PointerSize::Bit64,
)
.with_args(&args);
let mut thread = create_test_thread();
let result = intptr_op_inequality_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[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)"),
}
}
}