use crate::emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue, HeapRef,
};
pub fn register(manager: &mut HookManager) {
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.Alloc")
.match_name("System.Runtime.InteropServices", "GCHandle", "Alloc")
.pre(gchandle_alloc_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.AddrOfPinnedObject")
.match_name(
"System.Runtime.InteropServices",
"GCHandle",
"AddrOfPinnedObject",
)
.pre(gchandle_addr_of_pinned_object_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.Free")
.match_name("System.Runtime.InteropServices", "GCHandle", "Free")
.pre(gchandle_free_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.get_Target")
.match_name("System.Runtime.InteropServices", "GCHandle", "get_Target")
.pre(gchandle_get_target_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.set_Target")
.match_name("System.Runtime.InteropServices", "GCHandle", "set_Target")
.pre(gchandle_set_target_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.get_IsAllocated")
.match_name(
"System.Runtime.InteropServices",
"GCHandle",
"get_IsAllocated",
)
.pre(gchandle_is_allocated_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.ToIntPtr")
.match_name("System.Runtime.InteropServices", "GCHandle", "ToIntPtr")
.pre(gchandle_to_intptr_pre),
);
manager.register(
Hook::new("System.Runtime.InteropServices.GCHandle.FromIntPtr")
.match_name("System.Runtime.InteropServices", "GCHandle", "FromIntPtr")
.pre(gchandle_from_intptr_pre),
);
}
fn gchandle_alloc_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let result = if let Some(obj) = ctx.args.first() {
match obj {
#[allow(clippy::cast_possible_wrap)]
EmValue::ObjectRef(r) => EmValue::NativeInt(r.id() as i64),
_ => EmValue::NativeInt(0x1000),
}
} else {
EmValue::NativeInt(0x1000)
};
PreHookResult::Bypass(Some(result))
}
fn gchandle_addr_of_pinned_object_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
let result = if let Some(EmValue::NativeInt(handle)) = ctx.this {
EmValue::NativeInt(*handle)
} else {
EmValue::NativeInt(0)
};
PreHookResult::Bypass(Some(result))
}
fn gchandle_free_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn gchandle_get_target_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let result = if let Some(EmValue::NativeInt(heap_id)) = ctx.this {
if *heap_id > 0 {
#[allow(clippy::cast_sign_loss)]
let heap_ref = HeapRef::new(*heap_id as u64);
EmValue::ObjectRef(heap_ref)
} else {
EmValue::Null
}
} else {
EmValue::Null
};
PreHookResult::Bypass(Some(result))
}
fn gchandle_set_target_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn gchandle_is_allocated_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(1))) }
fn gchandle_to_intptr_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let result = if let Some(EmValue::NativeInt(handle)) = ctx.args.first() {
EmValue::NativeInt(*handle)
} else {
EmValue::NativeInt(0)
};
PreHookResult::Bypass(Some(result))
}
fn gchandle_from_intptr_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
let result = if let Some(EmValue::NativeInt(ptr)) = ctx.args.first() {
EmValue::NativeInt(*ptr)
} else {
EmValue::NativeInt(0)
};
PreHookResult::Bypass(Some(result))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{emulation::runtime::hook::HookManager, metadata::typesystem::PointerSize};
#[test]
fn test_register_hooks() {
let mut manager = HookManager::new();
register(&mut manager);
assert_eq!(manager.len(), 8);
}
#[test]
fn test_alloc_hook() {
use crate::metadata::token::Token;
let obj_ref = HeapRef::new(42);
let args = [EmValue::ObjectRef(obj_ref)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Runtime.InteropServices",
"GCHandle",
"Alloc",
PointerSize::Bit64,
)
.with_args(&args);
let mut thread = crate::test::emulation::create_test_thread();
let result = gchandle_alloc_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_addr_of_pinned_object_hook() {
use crate::metadata::token::Token;
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Runtime.InteropServices",
"GCHandle",
"AddrOfPinnedObject",
PointerSize::Bit64,
)
.with_this(Some(&EmValue::NativeInt(0x1000)));
let mut thread = crate::test::emulation::create_test_thread();
let result = gchandle_addr_of_pinned_object_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::NativeInt(v))) => assert_eq!(v, 0x1000),
_ => panic!("Expected Bypass with NativeInt(0x1000)"),
}
}
#[test]
fn test_get_target_hook() {
use crate::metadata::token::Token;
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Runtime.InteropServices",
"GCHandle",
"get_Target",
PointerSize::Bit64,
)
.with_this(Some(&EmValue::NativeInt(42)));
let mut thread = crate::test::emulation::create_test_thread();
let result = gchandle_get_target_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => assert_eq!(r.id(), 42),
_ => panic!("Expected Bypass with ObjectRef(42)"),
}
}
#[test]
fn test_get_target_null_hook() {
use crate::metadata::token::Token;
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Runtime.InteropServices",
"GCHandle",
"get_Target",
PointerSize::Bit64,
)
.with_this(Some(&EmValue::NativeInt(0)));
let mut thread = crate::test::emulation::create_test_thread();
let result = gchandle_get_target_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::Null)) => {}
_ => panic!("Expected Bypass with Null"),
}
}
#[test]
fn test_roundtrip() {
use crate::metadata::token::Token;
let obj_ref = HeapRef::new(123);
let args = [EmValue::ObjectRef(obj_ref)];
let alloc_ctx = HookContext::new(
Token::new(0x0A000001),
"System.Runtime.InteropServices",
"GCHandle",
"Alloc",
PointerSize::Bit64,
)
.with_args(&args);
let mut thread = crate::test::emulation::create_test_thread();
let handle = match gchandle_alloc_pre(&alloc_ctx, &mut thread) {
PreHookResult::Bypass(Some(v)) => v,
_ => panic!("Expected Bypass"),
};
let get_ctx = HookContext::new(
Token::new(0x0A000001),
"System.Runtime.InteropServices",
"GCHandle",
"get_Target",
PointerSize::Bit64,
)
.with_this(Some(&handle));
let result = gchandle_get_target_pre(&get_ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(r))) => assert_eq!(r.id(), 123),
_ => panic!("Expected Bypass with ObjectRef(123)"),
}
}
}