use log::{debug, warn};
use crate::{
assembly::{
decode_stream, Immediate, InstructionAssembler, Operand, OperandType, INSTRUCTIONS,
INSTRUCTIONS_FE, INSTRUCTIONS_FE_MAX, INSTRUCTIONS_MAX,
},
emulation::{
engine::{EmulationError, SyntheticMethodBody},
runtime::{
bcl::reflection::{
extract_type_token, object_ref_equality_pre, object_ref_inequality_pre,
resolve_method_from_token, unbox_value,
},
hook::{Hook, HookContext, HookManager, PreHookResult},
},
thread::{EmulationThread, ReflectionInvokeRequest},
tokens, EmValue, HeapObject, HeapRef, ManagedPointer,
},
file::parser::Parser,
metadata::{
tables::MemberRefSignature,
token::Token,
typesystem::{CilFlavor, TypeResolver},
},
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.Reflection.MethodBase.op_Equality")
.match_name("System.Reflection", "MethodBase", "op_Equality")
.pre(object_ref_equality_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.op_Inequality")
.match_name("System.Reflection", "MethodBase", "op_Inequality")
.pre(object_ref_inequality_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodInfo.op_Equality")
.match_name("System.Reflection", "MethodInfo", "op_Equality")
.pre(object_ref_equality_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodInfo.op_Inequality")
.match_name("System.Reflection", "MethodInfo", "op_Inequality")
.pre(object_ref_inequality_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.Invoke")
.match_name("System.Reflection", "MethodBase", "Invoke")
.pre(method_invoke_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodInfo.Invoke")
.match_name("System.Reflection", "MethodInfo", "Invoke")
.pre(method_invoke_pre),
)?;
manager.register(
Hook::new("System.Reflection.ConstructorInfo.Invoke")
.match_name("System.Reflection", "ConstructorInfo", "Invoke")
.pre(constructor_invoke_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.get_IsStatic")
.match_name("System.Reflection", "MethodBase", "get_IsStatic")
.pre(method_get_is_static_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodInfo.get_ReturnType")
.match_name("System.Reflection", "MethodInfo", "get_ReturnType")
.pre(method_get_return_type_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.GetParameters")
.match_name("System.Reflection", "MethodBase", "GetParameters")
.pre(method_get_parameters_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.DynamicMethod..ctor")
.match_name("System.Reflection.Emit", "DynamicMethod", ".ctor")
.pre(dynamic_method_ctor_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.DynamicMethod.GetILGenerator")
.match_name("System.Reflection.Emit", "DynamicMethod", "GetILGenerator")
.pre(dynamic_method_get_il_generator_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.ILGenerator.Emit")
.match_name("System.Reflection.Emit", "ILGenerator", "Emit")
.pre(il_generator_emit_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.ILGenerator.DeclareLocal")
.match_name("System.Reflection.Emit", "ILGenerator", "DeclareLocal")
.pre(il_generator_declare_local_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.DynamicMethod.CreateDelegate")
.match_name("System.Reflection.Emit", "DynamicMethod", "CreateDelegate")
.pre(dynamic_method_create_delegate_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.ILGenerator.DefineLabel")
.match_name("System.Reflection.Emit", "ILGenerator", "DefineLabel")
.pre(il_generator_define_label_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.ILGenerator.MarkLabel")
.match_name("System.Reflection.Emit", "ILGenerator", "MarkLabel")
.pre(il_generator_mark_label_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.ILGenerator.BeginExceptionBlock")
.match_name(
"System.Reflection.Emit",
"ILGenerator",
"BeginExceptionBlock",
)
.pre(il_generator_define_label_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.ILGenerator.BeginCatchBlock")
.match_name("System.Reflection.Emit", "ILGenerator", "BeginCatchBlock")
.pre(il_generator_noop_pre),
)?;
manager.register(
Hook::new("System.Reflection.Emit.ILGenerator.EndExceptionBlock")
.match_name("System.Reflection.Emit", "ILGenerator", "EndExceptionBlock")
.pre(il_generator_noop_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.get_Name")
.match_name("System.Reflection", "MethodBase", "get_Name")
.pre(method_get_name_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.get_IsVirtual")
.match_name("System.Reflection", "MethodBase", "get_IsVirtual")
.pre(method_get_is_virtual_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.get_IsAbstract")
.match_name("System.Reflection", "MethodBase", "get_IsAbstract")
.pre(method_get_is_abstract_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.get_IsPublic")
.match_name("System.Reflection", "MethodBase", "get_IsPublic")
.pre(method_get_is_public_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.GetMethodBody")
.match_name("System.Reflection", "MethodBase", "GetMethodBody")
.pre(method_get_method_body_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.get_MethodHandle")
.match_name("System.Reflection", "MethodBase", "get_MethodHandle")
.pre(method_get_method_handle_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodBase.get_ContainsGenericParameters")
.match_name(
"System.Reflection",
"MethodBase",
"get_ContainsGenericParameters",
)
.pre(method_get_contains_generic_parameters_pre),
)?;
manager.register(
Hook::new("System.Reflection.MethodInfo.MakeGenericMethod")
.match_name("System.Reflection", "MethodInfo", "MakeGenericMethod")
.pre(method_make_generic_method_pre),
)?;
Ok(())
}
fn method_invoke_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod {
method_token,
method_type_args,
}) = thread.heap().get(*method_ref)
{
let this_ref = ctx.args.first().cloned();
let sig_params: Option<Vec<bool>> = thread
.assembly()
.and_then(|asm| asm.method(&method_token))
.map(|m| m.signature.params.iter().map(|p| p.by_ref).collect());
let method_args = if let Some(EmValue::ObjectRef(arr_ref)) = ctx.args.get(1) {
if let Ok(HeapObject::Array { elements, .. }) = thread.heap().get(*arr_ref) {
elements
.into_iter()
.enumerate()
.map(|(i, v)| {
let is_byref = sig_params
.as_ref()
.and_then(|params: &Vec<bool>| params.get(i).copied())
.unwrap_or(false);
if is_byref {
EmValue::ManagedPtr(ManagedPointer::to_array_element(*arr_ref, i))
} else {
unbox_value(thread, &v)
}
})
.collect()
} else {
Vec::new()
}
} else {
Vec::new()
};
return PreHookResult::ReflectionInvoke {
request: Box::new(ReflectionInvokeRequest {
method_token,
this_ref,
args: method_args,
method_type_args,
}),
bypass_value: Some(EmValue::Null),
};
}
}
debug!("MethodBase.Invoke: no valid method token found, returning Null");
PreHookResult::Bypass(Some(EmValue::Null))
}
fn constructor_invoke_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(ctor_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) = thread.heap().get(*ctor_ref)
{
if let Some(asm) = thread.assembly().cloned() {
let declaring_type_token = match asm.resolver().declaring_type(method_token) {
Some(t) => t.token,
None => {
return PreHookResult::throw_invalid_operation(
"ConstructorInfo.Invoke: cannot resolve declaring type",
);
}
};
match thread.heap_mut().alloc_object(declaring_type_token) {
Ok(obj_ref) => {
let ctor_args: Vec<EmValue> = ctx
.args
.iter()
.find_map(|arg| {
if let EmValue::ObjectRef(arr_ref) = arg {
if let Ok(HeapObject::Array { elements, .. }) =
thread.heap().get(*arr_ref)
{
return Some(elements);
}
}
None
})
.unwrap_or_default()
.into_iter()
.map(|v| unbox_value(thread, &v))
.collect();
let resolved_token = asm
.resolver()
.resolve_method(method_token)
.unwrap_or(method_token);
return PreHookResult::ReflectionInvoke {
request: Box::new(ReflectionInvokeRequest {
method_token: resolved_token,
this_ref: Some(EmValue::ObjectRef(obj_ref)),
args: ctor_args,
method_type_args: None,
}),
bypass_value: Some(EmValue::ObjectRef(obj_ref)),
};
}
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
}
}
PreHookResult::throw_invalid_operation("ConstructorInfo.Invoke: invalid constructor reference")
}
fn method_get_name_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = resolve_method_from_token(method_token, &asm) {
match thread.heap_mut().alloc_string(&method.name) {
Ok(s_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(s_ref))),
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
if let Some(member_ref) = asm.member_ref(&method_token) {
match thread.heap_mut().alloc_string(&member_ref.name) {
Ok(s_ref) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(s_ref))),
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn method_get_is_static_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = resolve_method_from_token(method_token, &asm) {
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(
method.is_static(),
))));
}
if let Some(member_ref) = asm.member_ref(&method_token) {
if let MemberRefSignature::Method(sig) = &member_ref.signature {
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(!sig.has_this))));
}
}
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(1)))
}
fn method_get_is_virtual_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = resolve_method_from_token(method_token, &asm) {
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(
method.is_virtual(),
))));
}
if let Some(member_ref) = asm.member_ref(&method_token) {
if let MemberRefSignature::Method(sig) = &member_ref.signature {
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(sig.has_this))));
}
}
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn method_get_is_abstract_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = resolve_method_from_token(method_token, &asm) {
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(
method.is_abstract(),
))));
}
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn method_get_is_public_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = resolve_method_from_token(method_token, &asm) {
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(
method.is_public(),
))));
}
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(1)))
}
fn method_get_return_type_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
let ret_sig = resolve_method_from_token(method_token, &asm)
.map(|m| m.signature.return_type.base.clone())
.or_else(|| {
asm.member_ref(&method_token).and_then(|mr| {
if let MemberRefSignature::Method(sig) = &mr.signature {
Some(sig.return_type.base.clone())
} else {
None
}
})
});
if let Some(ret_type) = ret_sig {
if let Ok(cil_type) = TypeResolver::new(asm.types()).resolve(&ret_type) {
match thread
.heap_mut()
.alloc_reflection_type(cil_type.token, None)
{
Ok(type_ref) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(type_ref)))
}
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
return PreHookResult::throw_type_load("Cannot resolve method return type");
}
}
}
}
PreHookResult::throw_type_load("Cannot resolve method return type")
}
fn method_get_parameters_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = resolve_method_from_token(method_token, &asm) {
let mut param_elements = Vec::new();
#[allow(clippy::cast_possible_truncation)]
for (i, param) in method.signature.params.iter().enumerate() {
match thread.heap_mut().alloc_reflection_parameter(
method_token,
i as u32,
param.base.clone(),
) {
Ok(p_ref) => param_elements.push(EmValue::ObjectRef(p_ref)),
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, param_elements)
{
Ok(arr_ref) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_ref)))
}
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
if let Some(member_ref) = asm.member_ref(&method_token) {
if let MemberRefSignature::Method(sig) = &member_ref.signature {
let mut param_elements = Vec::new();
#[allow(clippy::cast_possible_truncation)]
for (i, param) in sig.params.iter().enumerate() {
match thread.heap_mut().alloc_reflection_parameter(
method_token,
i as u32,
param.base.clone(),
) {
Ok(p_ref) => param_elements.push(EmValue::ObjectRef(p_ref)),
Err(e) => {
return PreHookResult::Error(format!(
"heap allocation failed: {e}"
))
}
}
}
match thread
.heap_mut()
.alloc_array_with_values(CilFlavor::Object, param_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 method_get_method_handle_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let method_token = ctx
.this
.and_then(|this| match this {
EmValue::ObjectRef(href) => thread
.heap()
.get_reflection_method_token(*href)
.ok()
.flatten(),
_ => None,
})
.unwrap_or(ctx.method_token);
PreHookResult::Bypass(Some(EmValue::I64(i64::from(method_token.value()))))
}
fn method_get_method_body_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = asm.method(&method_token) {
if method.body.get().is_some() {
match thread
.heap_mut()
.alloc_object(tokens::reflection::METHOD_BODY)
{
Ok(body_ref) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(body_ref)))
}
Err(e) => {
return PreHookResult::Error(format!("heap allocation failed: {e}"))
}
}
}
}
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn method_get_contains_generic_parameters_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(method_ref)) = ctx.this {
if let Ok(HeapObject::ReflectionMethod { method_token, .. }) =
thread.heap().get(*method_ref)
{
if let Some(asm) = thread.assembly().cloned() {
if let Some(method) = resolve_method_from_token(method_token, &asm) {
return PreHookResult::Bypass(Some(EmValue::I32(i32::from(
!method.generic_params.is_empty(),
))));
}
}
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn dynamic_method_ctor_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(EmValue::ObjectRef(dm_ref)) = ctx.this {
let return_type = ctx.args.get(1).and_then(|arg| {
if let EmValue::ObjectRef(t_ref) = arg {
thread
.heap()
.get_reflection_type_token(*t_ref)
.unwrap_or_default()
} else {
None
}
});
let param_types: Vec<Token> = ctx
.args
.get(2)
.and_then(|arg| {
if let EmValue::ObjectRef(arr_ref) = arg {
if let Ok(HeapObject::Array { elements, .. }) = thread.heap().get(*arr_ref) {
return Some(
elements
.iter()
.filter_map(|e| {
if let EmValue::ObjectRef(t_ref) = e {
thread
.heap()
.get_reflection_type_token(*t_ref)
.unwrap_or_default()
} else {
None
}
})
.collect(),
);
}
}
None
})
.unwrap_or_default();
try_hook!(thread
.heap()
.set_dynamic_method_params(*dm_ref, param_types));
try_hook!(thread
.heap()
.set_dynamic_method_return_type(*dm_ref, return_type));
}
PreHookResult::Bypass(None)
}
fn dynamic_method_get_il_generator_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(dm_ref)) = ctx.this {
match thread.heap_mut().alloc_il_generator(*dm_ref) {
Ok(il_ref) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(il_ref)));
}
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
match thread
.heap_mut()
.alloc_object(tokens::codegen::DYNAMIC_METHOD)
{
Ok(il_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(il_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn il_generator_emit_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let Some(EmValue::ObjectRef(il_ref)) = ctx.this else {
return PreHookResult::Bypass(None);
};
let il_ref = *il_ref;
let Some(assembler_arc) = try_hook!(thread.heap().get_il_generator_assembler(il_ref)) else {
return PreHookResult::Bypass(None);
};
let opcode_value = extract_opcode_value(ctx, thread);
let mut method_token: Option<Token> = None;
let mut field_token: Option<Token> = None;
let mut type_token: Option<Token> = None;
let mut label_id: Option<i32> = None;
let mut i32_operand: Option<i32> = None;
let mut i64_operand: Option<i64> = None;
for (i, arg) in ctx.args.iter().enumerate() {
if i == 0 {
continue; }
match arg {
EmValue::ObjectRef(href) => {
if let Ok(obj) = thread.heap().get(*href) {
match obj {
HeapObject::ReflectionMethod {
method_token: mt, ..
} => method_token = Some(mt),
HeapObject::ReflectionField {
field_token: ft, ..
} => field_token = Some(ft),
HeapObject::ReflectionType { type_token: tt, .. } => type_token = Some(tt),
_ => {}
}
}
}
EmValue::I32(v) => {
if i == 1 {
label_id = Some(*v);
}
i32_operand = Some(*v);
}
EmValue::I64(v) => i64_operand = Some(*v),
_ => {}
}
}
let mut asm = match assembler_arc.lock() {
Ok(guard) => guard,
Err(_) => return PreHookResult::Error("assembler lock poisoned".into()),
};
let emit_result = if InstructionAssembler::is_branch_opcode(opcode_value) {
let label = resolve_label_name(label_id, il_ref, thread);
asm.emit_branch_to_label(opcode_value, &label).map(|_| ())
} else if InstructionAssembler::is_token_opcode(opcode_value) {
let token = method_token
.or(field_token)
.or(type_token)
.unwrap_or(Token::new(0));
asm.emit_opcode(opcode_value, Some(Operand::Token(token)))
.map(|_| ())
} else if let Some(v) = i64_operand {
asm.emit_opcode(opcode_value, Some(Operand::Immediate(Immediate::Int64(v))))
.map(|_| ())
} else if let Some(v) = i32_operand {
let operand = build_immediate_operand(opcode_value, v);
asm.emit_opcode(opcode_value, operand).map(|_| ())
} else {
asm.emit_opcode(opcode_value, None).map(|_| ())
};
if let Err(e) = emit_result {
debug!(
"ILGenerator.Emit: assembler error for opcode 0x{:04X}: {e}",
opcode_value
);
}
PreHookResult::Bypass(None)
}
fn build_immediate_operand(opcode: u16, value: i32) -> Option<Operand> {
let op_type = if opcode < u16::from(INSTRUCTIONS_MAX) {
INSTRUCTIONS[opcode as usize].op_type
} else if opcode >= 0xFE00 {
let sub = (opcode & 0xFF) as usize;
if sub >= usize::from(INSTRUCTIONS_FE_MAX) {
return None;
}
INSTRUCTIONS_FE[sub].op_type
} else {
return None;
};
match op_type {
OperandType::None => None,
OperandType::Int8 => Some(Operand::Immediate(Immediate::Int8(value as i8))),
OperandType::UInt8 => Some(Operand::Immediate(Immediate::UInt8(value as u8))),
OperandType::Int16 => Some(Operand::Immediate(Immediate::Int16(value as i16))),
OperandType::UInt16 => Some(Operand::Immediate(Immediate::UInt16(value as u16))),
OperandType::Int32 => Some(Operand::Immediate(Immediate::Int32(value))),
OperandType::UInt32 => Some(Operand::Immediate(Immediate::UInt32(value as u32))),
OperandType::Int64 => Some(Operand::Immediate(Immediate::Int64(i64::from(value)))),
OperandType::UInt64 => Some(Operand::Immediate(Immediate::UInt64(value as u64))),
OperandType::Float32 => Some(Operand::Immediate(Immediate::Float32(value as f32))),
OperandType::Float64 => Some(Operand::Immediate(Immediate::Float64(f64::from(value)))),
OperandType::Token => Some(Operand::Immediate(Immediate::UInt32(value as u32))),
OperandType::Switch => None,
}
}
fn extract_opcode_value(ctx: &HookContext<'_>, thread: &EmulationThread) -> u16 {
match ctx.args.first() {
Some(EmValue::I32(v)) => *v as u16,
Some(EmValue::ObjectRef(href)) => {
if let Ok(HeapObject::BoxedValue { value, .. }) = thread.heap().get(*href) {
if let EmValue::I32(v) = value.as_ref() {
return *v as u16;
}
}
if let Ok(EmValue::I32(v)) = thread
.heap()
.get_field(*href, tokens::misc_fields::OPCODE_VALUE)
{
return v as u16;
}
0
}
Some(EmValue::ValueType { fields, .. }) => {
if let Some(EmValue::I32(packed)) = fields.first() {
(*packed & 0xFFFF) as u16
} else {
debug!("OpCode ValueType has no i32 field, fields: {:?}", fields);
0
}
}
_ => 0,
}
}
fn resolve_label_name(label_id: Option<i32>, il_ref: HeapRef, thread: &EmulationThread) -> String {
let id = label_id.unwrap_or(0) as usize;
match thread.heap().il_generator_get_label(il_ref, id) {
Ok(Some(name)) => name,
_ => format!("_L{id}"),
}
}
fn il_generator_declare_local_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(il_ref)) = ctx.this {
let type_token = ctx.args.first().and_then(|a| extract_type_token(thread, a));
if let Some(tt) = type_token {
try_hook!(thread.heap().il_generator_push_local(*il_ref, tt));
}
}
match thread
.heap_mut()
.alloc_object(tokens::codegen::IL_GENERATOR)
{
Ok(lb_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(lb_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn dynamic_method_create_delegate_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let type_token = ctx.args.first().and_then(|arg| {
if let EmValue::ObjectRef(href) = arg {
thread
.heap()
.get_reflection_type_token(*href)
.unwrap_or_default()
} else {
None
}
});
let Some(type_token) = type_token else {
warn!("DynamicMethod.CreateDelegate: missing delegate type argument");
return PreHookResult::throw_argument_null("delegateType");
};
let Some(EmValue::ObjectRef(dm_ref)) = ctx.this else {
return PreHookResult::throw_invalid_operation("CreateDelegate: invalid this");
};
let dm_ref = *dm_ref;
match finalize_dynamic_method_delegate(thread, dm_ref, type_token) {
Ok(del_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(del_ref))),
Err(e) => {
warn!("DynamicMethod.CreateDelegate: finalization failed: {e}");
PreHookResult::throw_invalid_operation("DynamicMethod IL finalization failed")
}
}
}
fn finalize_dynamic_method_delegate(
thread: &mut EmulationThread,
dm_ref: HeapRef,
type_token: Token,
) -> Result<HeapRef> {
let il_ref = thread
.heap()
.get_dynamic_method_il_generator(dm_ref)?
.ok_or_else(|| EmulationError::InternalError {
description: "DynamicMethod has no ILGenerator".into(),
})?;
let assembler_arc = thread
.heap()
.get_il_generator_assembler(il_ref)?
.ok_or_else(|| EmulationError::InternalError {
description: "ILGenerator has no assembler".into(),
})?;
let assembler = {
let mut guard = assembler_arc
.lock()
.map_err(|_| EmulationError::LockPoisoned {
description: "IL generator assembler",
})?;
std::mem::take(&mut *guard)
};
let (bytecode, _max_stack, exception_handlers) = assembler.finish()?;
let instructions = if bytecode.is_empty() {
Vec::new()
} else {
let mut parser = Parser::new(&bytecode);
decode_stream(&mut parser, 0)?
};
let local_tokens = thread
.heap()
.il_generator_get_locals(il_ref)?
.unwrap_or_default();
let local_types: Vec<CilFlavor> = local_tokens
.iter()
.map(|tok| {
thread
.type_token_to_cil_flavor(*tok)
.unwrap_or(CilFlavor::Object)
})
.collect();
let (is_static, param_tokens) = thread
.heap()
.get_dynamic_method_info(dm_ref)?
.unwrap_or((true, Vec::new()));
let param_types: Vec<CilFlavor> = param_tokens
.iter()
.map(|tok| {
thread
.type_token_to_cil_flavor(*tok)
.unwrap_or(CilFlavor::Object)
})
.collect();
let return_type_token = thread.heap().get_dynamic_method_return_type(dm_ref)?;
let returns_value = match return_type_token {
None => false,
Some(tok) => match thread.type_token_to_cil_flavor(tok) {
Some(CilFlavor::Void) => false,
Some(_) => true,
None => true, },
};
debug!(
"DynamicMethod.CreateDelegate: finalized {} instructions, {} locals, {} params, {} handlers, returns={}",
instructions.len(),
local_types.len(),
param_types.len(),
exception_handlers.len(),
returns_value,
);
let body = SyntheticMethodBody {
instructions,
local_types,
param_types,
is_static,
returns_value,
exception_handlers,
};
let synthetic_token = thread.register_synthetic_method(body);
thread
.heap_mut()
.alloc_delegate(type_token, None, synthetic_token)
}
fn method_make_generic_method_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let method_token = match ctx.this {
Some(EmValue::ObjectRef(method_ref)) => {
try_hook!(thread.heap().get_reflection_method_token(*method_ref))
}
_ => None,
};
let Some(method_token) = method_token else {
debug!("MakeGenericMethod: no valid method token on 'this'");
return PreHookResult::Bypass(Some(EmValue::Null));
};
let type_args = match ctx.args.first() {
Some(EmValue::ObjectRef(arr_ref)) => match thread.heap().get(*arr_ref) {
Ok(HeapObject::Array { elements, .. }) => {
let mut args = Vec::with_capacity(elements.len());
for elem in &elements {
if let EmValue::ObjectRef(type_ref) = elem {
if let Some(tt) =
try_hook!(thread.heap().get_reflection_type_token(*type_ref))
{
args.push(tt);
}
}
}
args
}
_ => Vec::new(),
},
_ => Vec::new(),
};
if type_args.is_empty() {
debug!("MakeGenericMethod: no type arguments provided, returning open method");
match thread.heap_mut().alloc_reflection_method(method_token) {
Ok(href) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
debug!(
"MakeGenericMethod: 0x{:08X} with {} type arg(s)",
method_token.value(),
type_args.len()
);
match thread
.heap_mut()
.alloc_reflection_method_generic(method_token, type_args)
{
Ok(href) => PreHookResult::Bypass(Some(EmValue::ObjectRef(href))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn il_generator_define_label_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(il_ref)) = ctx.this {
if let Some((id, _name)) = try_hook!(thread.heap().il_generator_define_label(*il_ref)) {
return PreHookResult::Bypass(Some(EmValue::I32(id as i32)));
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn il_generator_mark_label_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if let Some(EmValue::ObjectRef(il_ref)) = ctx.this {
let label_id = match ctx.args.first() {
Some(EmValue::I32(v)) => *v as usize,
_ => 0,
};
if let Some(label_name) = try_hook!(thread.heap().il_generator_get_label(*il_ref, label_id))
{
if let Some(assembler_arc) =
try_hook!(thread.heap().get_il_generator_assembler(*il_ref))
{
let mut asm = match assembler_arc.lock() {
Ok(guard) => guard,
Err(_) => return PreHookResult::Error("assembler lock poisoned".into()),
};
if let Err(e) = asm.label(&label_name) {
debug!("ILGenerator.MarkLabel: assembler error: {e}");
}
}
}
}
PreHookResult::Bypass(None)
}
fn il_generator_noop_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
#[cfg(test)]
mod tests {
use crate::{
emulation::{
runtime::hook::{HookContext, PreHookResult},
EmValue,
},
metadata::typesystem::PointerSize,
test::emulation::create_test_thread,
};
use super::method_invoke_pre;
#[test]
fn test_method_invoke_hook() {
let ctx = HookContext::new(
crate::metadata::token::Token::new(0x0A000001),
"System.Reflection",
"MethodBase",
"Invoke",
PointerSize::Bit64,
);
let mut thread = create_test_thread();
let result = method_invoke_pre(&ctx, &mut thread);
match result {
PreHookResult::Bypass(Some(EmValue::Null)) => {}
_ => panic!("Expected Bypass with Null"),
}
}
}