use std::sync::{Arc, RwLock};
use dashmap::DashMap;
use log::debug;
use crate::{
emulation::{
engine::{
cctors::CctorTracker, context::EmulationContext, dispatch::DispatchResolver,
error::synthetic_exception, generics::GenericRegistry, interpreter::Interpreter,
resolution::CallResolution, typeops, EmulationError,
},
memory::{AddressSpace, HeapObject, TypeInitState},
process::{EmulationConfig, UnknownMethodBehavior},
runtime::{HookContext, HookManager, HookOutcome, RuntimeState},
thread::{EmulationThread, MulticastState, ThreadCallFrame},
tokens,
tracer::{TraceEvent, TraceWriter},
EmValue, SymbolicValue, TaintSource,
},
metadata::{
signatures::TypeSignature,
tables::{MemberRef, MemberRefSignature, TableId},
token::Token,
typesystem::{CilFlavor, CilTypeReference},
},
Result,
};
enum TokenCacheEntry {
NoMatch,
Cached(ResolvedMethodInfo),
}
#[derive(Clone)]
struct ResolvedMethodInfo {
namespace: Arc<str>,
type_name: Arc<str>,
method_name: Arc<str>,
is_internal: bool,
param_count: usize,
has_this: bool,
}
pub struct CallResolver {
hooks: Arc<HookManager>,
token_cache: DashMap<Token, TokenCacheEntry>,
dispatch: DispatchResolver,
config: Arc<EmulationConfig>,
runtime: Arc<RwLock<RuntimeState>>,
trace_writer: Option<Arc<TraceWriter>>,
generics: Arc<GenericRegistry>,
}
impl CallResolver {
pub fn new(
runtime: Arc<RwLock<RuntimeState>>,
config: Arc<EmulationConfig>,
trace_writer: Option<Arc<TraceWriter>>,
generics: Arc<GenericRegistry>,
) -> Result<Self> {
let hooks = runtime
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "runtime state",
})?
.hooks();
Ok(Self {
hooks,
token_cache: DashMap::new(),
dispatch: DispatchResolver::new(),
config,
runtime,
trace_writer,
generics,
})
}
#[must_use]
pub fn hooks(&self) -> &HookManager {
&self.hooks
}
#[inline]
fn trace(&self, event: TraceEvent) {
if let Some(ref writer) = self.trace_writer {
writer.write(event);
}
}
#[inline]
fn trace_stubs_enabled(&self) -> bool {
self.trace_writer.is_some() && self.config.tracing.trace_stubs
}
#[must_use]
pub fn format_method_name(&self, method_token: Token) -> String {
if let Some(cached) = self.token_cache.get(&method_token) {
if let TokenCacheEntry::Cached(info) = cached.value() {
return if info.namespace.is_empty() {
format!("{}::{}", info.type_name, info.method_name)
} else {
format!(
"{}.{}::{}",
info.namespace, info.type_name, info.method_name
)
};
}
}
format!("0x{:08X}", method_token.value())
}
fn try_cross_assembly_resolve(
&self,
member_ref: &MemberRef,
is_virtual: bool,
) -> Result<Option<CallResolution>> {
let Some((namespace, type_name)) = EmulationContext::get_member_ref_type_info(member_ref)
else {
return Ok(None);
};
let method_name = &member_ref.name;
let fullname = if namespace.is_empty() {
type_name.clone()
} else {
format!("{namespace}.{type_name}")
};
let state = self
.runtime
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "runtime state",
})?;
for i in 0..state.app_domain().parsed_assembly_count() {
let Some(asm) = state.app_domain().get_parsed_assembly(i) else {
continue;
};
let Some(cil_type) = asm.types().get_by_fullname(&fullname, true) else {
continue;
};
if let Some(method) = cil_type.find_method(method_name) {
debug!(
"Cross-assembly resolve: {}.{} → asm[{}] 0x{:08X}",
fullname,
method_name,
i,
method.token.value()
);
return Ok(Some(CallResolution::Redirect {
target_token: method.token,
arguments: vec![],
is_virtual,
pre_push_value: None,
is_reflection_invoke: false,
#[allow(clippy::cast_possible_truncation)]
assembly_index: Some(i as u8),
method_type_args: None,
}));
}
}
Ok(None)
}
pub fn resolve_call(
&self,
context: &EmulationContext,
method_token: Token,
thread: &mut EmulationThread,
is_virtual: bool,
constrained_type: Option<Token>,
address_space: &AddressSpace,
) -> Result<CallResolution> {
match self.try_hook_call(context, method_token, thread)? {
HookOutcome::ReflectionInvoke {
request,
bypass_value,
} => {
if self.trace_stubs_enabled() {
let name = self.format_method_name(method_token);
self.trace(TraceEvent::HookInvoke {
method: method_token,
hook_name: format!("{name} [reflection-invoke]"),
bypassed: false,
return_value: None,
});
}
let request = *request;
let target_token = request.method_token;
if target_token.is_table(TableId::MethodDef) {
let is_ctor_invoke = context
.get_method(target_token)
.map(|m| m.name == ".ctor")
.unwrap_or(false);
let target_returns_value =
context.method_returns_value(target_token).unwrap_or(false);
let pre_push = if is_ctor_invoke {
bypass_value.clone()
} else if !target_returns_value {
Some(EmValue::Null)
} else {
None
};
let is_static = context.is_static_method(target_token).unwrap_or(true);
let mut args = Vec::new();
if let Some(this_val) = request.this_ref {
if !is_static {
args.push(this_val);
}
}
args.extend(request.args);
return Ok(CallResolution::Redirect {
target_token,
arguments: args,
is_virtual: false,
pre_push_value: pre_push,
is_reflection_invoke: true,
assembly_index: None,
method_type_args: request.method_type_args,
});
}
return Ok(CallResolution::HookedBypass {
return_value: bypass_value,
});
}
HookOutcome::Handled(value) => {
if self.trace_stubs_enabled() {
let name = self.format_method_name(method_token);
self.trace(TraceEvent::HookInvoke {
method: method_token,
hook_name: name,
bypassed: true,
return_value: value.as_ref().map(|v| format!("{v}")),
});
}
if let Some(EmValue::Symbolic(ref sym)) = value {
debug!(
"Hook for 0x{:08X} returned Symbolic({:?}, source={:?})",
method_token.value(),
sym.cil_flavor,
sym.source,
);
}
return Ok(CallResolution::HookedBypass {
return_value: value,
});
}
HookOutcome::ThrewException {
exception_type,
message,
} => {
return Ok(CallResolution::ThrowException {
exception_type,
message,
});
}
HookOutcome::NoMatch => { }
}
if let Some(result) = self.try_native_call(context, method_token, thread)? {
if self.trace_stubs_enabled() {
self.trace(TraceEvent::HookInvoke {
method: method_token,
hook_name: format!("{} [native]", self.format_method_name(method_token)),
bypassed: true,
return_value: result.as_ref().map(|v| format!("{v}")),
});
}
return Ok(CallResolution::HookedBypass {
return_value: result,
});
}
if context.is_synthetic_method(method_token) {
let is_static = context.is_static_method(method_token)?;
let param_types = context.get_parameter_types(method_token)?;
let total_args = if is_static {
param_types.len()
} else {
param_types.len() + 1
};
let arg_values = thread.pop_args(total_args)?;
let expects_return = context.method_returns_value(method_token)?;
return Ok(CallResolution::EnterMethod {
token: method_token,
arguments: arg_values,
expects_return,
assembly_index: None,
method_type_args: None,
});
}
if method_token.is_table(TableId::MethodSpec) {
if let Some(method_spec) = context.get_method_spec(method_token) {
if let Some(underlying_token) =
EmulationContext::resolve_method_spec_to_token(&method_spec)
{
let type_args: Vec<Token> = method_spec
.instantiation
.generic_args
.iter()
.filter_map(|sig| {
let frame_type_args = thread
.current_frame()
.and_then(|f| f.type_type_args().map(|a| a.to_vec()));
let frame_method_args = thread
.current_frame()
.and_then(|f| f.method_type_args().map(|a| a.to_vec()));
context.type_signature_to_token(
sig,
frame_type_args.as_deref(),
frame_method_args.as_deref(),
&self.generics,
)
})
.collect();
let method_type_args = if type_args.is_empty() {
None
} else {
Some(type_args)
};
return Ok(CallResolution::Redirect {
target_token: underlying_token,
arguments: vec![],
is_virtual,
is_reflection_invoke: false,
pre_push_value: None,
assembly_index: None,
method_type_args,
});
}
}
return Err(EmulationError::MethodNotFound {
token: method_token,
}
.into());
}
if method_token.is_table(TableId::MemberRef) {
if let Some(resolved) = context.assembly().resolver().resolve_method(method_token) {
if resolved.is_table(TableId::MethodDef) {
return Ok(CallResolution::Redirect {
target_token: resolved,
arguments: vec![],
is_virtual,
pre_push_value: None,
is_reflection_invoke: false,
assembly_index: None,
method_type_args: None,
});
}
}
if let Some(member_ref) = context.get_member_ref(method_token) {
if let Some(resolution) =
self.try_cross_assembly_resolve(&member_ref, is_virtual)?
{
return Ok(resolution);
}
}
if let Some(member_ref) = context.get_member_ref(method_token) {
if let MemberRefSignature::Method(method_sig) = &member_ref.signature {
let total_args = if method_sig.has_this {
method_sig.param_count as usize + 1
} else {
method_sig.param_count as usize
};
if is_virtual && method_sig.has_this && total_args > 0 {
let args = thread.peek_args(total_args)?;
let this_arg = &args[0];
if let EmValue::ObjectRef(heap_ref) = this_arg {
if let Ok(runtime_type_token) = thread.heap().get_type_token(*heap_ref)
{
if let Some(runtime_type) = context.get_type(runtime_type_token) {
let rt_namespace = if runtime_type.namespace.is_empty() {
runtime_type
.enclosing_type()
.map(|enc| enc.namespace.clone())
.filter(|ns| !ns.is_empty())
.unwrap_or_default()
} else {
runtime_type.namespace.clone()
};
let rt_type_name = &runtime_type.name;
let declared_type_info =
EmulationContext::get_member_ref_type_info(&member_ref);
let types_differ =
!declared_type_info.as_ref().is_some_and(|(dns, dtn)| {
dns == &rt_namespace && dtn == rt_type_name.as_str()
});
if types_differ {
let param_types =
context.get_parameter_types(method_token).ok();
let param_types_ref: Option<&[CilFlavor]> =
param_types.as_deref();
let return_type =
context.get_return_type(method_token).ok().flatten();
let (this_ref, method_args): (
Option<&EmValue>,
&[EmValue],
) = (Some(&args[0]), &args[1..]);
let hook_context = HookContext::new(
method_token,
&rt_namespace,
rt_type_name,
&member_ref.name,
self.config.pointer_size,
)
.with_this(this_ref)
.with_args(method_args)
.with_param_types(param_types_ref)
.with_return_type(return_type);
let outcome =
self.hooks.execute(&hook_context, thread, |_| None)?;
match outcome {
HookOutcome::Handled(value) => {
thread.pop_args(total_args)?;
if self.trace_stubs_enabled() {
self.trace(TraceEvent::HookInvoke {
method: method_token,
hook_name: format!(
"{}.{}.{} [virtual dispatch]",
rt_namespace,
rt_type_name,
member_ref.name
),
bypassed: true,
return_value: value
.as_ref()
.map(|v| format!("{v}")),
});
}
return Ok(CallResolution::HookedBypass {
return_value: value,
});
}
HookOutcome::ThrewException {
exception_type,
message,
} => {
thread.pop_args(total_args)?;
return Ok(CallResolution::ThrowException {
exception_type,
message,
});
}
HookOutcome::ReflectionInvoke { .. } => {
thread.pop_args(total_args)?;
return Ok(CallResolution::HookedBypass {
return_value: None,
});
}
HookOutcome::NoMatch => {
}
}
}
}
}
}
}
for _ in 0..total_args {
thread.pop()?;
}
let type_info = EmulationContext::get_member_ref_type_info(&member_ref);
let type_desc = type_info
.as_ref()
.map(|(ns, tn)| format!("{ns}.{tn}"))
.unwrap_or_else(|| "Unknown".to_string());
let value = if !matches!(method_sig.return_type.base, TypeSignature::Void) {
let return_type = CilFlavor::from(&method_sig.return_type.base);
debug!(
"Unhooked MemberRef 0x{:08X} '{}.{}' → returning Symbolic({:?})",
method_token.value(),
type_desc,
member_ref.name,
return_type
);
Some(EmValue::Symbolic(SymbolicValue::new(
return_type,
TaintSource::MethodReturn(method_token.value()),
)))
} else {
debug!(
"Unhooked MemberRef 0x{:08X} '{}.{}' → void (no return)",
method_token.value(),
type_desc,
member_ref.name,
);
None
};
if self.trace_stubs_enabled() {
self.trace(TraceEvent::HookInvoke {
method: method_token,
hook_name: format!("{}.{} [synthetic]", type_desc, member_ref.name),
bypassed: true,
return_value: value.as_ref().map(|v| format!("{v}")),
});
}
return Ok(CallResolution::ReturnSynthetic { value });
}
}
return Err(EmulationError::MethodNotFound {
token: method_token,
}
.into());
}
let method = context.get_method(method_token)?;
let param_count = method.signature.params.len();
let is_instance = !context.is_static_method(method_token)?;
let total_args = if is_instance {
param_count + 1
} else {
param_count
};
let mut arg_values = thread.pop_args(total_args)?;
let resolved_method_token = if is_virtual && is_instance && !arg_values.is_empty() {
if let Some(constraint_token) = constrained_type {
let resolved = context.resolve_virtual_call(method_token, constraint_token);
if resolved == method_token && context.is_value_type(constraint_token) {
if let EmValue::ManagedPtr(ptr) = &arg_values[0] {
if let Ok(value) = typeops::deref_managed_ptr(address_space, thread, ptr) {
let boxed = thread.heap_mut().alloc_boxed(constraint_token, value)?;
arg_values[0] = EmValue::ObjectRef(boxed);
}
}
}
resolved
} else {
self.resolve_virtual_dispatch(context, thread, method_token, &arg_values[0])
}
} else {
method_token
};
let method = if resolved_method_token == method_token {
method
} else {
context.get_method(resolved_method_token)?
};
if method.is_code_native() && method.is_code_unmanaged() {
return Err(EmulationError::InternalError {
description: format!(
"Cannot emulate native x86 method 0x{:08x} '{}'. \
Native methods must be converted to CIL during deobfuscation.",
resolved_method_token.value(),
method.name
),
}
.into());
}
if method.is_code_runtime()
&& method.name == "Invoke"
&& is_instance
&& !arg_values.is_empty()
{
if let EmValue::ObjectRef(href) = &arg_values[0] {
match thread.heap().get(*href) {
Ok(HeapObject::Delegate {
invocation_list, ..
}) => {
if let Some(entry) = invocation_list.first() {
let target_token = entry.method_token;
let delegate_target = entry.target;
if tokens::is_native_function_pointer(target_token) {
let return_value =
resolve_native_delegate_return(target_token, thread, context);
return Ok(CallResolution::HookedBypass { return_value });
}
let mut dispatch_token = if target_token.is_table(TableId::MemberRef) {
context
.assembly()
.resolver()
.resolve_method(target_token)
.filter(|t| t.is_table(TableId::MethodDef))
.unwrap_or(target_token)
} else {
target_token
};
let should_dispatch = if context.is_synthetic_method(dispatch_token) {
true
} else if dispatch_token.is_table(TableId::MethodDef) {
let has_il = context
.get_method(dispatch_token)
.map(|m| m.has_body() && m.instructions().next().is_some())
.unwrap_or(false);
if has_il {
true
} else {
let is_virtual = context
.get_method(dispatch_token)
.map(|m| m.is_virtual())
.unwrap_or(false);
if is_virtual {
let instance_ref = delegate_target.or_else(|| {
arg_values.get(1).and_then(|v| match v {
EmValue::ObjectRef(r) => Some(*r),
_ => None,
})
});
if let Some(inst_ref) = instance_ref {
let resolved = self.resolve_virtual_dispatch(
context,
thread,
dispatch_token,
&EmValue::ObjectRef(inst_ref),
);
if resolved != dispatch_token {
let resolved_has_il = context
.get_method(resolved)
.map(|m| {
m.has_body()
&& m.instructions().next().is_some()
})
.unwrap_or(false);
if resolved_has_il {
dispatch_token = resolved;
true
} else {
false
}
} else {
false
}
} else {
false
}
} else {
false
}
}
} else {
false
};
if should_dispatch {
let delegate_args: Vec<EmValue> = arg_values[1..].to_vec();
if invocation_list.len() > 1 {
thread.set_multicast_state(MulticastState {
remaining_entries: invocation_list[1..].to_vec(),
delegate_args: delegate_args.clone(),
dispatch_depth: thread.call_depth(),
});
}
return Ok(CallResolution::Redirect {
target_token: dispatch_token,
arguments: delegate_args,
is_virtual: false,
pre_push_value: None,
is_reflection_invoke: false,
assembly_index: None,
method_type_args: None,
});
}
if target_token.is_table(TableId::MemberRef) {
let delegate_args: Vec<EmValue> = arg_values[1..].to_vec();
if invocation_list.len() > 1 {
thread.set_multicast_state(MulticastState {
remaining_entries: invocation_list[1..].to_vec(),
delegate_args: delegate_args.clone(),
dispatch_depth: thread.call_depth(),
});
}
return Ok(CallResolution::Redirect {
target_token,
arguments: delegate_args,
is_virtual: false,
pre_push_value: None,
is_reflection_invoke: false,
assembly_index: None,
method_type_args: None,
});
}
debug!(
"delegate dispatch failed: target 0x{:08X} has no concrete implementation",
target_token.value()
);
return Ok(CallResolution::ThrowException {
exception_type: synthetic_exception::INVALID_OPERATION,
message: format!(
"delegate target 0x{:08X} has no concrete implementation",
target_token.value()
),
});
}
}
Ok(other) => {
debug!(
"Delegate Invoke on {:?}: this is {:?}, not a Delegate — dispatch skipped",
resolved_method_token,
std::mem::discriminant(&other),
);
}
Err(_) => {
debug!(
"Delegate Invoke on {:?}: heap lookup failed for {:?}",
resolved_method_token, href,
);
}
}
} else {
debug!(
"Delegate Invoke on {:?}: this is {:?}, not an ObjectRef",
resolved_method_token, arg_values[0],
);
}
}
let has_instructions = method.instructions().next().is_some();
let default_behavior = self
.runtime
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "runtime lock poisoned",
})?
.unknown_method_behavior();
match default_behavior {
UnknownMethodBehavior::Emulate => {
if method.has_body() && has_instructions {
let expects_return = context.method_returns_value(resolved_method_token)?;
return Ok(CallResolution::EnterMethod {
token: resolved_method_token,
arguments: arg_values,
expects_return,
assembly_index: None,
method_type_args: None,
});
}
let return_flavor = context
.get_return_type(resolved_method_token)?
.unwrap_or(CilFlavor::Object);
Ok(CallResolution::ReturnSynthetic {
value: Some(EmValue::Symbolic(SymbolicValue::new(
return_flavor,
TaintSource::MethodReturn(resolved_method_token.value()),
))),
})
}
UnknownMethodBehavior::Symbolic => {
let return_flavor = context
.get_return_type(resolved_method_token)?
.unwrap_or(CilFlavor::Object);
Ok(CallResolution::ReturnSynthetic {
value: Some(EmValue::Symbolic(SymbolicValue::new(
return_flavor,
TaintSource::MethodReturn(resolved_method_token.value()),
))),
})
}
UnknownMethodBehavior::Fail => Err(EmulationError::UnsupportedMethod {
token: resolved_method_token,
reason: "No hook registered and Fail behavior configured",
}
.into()),
UnknownMethodBehavior::Default => {
let return_flavor = context.get_return_type(resolved_method_token)?;
let value = return_flavor.and_then(|flavor| match flavor {
CilFlavor::Void => None,
CilFlavor::Boolean
| CilFlavor::I1
| CilFlavor::U1
| CilFlavor::I2
| CilFlavor::U2
| CilFlavor::I4
| CilFlavor::U4
| CilFlavor::Char => Some(EmValue::I32(0)),
CilFlavor::I8 | CilFlavor::U8 => Some(EmValue::I64(0)),
CilFlavor::R4 => Some(EmValue::F32(0.0)),
CilFlavor::R8 => Some(EmValue::F64(0.0)),
CilFlavor::I | CilFlavor::U => Some(EmValue::NativeInt(0)),
_ => Some(EmValue::Null),
});
Ok(CallResolution::ReturnSynthetic { value })
}
UnknownMethodBehavior::Skip => Ok(CallResolution::ReturnSynthetic { value: None }),
}
}
pub fn resolve_virtual_dispatch(
&self,
context: &EmulationContext,
thread: &EmulationThread,
declared_method: Token,
this_arg: &EmValue,
) -> Token {
let runtime_type = match this_arg {
EmValue::ObjectRef(heap_ref) => {
match thread.heap().get_type_token(*heap_ref) {
Ok(token) => token,
Err(_) => return declared_method, }
}
_ => return declared_method, };
self.dispatch
.resolve(declared_method, runtime_type, context)
}
fn try_hook_call(
&self,
context: &EmulationContext,
method_token: Token,
thread: &mut EmulationThread,
) -> Result<HookOutcome> {
if let Some(cached) = self.token_cache.get(&method_token) {
return match cached.value() {
TokenCacheEntry::NoMatch => Ok(HookOutcome::NoMatch),
TokenCacheEntry::Cached(info) => {
let info = info.clone();
drop(cached);
self.execute_hook_with_resolved(context, method_token, thread, &info)
}
};
}
let member_ref_arc;
let method_arc;
let declaring_type_arc;
let is_internal: bool;
let param_count: usize;
let has_this: bool;
if method_token.is_table(TableId::MemberRef) {
let Some(mr) = context.get_member_ref(method_token) else {
self.token_cache
.insert(method_token, TokenCacheEntry::NoMatch);
return Ok(HookOutcome::NoMatch);
};
declaring_type_arc = match &mr.declaredby {
CilTypeReference::TypeRef(r)
| CilTypeReference::TypeDef(r)
| CilTypeReference::TypeSpec(r) => r.upgrade(),
_ => None,
};
let (count, ht) = match &mr.signature {
MemberRefSignature::Method(sig) => (sig.param_count as usize, sig.has_this),
MemberRefSignature::Field(_) => {
self.token_cache
.insert(method_token, TokenCacheEntry::NoMatch);
return Ok(HookOutcome::NoMatch);
}
};
member_ref_arc = Some(mr);
method_arc = None;
is_internal = false;
param_count = count;
has_this = ht;
} else {
let Ok(method) = context.get_method(method_token) else {
self.token_cache
.insert(method_token, TokenCacheEntry::NoMatch);
return Ok(HookOutcome::NoMatch);
};
declaring_type_arc = method.declaring_type_rc();
let ht = !method.is_static();
let pc = method.signature.params.len();
method_arc = Some(method);
member_ref_arc = None;
is_internal = true;
param_count = pc;
has_this = ht;
}
let raw_namespace = declaring_type_arc
.as_ref()
.map_or("", |dt| dt.namespace.as_str());
let type_name = declaring_type_arc
.as_ref()
.map_or("", |dt| dt.name.as_str());
let enclosing_ns: Option<String>;
let namespace = if raw_namespace.is_empty() {
enclosing_ns = declaring_type_arc
.as_ref()
.and_then(|dt| {
let enc = dt.enclosing_type()?;
if enc.namespace.is_empty() {
None
} else {
Some(enc.namespace.clone())
}
})
.or_else(|| {
let dt = declaring_type_arc.as_ref()?;
if !dt.token.is_table(TableId::TypeSpec) {
return None;
}
let sig = context.get_typespec_signature(dt.token)?;
let base_token = match &sig.base {
TypeSignature::GenericInst(base_sig, _) => match base_sig.as_ref() {
TypeSignature::Class(t) | TypeSignature::ValueType(t) => Some(*t),
_ => None,
},
_ => None,
}?;
let base_type = context.get_type(base_token)?;
let enc = base_type.enclosing_type()?;
if enc.namespace.is_empty() {
None
} else {
Some(enc.namespace.clone())
}
});
enclosing_ns.as_deref().unwrap_or("")
} else {
raw_namespace
};
let method_name = if let Some(mr) = &member_ref_arc {
mr.name.as_str()
} else if let Some(m) = &method_arc {
m.name.as_str()
} else {
self.token_cache
.insert(method_token, TokenCacheEntry::NoMatch);
return Ok(HookOutcome::NoMatch);
};
if !self
.hooks
.has_potential_match(namespace, type_name, method_name)?
{
self.token_cache
.insert(method_token, TokenCacheEntry::NoMatch);
return Ok(HookOutcome::NoMatch);
}
let info = ResolvedMethodInfo {
namespace: Arc::from(namespace),
type_name: Arc::from(type_name),
method_name: Arc::from(method_name),
is_internal,
param_count,
has_this,
};
self.token_cache
.insert(method_token, TokenCacheEntry::Cached(info.clone()));
self.execute_hook_with_resolved(context, method_token, thread, &info)
}
#[allow(clippy::option_option)] fn try_native_call(
&self,
context: &EmulationContext,
method_token: Token,
thread: &mut EmulationThread,
) -> Result<Option<Option<EmValue>>> {
if method_token.table() != 0x06 {
return Ok(None);
}
let Ok(method) = context.get_method(method_token) else {
return Ok(None);
};
if method.has_body() {
return Ok(None);
}
let (function_name, dll_name): (String, Option<String>) =
if let Some(import) = context.find_import_by_method(method_token) {
let dll = context.get_import_dll_name(&import);
(import.name.clone(), dll)
} else {
(method.name.clone(), None)
};
if let Some(dll) = &dll_name {
let param_count = method.signature.params.len();
let args = thread.pop_args(param_count)?;
let hook_context =
HookContext::native(method_token, dll, &function_name, self.config.pointer_size)
.with_args(&args);
match self.hooks.execute(&hook_context, thread, |_| None)? {
HookOutcome::NoMatch => {
for arg in args.into_iter().rev() {
thread.push(arg)?;
}
return Ok(None);
}
HookOutcome::Handled(result)
| HookOutcome::ReflectionInvoke {
bypass_value: result,
..
} => {
return Ok(Some(result));
}
HookOutcome::ThrewException { message, .. } => {
return Err(EmulationError::HookError(format!(
"P/Invoke hook threw CLR exception: {message}"
))
.into());
}
}
}
Ok(None)
}
fn execute_hook_with_resolved(
&self,
context: &EmulationContext,
method_token: Token,
thread: &mut EmulationThread,
info: &ResolvedMethodInfo,
) -> Result<HookOutcome> {
let total_args = if info.has_this {
info.param_count + 1
} else {
info.param_count
};
let args = thread.peek_args(total_args)?;
let param_types = context.get_parameter_types(method_token).ok();
let param_types_ref: Option<&[CilFlavor]> = param_types.as_deref();
let return_type = context.get_return_type(method_token).ok().flatten();
let (this_ref, method_args): (Option<&EmValue>, &[EmValue]) =
if info.has_this && !args.is_empty() {
(Some(&args[0]), &args[1..])
} else {
(None, &args[..])
};
let hook_context = HookContext::new(
method_token,
&info.namespace,
&info.type_name,
&info.method_name,
self.config.pointer_size,
)
.with_this(this_ref)
.with_args(method_args)
.with_internal(info.is_internal)
.with_param_types(param_types_ref)
.with_return_type(return_type);
let outcome = self.hooks.execute(&hook_context, thread, |_| {
None
})?;
match &outcome {
HookOutcome::NoMatch => {}
_ => {
thread.pop_args(total_args)?;
}
}
Ok(outcome)
}
}
pub fn push_method_frame(
interpreter: &mut Interpreter,
thread: &mut EmulationThread,
context: &EmulationContext,
token: Token,
arguments: Vec<EmValue>,
expects_return: bool,
) -> Result<()> {
let return_method = thread.current_frame().map(ThreadCallFrame::method);
let return_offset = interpreter.ip().next_offset();
let caller_stack = thread.take_stack();
let local_cil_flavors = context.get_local_types(token)?;
let callee_is_instance = !context.is_static_method(token)?;
let param_types = context.get_parameter_types(token)?;
let arg_types: Vec<CilFlavor> = if callee_is_instance {
let mut types = vec![CilFlavor::Object];
types.extend(param_types);
types
} else {
param_types
};
let args_with_types: Vec<(EmValue, CilFlavor)> = arguments.into_iter().zip(arg_types).collect();
let mut frame = ThreadCallFrame::new(
token,
return_method,
return_offset,
local_cil_flavors,
args_with_types,
expects_return,
);
frame.save_caller_stack(caller_stack);
let saved_leave_target = thread.exception_state_mut().take_leave_target();
frame.save_leave_target(saved_leave_target);
thread.push_frame(frame);
interpreter.set_method(token);
interpreter.set_offset(0);
Ok(())
}
pub fn maybe_run_type_cctor(
address_space: &AddressSpace,
cctor_tracker: &CctorTracker,
interpreter: &mut Interpreter,
thread: &mut EmulationThread,
context: &EmulationContext,
field: Token,
) -> Result<bool> {
if field.table() != 0x04 {
return Ok(false);
}
let Some(type_token) = context
.assembly()
.resolver()
.declaring_type_of_field(field)
.map(|t| t.token)
else {
return Ok(false);
};
if cctor_tracker.get_type_failure(type_token)?.is_some() {
debug!(
"type 0x{:08X} .cctor previously failed — skipping re-throw (lenient mode)",
type_token.value()
);
return Ok(false);
}
if address_space.statics().is_type_initialized(type_token)? {
return Ok(false);
}
let Some(cctor_token) = context.find_type_cctor(type_token) else {
address_space.statics().mark_type_initialized(type_token)?;
return Ok(false);
};
let Ok(method) = context.get_method(cctor_token) else {
address_space.statics().mark_type_initialized(type_token)?;
return Ok(false);
};
if !method.has_body() {
address_space.statics().mark_type_initialized(type_token)?;
return Ok(false);
}
address_space
.statics()
.set_type_init_state(type_token, TypeInitState::InProgress)?;
zero_initialize_static_fields(address_space, context, type_token)?;
if let Some(parent_token) = context.get_base_type_token(type_token) {
if !address_space.statics().is_type_initialized(parent_token)? {
if let Some(parent_cctor) = context.find_type_cctor(parent_token) {
if let Ok(parent_method) = context.get_method(parent_cctor) {
if parent_method.has_body() {
address_space
.statics()
.set_type_init_state(parent_token, TypeInitState::InProgress)?;
zero_initialize_static_fields(address_space, context, parent_token)?;
}
}
}
}
}
let local_types = context.get_local_types(cctor_token).unwrap_or_default();
let caller_stack = thread.take_stack();
let return_method = interpreter.ip().method();
let return_offset = interpreter.ip().offset();
let args_with_types: Vec<(EmValue, CilFlavor)> = vec![];
let mut frame = ThreadCallFrame::new(
cctor_token,
Some(return_method),
return_offset,
local_types,
args_with_types,
false, );
frame.save_caller_stack(caller_stack);
frame.set_is_cctor();
thread.push_frame(frame);
interpreter.set_method(cctor_token);
interpreter.set_offset(0);
Ok(true)
}
pub fn maybe_run_type_cctor_for_method(
address_space: &AddressSpace,
cctor_tracker: &CctorTracker,
interpreter: &mut Interpreter,
thread: &mut EmulationThread,
context: &EmulationContext,
method: Token,
) -> Result<bool> {
let table = method.table();
if table != 0x06 && table != 0x0A && table != 0x2B {
return Ok(false);
}
let Some(type_info) = context.assembly().resolver().declaring_type(method) else {
return Ok(false);
};
let type_token = type_info.token;
if type_token.table() == 0x02 && type_token.row() == 1 {
return Ok(false);
}
if cctor_tracker.get_type_failure(type_token)?.is_some() {
debug!(
"type 0x{:08X} .cctor previously failed — skipping re-throw (lenient mode)",
type_token.value()
);
return Ok(false);
}
if address_space.statics().is_type_initialized(type_token)? {
return Ok(false);
}
let Some(cctor_token) = context.find_type_cctor(type_token) else {
address_space.statics().mark_type_initialized(type_token)?;
return Ok(false);
};
let Ok(cctor_method) = context.get_method(cctor_token) else {
address_space.statics().mark_type_initialized(type_token)?;
return Ok(false);
};
if !cctor_method.has_body() {
address_space.statics().mark_type_initialized(type_token)?;
return Ok(false);
}
address_space
.statics()
.set_type_init_state(type_token, TypeInitState::InProgress)?;
zero_initialize_static_fields(address_space, context, type_token)?;
if let Some(parent_token) = context.get_base_type_token(type_token) {
if !address_space.statics().is_type_initialized(parent_token)? {
if let Some(parent_cctor) = context.find_type_cctor(parent_token) {
if let Ok(parent_method) = context.get_method(parent_cctor) {
if parent_method.has_body() {
address_space
.statics()
.set_type_init_state(parent_token, TypeInitState::InProgress)?;
zero_initialize_static_fields(address_space, context, parent_token)?;
}
}
}
}
}
debug!(
"triggering .cctor 0x{:08X} for type 0x{:08X} (method call 0x{:08X})",
cctor_token.value(),
type_token.value(),
method.value()
);
let local_types = context.get_local_types(cctor_token).unwrap_or_default();
let caller_stack = thread.take_stack();
let return_method = interpreter.ip().method();
let return_offset = interpreter.ip().offset();
let args_with_types: Vec<(EmValue, CilFlavor)> = vec![];
let mut frame = ThreadCallFrame::new(
cctor_token,
Some(return_method),
return_offset,
local_types,
args_with_types,
false,
);
frame.save_caller_stack(caller_stack);
frame.set_is_cctor();
thread.push_frame(frame);
interpreter.set_method(cctor_token);
interpreter.set_offset(0);
Ok(true)
}
fn zero_initialize_static_fields(
address_space: &AddressSpace,
context: &EmulationContext,
type_token: Token,
) -> Result<()> {
let Some(type_info) = context.get_type(type_token) else {
return Ok(());
};
for (_, field) in type_info.fields.iter() {
if !field.flags.is_static() {
continue;
}
if address_space.statics().contains(field.token)? {
continue;
}
let default_value = match CilFlavor::from(&field.signature.base) {
CilFlavor::Boolean
| CilFlavor::I1
| CilFlavor::U1
| CilFlavor::I2
| CilFlavor::U2
| CilFlavor::I4
| CilFlavor::U4
| CilFlavor::Char => EmValue::I32(0),
CilFlavor::I8 | CilFlavor::U8 => EmValue::I64(0),
CilFlavor::R4 => EmValue::F32(0.0),
CilFlavor::R8 => EmValue::F64(0.0),
CilFlavor::I | CilFlavor::U => EmValue::NativeInt(0),
_ => EmValue::Null,
};
address_space.statics().set(field.token, default_value)?;
}
Ok(())
}
fn resolve_native_delegate_return(
target_token: Token,
thread: &EmulationThread,
_context: &EmulationContext,
) -> Option<EmValue> {
let func_name = thread
.runtime_state()
.read()
.ok()
.and_then(|rt| rt.native_functions().lookup_by_token(target_token));
let name = func_name.as_deref().unwrap_or("unknown");
log::debug!(
"Native delegate invoke: {name} (token 0x{:08X})",
target_token.value()
);
match name {
"VirtualProtect" | "VirtualFree" | "CloseHandle" | "FreeLibrary" => Some(EmValue::I32(1)),
"VirtualAlloc" => {
let size = 0x1_0000usize; match thread.address_space().alloc_unmanaged(size) {
Ok(addr) => Some(EmValue::NativeInt(addr as i64)),
Err(_) => Some(EmValue::NativeInt(0)),
}
}
"GetCurrentProcess" => Some(EmValue::NativeInt(-1)),
"GetCurrentThread" => Some(EmValue::NativeInt(-2)),
"GetLastError" => Some(EmValue::I32(0)),
"GetTickCount" | "GetTickCount64" => Some(EmValue::I32(0)),
_ => {
log::debug!("Native delegate invoke: unknown function {name}, returning 0");
Some(EmValue::I32(0))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulation::{
engine::generics::GenericRegistry, process::EmulationConfig, runtime::RuntimeState,
tracer::TraceWriter,
};
fn create_test_resolver() -> CallResolver {
let runtime = Arc::new(RwLock::new(RuntimeState::new()));
let config = Arc::new(EmulationConfig::default());
let generics = Arc::new(GenericRegistry::new());
CallResolver::new(runtime, config, None, generics).unwrap()
}
#[test]
fn test_resolver_creation() {
let resolver = create_test_resolver();
assert!(!resolver.hooks().is_empty());
}
#[test]
fn test_format_method_name_no_cache() {
let resolver = create_test_resolver();
let name = resolver.format_method_name(Token::new(0x0600_1234));
assert_eq!(name, "0x06001234");
}
#[test]
fn test_format_method_name_with_cache() {
let resolver = create_test_resolver();
let info = ResolvedMethodInfo {
namespace: Arc::from("System"),
type_name: Arc::from("String"),
method_name: Arc::from("Concat"),
is_internal: false,
param_count: 2,
has_this: false,
};
resolver
.token_cache
.insert(Token::new(0x0A00_0042), TokenCacheEntry::Cached(info));
let name = resolver.format_method_name(Token::new(0x0A00_0042));
assert_eq!(name, "System.String::Concat");
}
#[test]
fn test_format_method_name_empty_namespace() {
let resolver = create_test_resolver();
let info = ResolvedMethodInfo {
namespace: Arc::from(""),
type_name: Arc::from("Program"),
method_name: Arc::from("Main"),
is_internal: true,
param_count: 0,
has_this: false,
};
resolver
.token_cache
.insert(Token::new(0x0600_0001), TokenCacheEntry::Cached(info));
let name = resolver.format_method_name(Token::new(0x0600_0001));
assert_eq!(name, "Program::Main");
}
#[test]
fn test_format_method_name_no_match_cached() {
let resolver = create_test_resolver();
resolver
.token_cache
.insert(Token::new(0x0600_0099), TokenCacheEntry::NoMatch);
let name = resolver.format_method_name(Token::new(0x0600_0099));
assert_eq!(name, "0x06000099");
}
#[test]
fn test_resolver_with_trace_writer() {
let runtime = Arc::new(RwLock::new(RuntimeState::new()));
let config = Arc::new(EmulationConfig::default());
let writer = Arc::new(TraceWriter::new_memory(1000, None));
let generics = Arc::new(GenericRegistry::new());
let resolver = CallResolver::new(runtime, config, Some(writer), generics).unwrap();
assert!(!resolver.trace_stubs_enabled());
}
}