use log::warn;
use crate::{
emulation::{
runtime::{
bcl::reflection::{object_ref_equality_pre, object_ref_inequality_pre},
hook::{Hook, HookContext, HookManager, PreHookResult},
},
thread::EmulationThread,
tokens, EmValue,
},
metadata::{tables::ModuleRaw, token::Token, typesystem::CilFlavor},
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.Reflection.Module.get_FullyQualifiedName")
.match_name("System.Reflection", "Module", "get_FullyQualifiedName")
.pre(module_get_fully_qualified_name_pre),
)?;
manager.register(
Hook::new("System.Reflection.Module.get_Assembly")
.match_name("System.Reflection", "Module", "get_Assembly")
.pre(module_get_assembly_pre),
)?;
manager.register(
Hook::new("System.Reflection.Module.get_ModuleHandle")
.match_name("System.Reflection", "Module", "get_ModuleHandle")
.pre(module_get_module_handle_pre),
)?;
manager.register(
Hook::new("System.Reflection.Module.ResolveMethod")
.match_name("System.Reflection", "Module", "ResolveMethod")
.pre(module_resolve_method_pre),
)?;
manager.register(
Hook::new("System.Reflection.Module.ResolveType")
.match_name("System.Reflection", "Module", "ResolveType")
.pre(module_resolve_type_pre),
)?;
manager.register(
Hook::new("System.Reflection.Module.ResolveField")
.match_name("System.Reflection", "Module", "ResolveField")
.pre(module_resolve_field_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.get_GlobalAssemblyCache")
.match_name("System.Reflection", "Assembly", "get_GlobalAssemblyCache")
.pre(assembly_get_global_assembly_cache_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.get_Location")
.match_name("System.Reflection", "Assembly", "get_Location")
.pre(assembly_get_location_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.Debugger.get_IsAttached")
.match_name("System.Diagnostics", "Debugger", "get_IsAttached")
.pre(debugger_get_is_attached_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetTypes")
.match_name("System.Reflection", "Assembly", "GetTypes")
.pre(assembly_get_types_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetExportedTypes")
.match_name("System.Reflection", "Assembly", "GetExportedTypes")
.pre(assembly_get_exported_types_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetType")
.match_name("System.Reflection", "Assembly", "GetType")
.pre(assembly_get_type_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.get_FullName")
.match_name("System.Reflection", "Assembly", "get_FullName")
.pre(assembly_get_full_name_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetName")
.match_name("System.Reflection", "Assembly", "GetName")
.pre(assembly_get_name_pre),
)?;
manager.register(
Hook::new("System.Reflection.AssemblyName.get_Name")
.match_name("System.Reflection", "AssemblyName", "get_Name")
.pre(assembly_name_get_name_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.Reflection.Assembly.GetModules")
.match_name("System.Reflection", "Assembly", "GetModules")
.pre(assembly_get_modules_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.get_ManifestModule")
.match_name("System.Reflection", "Assembly", "get_ManifestModule")
.pre(assembly_get_manifest_module_pre),
)?;
manager.register(
Hook::new("System.ModuleHandle.GetRuntimeTypeHandleFromMetadataToken")
.match_name(
"System",
"ModuleHandle",
"GetRuntimeTypeHandleFromMetadataToken",
)
.pre(module_handle_resolve_token_pre),
)?;
manager.register(
Hook::new("System.ModuleHandle.ResolveTypeHandle")
.match_name("System", "ModuleHandle", "ResolveTypeHandle")
.pre(module_handle_resolve_token_pre),
)?;
manager.register(
Hook::new("System.ModuleHandle.GetRuntimeFieldHandleFromMetadataToken")
.match_name(
"System",
"ModuleHandle",
"GetRuntimeFieldHandleFromMetadataToken",
)
.pre(module_handle_resolve_token_pre),
)?;
manager.register(
Hook::new("System.ModuleHandle.ResolveFieldHandle")
.match_name("System", "ModuleHandle", "ResolveFieldHandle")
.pre(module_handle_resolve_token_pre),
)?;
manager.register(
Hook::new("System.ModuleHandle.GetRuntimeMethodHandleFromMetadataToken")
.match_name(
"System",
"ModuleHandle",
"GetRuntimeMethodHandleFromMetadataToken",
)
.pre(module_handle_resolve_token_pre),
)?;
manager.register(
Hook::new("System.ModuleHandle.ResolveMethodHandle")
.match_name("System", "ModuleHandle", "ResolveMethodHandle")
.pre(module_handle_resolve_token_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.StackFrame..ctor")
.match_name("System.Diagnostics", "StackFrame", ".ctor")
.pre(stack_frame_ctor_pre),
)?;
manager.register(
Hook::new("System.Diagnostics.StackFrame.GetMethod")
.match_name("System.Diagnostics", "StackFrame", "GetMethod")
.pre(stack_frame_get_method_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.GetReferencedAssemblies")
.match_name("System.Reflection", "Assembly", "GetReferencedAssemblies")
.pre(assembly_get_referenced_assemblies_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.op_Equality")
.match_name("System.Reflection", "Assembly", "op_Equality")
.pre(object_ref_equality_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.op_Inequality")
.match_name("System.Reflection", "Assembly", "op_Inequality")
.pre(object_ref_inequality_pre),
)?;
manager.register(
Hook::new("System.Reflection.Assembly.get_EntryPoint")
.match_name("System.Reflection", "Assembly", "get_EntryPoint")
.pre(assembly_get_entry_point_pre),
)?;
Ok(())
}
fn module_get_fully_qualified_name_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 base = &thread.config().environment.module_base_path;
let path = format!("{base}\\{module_name}");
match thread.heap_mut().alloc_string(&path) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn module_get_assembly_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(asm_ref) = thread.fake_objects().assembly() {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(asm_ref)));
}
match thread.heap_mut().alloc_object(tokens::singletons::ASSEMBLY) {
Ok(asm_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(asm_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn module_get_module_handle_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 module_resolve_method_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
#[allow(clippy::cast_sign_loss)]
let method_token = if let Some(token_value) = ctx.args.first().and_then(EmValue::as_i32) {
Token::new(token_value as u32)
} else {
return PreHookResult::throw_argument_exception("Missing metadata token argument");
};
let table = method_token.table();
if table != 0x06 && table != 0x0A && table != 0x2B {
warn!(
"Module.ResolveMethod: invalid token table 0x{:02X} for 0x{:08X}",
table,
method_token.value()
);
return PreHookResult::throw_argument_exception("Token is not a valid method token");
}
match thread.heap_mut().alloc_reflection_method(method_token) {
Ok(method_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(method_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn module_resolve_type_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
#[allow(clippy::cast_sign_loss)]
let type_token = if let Some(token_value) = ctx.args.first().and_then(EmValue::as_i32) {
Token::new(token_value as u32)
} else {
return PreHookResult::throw_argument_exception("Missing metadata token argument");
};
match thread.heap_mut().alloc_reflection_type(type_token, None) {
Ok(type_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(type_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn module_resolve_field_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
#[allow(clippy::cast_sign_loss)]
let field_token = if let Some(token_value) = ctx.args.first().and_then(EmValue::as_i32) {
Token::new(token_value as u32)
} else {
return PreHookResult::throw_argument_exception("Missing metadata token argument");
};
if let Some(asm) = thread.assembly().cloned() {
for entry in asm.types().iter() {
let cil_type = entry.value();
for (_, field) in cil_type.fields.iter() {
if field.token == field_token {
match thread.heap_mut().alloc_reflection_field(
field_token,
cil_type.token,
field.flags.is_static(),
) {
Ok(fi_ref) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(fi_ref)));
}
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn module_handle_resolve_token_pre(
ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
let token_value = match ctx.args.first() {
Some(EmValue::I32(v)) => i64::from(*v),
Some(EmValue::NativeInt(v)) => *v,
_ => 0,
};
PreHookResult::Bypass(Some(EmValue::NativeInt(token_value)))
}
fn assembly_get_global_assembly_cache_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn assembly_get_location_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 base = &thread.config().environment.assembly_location_base;
let path = format!("{base}\\{module_name}");
match thread.heap_mut().alloc_string(&path) {
Ok(str_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(str_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_full_name_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(asm) = thread.assembly().cloned() {
if let Some(assembly_meta) = asm.assembly() {
let full_name = format!(
"{}, Version={}.{}.{}.{}",
assembly_meta.name,
assembly_meta.major_version,
assembly_meta.minor_version,
assembly_meta.build_number,
assembly_meta.revision_number
);
match thread.heap_mut().alloc_string(&full_name) {
Ok(s_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(s_ref))),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
}
match thread.heap_mut().alloc_string("Assembly, Version=0.0.0.0") {
Ok(s_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(s_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_name_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
match thread
.heap_mut()
.alloc_object(tokens::reflection::ASSEMBLY_NAME)
{
Ok(obj_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(obj_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_name_get_name_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(asm) = thread.assembly().cloned() {
if let Some(assembly_meta) = asm.assembly() {
match thread.heap_mut().alloc_string(&assembly_meta.name) {
Ok(s_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(s_ref))),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
}
match thread.heap_mut().alloc_string("Assembly") {
Ok(s_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(s_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::Object, 0) {
Ok(arr_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_manifest_module_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
match thread.heap_mut().alloc_object(tokens::reflection::MODULE) {
Ok(m_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(m_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_modules_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let m_ref = try_hook!(thread.heap_mut().alloc_object(tokens::reflection::MODULE));
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, vec![EmValue::ObjectRef(m_ref)])
{
Ok(arr_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_types_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(asm) = thread.assembly().cloned() {
let mut type_elements = Vec::new();
for entry in asm.types().iter() {
let cil_type = entry.value();
match thread
.heap_mut()
.alloc_reflection_type(cil_type.token, None)
{
Ok(t_ref) => type_elements.push(EmValue::ObjectRef(t_ref)),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, type_elements)
{
Ok(arr_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
match thread.heap_mut().alloc_array(CilFlavor::Object, 0) {
Ok(arr_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_exported_types_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(asm) = thread.assembly().cloned() {
let mut type_elements = Vec::new();
for entry in asm.types().iter() {
let cil_type = entry.value();
if cil_type.flags.is_public() {
match thread
.heap_mut()
.alloc_reflection_type(cil_type.token, None)
{
Ok(t_ref) => type_elements.push(EmValue::ObjectRef(t_ref)),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
}
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, type_elements)
{
Ok(arr_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
match thread.heap_mut().alloc_array(CilFlavor::Object, 0) {
Ok(arr_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_type_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(name_ref)) = ctx.args.first() {
if let Ok(type_name) = thread.heap().get_string(*name_ref) {
if let Some(asm) = thread.assembly().cloned() {
for entry in asm.types().iter() {
let cil_type = entry.value();
let full_name = if cil_type.namespace.is_empty() {
&cil_type.name
} else {
&format!("{}.{}", cil_type.namespace, cil_type.name)
};
if full_name == type_name.as_ref() || cil_type.name == type_name.as_ref() {
match thread
.heap_mut()
.alloc_reflection_type(cil_type.token, None)
{
Ok(t_ref) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(t_ref)))
}
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
}
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn assembly_get_referenced_assemblies_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let assembly_names: Vec<EmValue> = thread
.assembly()
.cloned()
.map(|asm| {
asm.refs_assembly()
.iter()
.filter_map(|entry| {
let aref = entry.value();
let name_obj = thread
.heap_mut()
.alloc_object(tokens::reflection::ASSEMBLY_NAME)
.ok()?;
if let Ok(name_str) = thread.heap_mut().alloc_string(&aref.name) {
if thread
.heap()
.set_field(
name_obj,
tokens::misc_fields::ASSEMBLY_NAME_NAME,
EmValue::ObjectRef(name_str),
)
.is_err()
{
return None;
}
}
Some(EmValue::ObjectRef(name_obj))
})
.collect()
})
.unwrap_or_default();
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, assembly_names)
{
Ok(arr_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn assembly_get_entry_point_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(asm) = thread.assembly().cloned() {
let entry_token = asm.cor20header().entry_point_token;
if entry_token != 0 {
match thread
.heap_mut()
.alloc_reflection_method(Token::new(entry_token))
{
Ok(method_ref) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(method_ref)))
}
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn debugger_get_is_attached_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn stack_frame_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let skip_frames = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as usize,
_ => 0,
};
let depth = thread.call_depth();
let target_index = depth.saturating_sub(1).saturating_sub(skip_frames);
let method_token = thread
.get_frame_at(target_index)
.map(|f| f.method())
.unwrap_or(Token::new(0));
if let Some(EmValue::ObjectRef(this_ref)) = ctx.this {
if method_token.value() != 0 {
if let Ok(method_ref) = thread.heap_mut().alloc_reflection_method(method_token) {
try_hook!(thread.heap().set_field(
*this_ref,
tokens::misc_fields::STACKFRAME_METHOD,
EmValue::ObjectRef(method_ref),
));
}
}
}
PreHookResult::Bypass(None)
}
fn stack_frame_get_method_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(this_ref)) = ctx.this {
if let Ok(method_val) = thread
.heap()
.get_field(*this_ref, tokens::misc_fields::STACKFRAME_METHOD)
{
return PreHookResult::Bypass(Some(method_val));
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}