use std::sync::Arc;
use log::debug;
use crate::{
emulation::{
capture::{AssemblyLoadMethod, CaptureSource},
memory::DelegateEntry,
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue, HeapObject,
},
metadata::{token::Token, typesystem::CilFlavor},
CilObject, Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.AppDomain.get_CurrentDomain")
.match_name("System", "AppDomain", "get_CurrentDomain")
.pre(appdomain_get_current_domain_pre),
)?;
manager.register(
Hook::new("System.AppDomain.add_AssemblyResolve")
.match_name("System", "AppDomain", "add_AssemblyResolve")
.pre(appdomain_add_assembly_resolve_pre),
)?;
manager.register(
Hook::new("System.AppDomain.remove_AssemblyResolve")
.match_name("System", "AppDomain", "remove_AssemblyResolve")
.pre(appdomain_remove_assembly_resolve_pre),
)?;
manager.register(
Hook::new("System.AppDomain.add_ResourceResolve")
.match_name("System", "AppDomain", "add_ResourceResolve")
.pre(appdomain_add_resource_resolve_pre),
)?;
manager.register(
Hook::new("System.AppDomain.GetAssemblies")
.match_name("System", "AppDomain", "GetAssemblies")
.pre(appdomain_get_assemblies_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.Load")
.match_name("System.Reflection", "Assembly", "Load")
.pre(assembly_load_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.LoadFrom")
.match_name("System.Reflection", "Assembly", "LoadFrom")
.pre(assembly_load_from_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetExecutingAssembly")
.match_name("System.Reflection", "Assembly", "GetExecutingAssembly")
.pre(assembly_get_executing_assembly_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetCallingAssembly")
.match_name("System.Reflection", "Assembly", "GetCallingAssembly")
.pre(assembly_get_calling_assembly_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetEntryAssembly")
.match_name("System.Reflection", "Assembly", "GetEntryAssembly")
.pre(assembly_get_entry_assembly_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetManifestResourceStream")
.match_name("System.Reflection", "Assembly", "GetManifestResourceStream")
.pre(assembly_get_manifest_resource_stream_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetManifestResourceNames")
.match_name("System.Reflection", "Assembly", "GetManifestResourceNames")
.pre(assembly_get_manifest_resource_names_pre),
)?;
manager.register(
Hook::new("System.Delegate..ctor")
.match_name("System", "Delegate", ".ctor")
.pre(delegate_ctor_pre),
)?;
manager.register(
Hook::new("System.MulticastDelegate..ctor")
.match_name("System", "MulticastDelegate", ".ctor")
.pre(delegate_ctor_pre),
)?;
manager.register(
Hook::new("System.ResolveEventHandler..ctor")
.match_name("System", "ResolveEventHandler", ".ctor")
.pre(delegate_ctor_pre),
)?;
Ok(())
}
fn appdomain_get_current_domain_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(domain_ref) = thread.fake_objects().app_domain() {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(domain_ref)));
}
match thread.heap_mut().alloc_object(Token::new(0x0100_0011)) {
Ok(domain_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(domain_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn appdomain_add_assembly_resolve_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn appdomain_remove_assembly_resolve_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn appdomain_add_resource_resolve_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn appdomain_get_assemblies_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
match thread.heap_mut().alloc_array(CilFlavor::Object, 0) {
Ok(array_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_load_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(array_ref)) = ctx.args.first() {
if let Some(bytes) = try_hook!(thread.heap().get_byte_array(*array_ref)) {
let source = CaptureSource::new(
thread.current_method().unwrap_or(Token::new(0)),
thread.id(),
thread.current_offset().unwrap_or(0),
0,
);
thread.capture().capture_assembly(
bytes.clone(),
source,
AssemblyLoadMethod::LoadBytes,
None,
);
if let Ok(loaded_asm) = CilObject::from_mem(bytes) {
let asm_arc = Arc::new(loaded_asm);
if let Ok(mut state) = thread.runtime_state().write() {
let index = state.app_domain_mut().register_parsed_assembly(asm_arc);
debug!(
"Assembly.Load(byte[]): parsed and registered as index {}",
index
);
}
}
}
}
match thread.heap_mut().alloc_object(Token::new(0x0100_0010)) {
Ok(assembly_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_load_from_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
match thread.heap_mut().alloc_object(Token::new(0x0100_0010)) {
Ok(assembly_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_executing_assembly_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(assembly_ref) = thread.fake_objects().assembly() {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref)));
}
match thread.heap_mut().alloc_object(Token::new(0x0100_0010)) {
Ok(assembly_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_calling_assembly_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(assembly_ref) = thread.fake_objects().assembly() {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref)));
}
match thread.heap_mut().alloc_object(Token::new(0x0100_0010)) {
Ok(assembly_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_entry_assembly_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(assembly_ref) = thread.fake_objects().assembly() {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref)));
}
match thread.heap_mut().alloc_object(Token::new(0x0100_0010)) {
Ok(assembly_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(assembly_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_manifest_resource_stream_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let resource_name = match ctx.args.first() {
Some(EmValue::ObjectRef(href)) => thread
.heap()
.get_string(*href)
.ok()
.map(|arc| arc.to_string()),
_ => None,
};
let Some(resource_name) = resource_name else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let Some(assembly) = thread.assembly() else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let resources = assembly.resources();
let resource = resources.get(&resource_name);
let Some(resource) = resource else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let Some(data) = resources.get_data(&resource) else {
return PreHookResult::Bypass(Some(EmValue::Null));
};
let type_token = thread.resolve_type_token("System.IO", "MemoryStream");
match thread.heap_mut().alloc_stream(data.to_vec(), type_token) {
Ok(stream_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(stream_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_manifest_resource_names_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
match thread.heap_mut().alloc_array(CilFlavor::String, 0) {
Ok(array_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(array_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn delegate_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() == 2 {
let target = match &ctx.args[0] {
EmValue::ObjectRef(href) => Some(*href),
_ => None,
};
let method_token = match &ctx.args[1] {
EmValue::UnmanagedPtr(ptr) => Some(Token::new(*ptr as u32)),
EmValue::I32(v) => Some(Token::new(*v as u32)),
EmValue::I64(v) => Some(Token::new(*v as u32)),
EmValue::NativeInt(v) => Some(Token::new(*v as u32)),
_ => None,
};
if let Some(method) = method_token {
if let Some(EmValue::ObjectRef(this_ref)) = ctx.this {
try_hook!(thread.heap().replace_object(
*this_ref,
HeapObject::Delegate {
type_token: ctx.method_token,
invocation_list: vec![DelegateEntry {
target,
method_token: method,
}],
},
));
}
return PreHookResult::Bypass(None); }
}
PreHookResult::Bypass(None)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::hook::HookManager, metadata::typesystem::PointerSize,
test::emulation::create_test_thread,
};
#[test]
fn test_register_hooks() {
let manager = HookManager::new();
register(&manager).unwrap();
assert_eq!(manager.len(), 15);
}
#[test]
fn test_get_current_domain() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"AppDomain",
"get_CurrentDomain",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = appdomain_get_current_domain_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(_))) => {}
_ => panic!("Expected Bypass with ObjectRef"),
}
}
#[test]
fn test_add_assembly_resolve_noop() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"AppDomain",
"add_AssemblyResolve",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = appdomain_add_assembly_resolve_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_assembly_get_executing() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Reflection",
"Assembly",
"GetExecutingAssembly",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = assembly_get_executing_assembly_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
#[test]
fn test_assembly_get_calling() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Reflection",
"Assembly",
"GetCallingAssembly",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = assembly_get_calling_assembly_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
#[test]
fn test_assembly_get_entry() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Reflection",
"Assembly",
"GetEntryAssembly",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = assembly_get_entry_assembly_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
#[test]
fn test_get_manifest_resource_stream_fallback() {
let mut thread = create_test_thread();
let name = thread.heap_mut().alloc_string("nonexistent").unwrap();
let args = [EmValue::ObjectRef(name)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Reflection",
"Assembly",
"GetManifestResourceStream",
PointerSize::Bit64,
)
.with_args(&args);
let result = assembly_get_manifest_resource_stream_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Continue | PreHookResult::Bypass(Some(EmValue::Null))
));
}
#[test]
fn test_get_manifest_resource_names() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Reflection",
"Assembly",
"GetManifestResourceNames",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = assembly_get_manifest_resource_names_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_))) | PreHookResult::Continue
));
}
#[test]
fn test_get_assemblies() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"AppDomain",
"GetAssemblies",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = appdomain_get_assemblies_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
#[test]
fn test_delegate_ctor() {
let mut thread = create_test_thread();
let obj = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let target = thread
.heap_mut()
.alloc_object(Token::new(0x02000002))
.unwrap();
let this = EmValue::ObjectRef(obj);
let args = [EmValue::ObjectRef(target), EmValue::NativeInt(0x06000001)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"MulticastDelegate",
".ctor",
PointerSize::Bit64,
)
.with_this(Some(&this))
.with_args(&args);
let result = delegate_ctor_pre(&ctx, &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_assembly_load_from_fallback() {
let mut thread = create_test_thread();
let path = thread.heap_mut().alloc_string("test.dll").unwrap();
let args = [EmValue::ObjectRef(path)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Reflection",
"Assembly",
"LoadFrom",
PointerSize::Bit64,
)
.with_args(&args);
let result = assembly_load_from_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
#[test]
fn test_assembly_load_captures_bytes() {
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Reflection",
"Assembly",
"Load",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let test_data = vec![0x4D, 0x5A, 0x90, 0x00]; let array_ref = thread.heap_mut().alloc_byte_array(&test_data).unwrap();
let args = [EmValue::ObjectRef(array_ref)];
let ctx = ctx.with_args(&args);
let result = assembly_load_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::ObjectRef(_))) => {}
_ => panic!("Expected Bypass with ObjectRef"),
}
let captured = thread.capture().assemblies();
assert_eq!(captured.len(), 1);
assert_eq!(captured[0].data, test_data);
}
}