use log::debug;
use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
tokens,
value::PointerTarget,
EmValue, ManagedPointer,
},
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.Threading.Monitor.Enter")
.match_name("System.Threading", "Monitor", "Enter")
.pre(monitor_enter_pre),
)?;
manager.register(
Hook::new("System.Threading.Monitor.Exit")
.match_name("System.Threading", "Monitor", "Exit")
.pre(monitor_exit_pre),
)?;
manager.register(
Hook::new("System.Threading.Monitor.TryEnter")
.match_name("System.Threading", "Monitor", "TryEnter")
.pre(monitor_try_enter_pre),
)?;
manager.register(
Hook::new("System.Threading.Monitor.Wait")
.match_name("System.Threading", "Monitor", "Wait")
.pre(monitor_wait_pre),
)?;
manager.register(
Hook::new("System.Threading.Monitor.Pulse")
.match_name("System.Threading", "Monitor", "Pulse")
.pre(monitor_pulse_pre),
)?;
manager.register(
Hook::new("System.Threading.Monitor.PulseAll")
.match_name("System.Threading", "Monitor", "PulseAll")
.pre(monitor_pulse_pre),
)?;
manager.register(
Hook::new("System.Threading.Interlocked.CompareExchange")
.match_name("System.Threading", "Interlocked", "CompareExchange")
.pre(interlocked_compare_exchange_pre),
)?;
manager.register(
Hook::new("System.Threading.Interlocked.Exchange")
.match_name("System.Threading", "Interlocked", "Exchange")
.pre(interlocked_exchange_pre),
)?;
manager.register(
Hook::new("System.Threading.Interlocked.Increment")
.match_name("System.Threading", "Interlocked", "Increment")
.pre(interlocked_increment_pre),
)?;
manager.register(
Hook::new("System.Threading.Interlocked.Decrement")
.match_name("System.Threading", "Interlocked", "Decrement")
.pre(interlocked_decrement_pre),
)?;
manager.register(
Hook::new("System.Threading.Interlocked.Add")
.match_name("System.Threading", "Interlocked", "Add")
.pre(interlocked_add_pre),
)?;
manager.register(
Hook::new("System.Threading.Thread.Sleep")
.match_name("System.Threading", "Thread", "Sleep")
.pre(thread_sleep_pre),
)?;
manager.register(
Hook::new("System.Threading.Thread.get_CurrentThread")
.match_name("System.Threading", "Thread", "get_CurrentThread")
.pre(thread_get_current_thread_pre),
)?;
manager.register(
Hook::new("System.Threading.Thread.get_ManagedThreadId")
.match_name("System.Threading", "Thread", "get_ManagedThreadId")
.pre(thread_get_managed_thread_id_pre),
)?;
Ok(())
}
fn extract_lock_object_id(ctx: &HookContext<'_>) -> Option<u64> {
ctx.args.first().and_then(|arg| match arg {
EmValue::ObjectRef(href) => Some(href.id()),
_ => None,
})
}
fn set_lock_taken(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> Result<()> {
if let Some(EmValue::ManagedPtr(ptr)) = ctx.args.last() {
if ctx.args.len() >= 2 {
thread.store_through_pointer(ptr, EmValue::I32(1))?;
}
}
Ok(())
}
fn monitor_enter_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(obj_id) = extract_lock_object_id(ctx) {
thread.address_space().monitor_enter(obj_id);
}
try_hook!(set_lock_taken(ctx, thread));
PreHookResult::Bypass(None)
}
fn monitor_exit_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(obj_id) = extract_lock_object_id(ctx) {
if !thread.address_space().monitor_exit(obj_id) {
debug!(
"Monitor.Exit: object {} was not locked (mismatched Enter/Exit)",
obj_id
);
}
}
PreHookResult::Bypass(None)
}
fn monitor_try_enter_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(obj_id) = extract_lock_object_id(ctx) {
thread.address_space().monitor_enter(obj_id);
}
try_hook!(set_lock_taken(ctx, thread));
PreHookResult::Bypass(Some(EmValue::I32(1)))
}
fn monitor_wait_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if let Some(obj_id) = extract_lock_object_id(ctx) {
thread.address_space().monitor_exit(obj_id);
thread.address_space().monitor_enter(obj_id);
}
PreHookResult::Bypass(Some(EmValue::I32(1)))
}
fn monitor_pulse_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn read_through_ptr(thread: &EmulationThread, ptr: &ManagedPointer) -> EmValue {
match &ptr.target {
PointerTarget::Local(idx) => thread
.get_frame_at(ptr.frame_depth)
.or_else(|| thread.current_frame())
.and_then(|f| f.locals().get(usize::from(*idx)).ok().cloned())
.unwrap_or(EmValue::Null),
PointerTarget::Argument(idx) => thread
.get_frame_at(ptr.frame_depth)
.or_else(|| thread.current_frame())
.and_then(|f| f.arguments().get(usize::from(*idx)).ok().cloned())
.unwrap_or(EmValue::Null),
PointerTarget::StaticField(field) => thread
.address_space()
.get_static(*field)
.ok()
.flatten()
.unwrap_or(EmValue::Null),
PointerTarget::ObjectField { object, field } => thread
.heap()
.get_field(*object, *field)
.ok()
.unwrap_or(EmValue::Null),
PointerTarget::ArrayElement { array, index } => thread
.heap()
.get_array_element(*array, *index)
.ok()
.unwrap_or(EmValue::Null),
}
}
fn interlocked_compare_exchange_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let (ptr, value, comparand) = match (ctx.args.first(), ctx.args.get(1), ctx.args.get(2)) {
(Some(EmValue::ManagedPtr(p)), Some(v), Some(c)) => (p, v, c),
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let current = read_through_ptr(thread, ptr);
if current == *comparand {
try_hook!(thread.store_through_pointer(ptr, value.clone()));
}
PreHookResult::Bypass(Some(current))
}
fn interlocked_exchange_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let (ptr, value) = match (ctx.args.first(), ctx.args.get(1)) {
(Some(EmValue::ManagedPtr(p)), Some(v)) => (p, v),
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
let current = read_through_ptr(thread, ptr);
try_hook!(thread.store_through_pointer(ptr, value.clone()));
PreHookResult::Bypass(Some(current))
}
fn interlocked_increment_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let ptr = match ctx.args.first() {
Some(EmValue::ManagedPtr(p)) => p,
_ => return PreHookResult::Bypass(Some(EmValue::I32(1))),
};
let current = read_through_ptr(thread, ptr);
let result = match current {
EmValue::I32(v) => EmValue::I32(v.wrapping_add(1)),
EmValue::I64(v) => EmValue::I64(v.wrapping_add(1)),
_ => EmValue::I32(1),
};
try_hook!(thread.store_through_pointer(ptr, result.clone()));
PreHookResult::Bypass(Some(result))
}
fn interlocked_decrement_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let ptr = match ctx.args.first() {
Some(EmValue::ManagedPtr(p)) => p,
_ => return PreHookResult::Bypass(Some(EmValue::I32(-1))),
};
let current = read_through_ptr(thread, ptr);
let result = match current {
EmValue::I32(v) => EmValue::I32(v.wrapping_sub(1)),
EmValue::I64(v) => EmValue::I64(v.wrapping_sub(1)),
_ => EmValue::I32(-1),
};
try_hook!(thread.store_through_pointer(ptr, result.clone()));
PreHookResult::Bypass(Some(result))
}
fn interlocked_add_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let (ptr, addend) = match (ctx.args.first(), ctx.args.get(1)) {
(Some(EmValue::ManagedPtr(p)), Some(v)) => (p, v),
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let current = read_through_ptr(thread, ptr);
let result = match (¤t, addend) {
(EmValue::I32(a), EmValue::I32(b)) => EmValue::I32(a.wrapping_add(*b)),
(EmValue::I64(a), EmValue::I64(b)) => EmValue::I64(a.wrapping_add(*b)),
_ => addend.clone(),
};
try_hook!(thread.store_through_pointer(ptr, result.clone()));
PreHookResult::Bypass(Some(result))
}
fn thread_sleep_pre(_ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
PreHookResult::Bypass(None)
}
fn thread_get_current_thread_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
match thread.heap_mut().alloc_object(tokens::system::THREAD) {
Ok(obj_ref) => PreHookResult::Bypass(Some(EmValue::ObjectRef(obj_ref))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn thread_get_managed_thread_id_pre(
_ctx: &HookContext<'_>,
_thread: &mut EmulationThread,
) -> PreHookResult {
PreHookResult::Bypass(Some(EmValue::I32(1)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::hook::HookManager,
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
fn ctx<'a>(
type_name: &'a str,
method: &'a str,
this: Option<&'a EmValue>,
args: &'a [EmValue],
) -> HookContext<'a> {
HookContext::new(
Token::new(0x0A000001),
"System.Threading",
type_name,
method,
PointerSize::Bit64,
)
.with_this(this)
.with_args(args)
}
#[test]
fn test_register_hooks() {
let manager = HookManager::new();
register(&manager).unwrap();
assert_eq!(manager.len(), 14);
}
#[test]
fn test_monitor_enter() {
let mut thread = create_test_thread();
let obj = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(obj)];
let result = monitor_enter_pre(&ctx("Monitor", "Enter", None, &args), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_monitor_exit() {
let mut thread = create_test_thread();
let obj = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(obj)];
monitor_enter_pre(&ctx("Monitor", "Enter", None, &args), &mut thread);
let result = monitor_exit_pre(&ctx("Monitor", "Exit", None, &args), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_monitor_try_enter() {
let mut thread = create_test_thread();
let obj = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(obj)];
let result = monitor_try_enter_pre(&ctx("Monitor", "TryEnter", None, &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_monitor_wait() {
let mut thread = create_test_thread();
let obj = thread
.heap_mut()
.alloc_object(Token::new(0x02000001))
.unwrap();
let args = [EmValue::ObjectRef(obj)];
monitor_enter_pre(&ctx("Monitor", "Enter", None, &args), &mut thread);
let result = monitor_wait_pre(&ctx("Monitor", "Wait", None, &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_monitor_pulse() {
let mut thread = create_test_thread();
let result = monitor_pulse_pre(&ctx("Monitor", "Pulse", None, &[]), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_thread_sleep() {
let mut thread = create_test_thread();
let args = [EmValue::I32(1000)];
let result = thread_sleep_pre(&ctx("Thread", "Sleep", None, &args), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(None)));
}
#[test]
fn test_thread_get_current_thread() {
let mut thread = create_test_thread();
let result = thread_get_current_thread_pre(
&ctx("Thread", "get_CurrentThread", None, &[]),
&mut thread,
);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
#[test]
fn test_thread_get_managed_thread_id() {
let mut thread = create_test_thread();
let result = thread_get_managed_thread_id_pre(
&ctx("Thread", "get_ManagedThreadId", None, &[]),
&mut thread,
);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_interlocked_increment_no_pointer() {
let mut thread = create_test_thread();
let result =
interlocked_increment_pre(&ctx("Interlocked", "Increment", None, &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(1)))
));
}
#[test]
fn test_interlocked_decrement_no_pointer() {
let mut thread = create_test_thread();
let result =
interlocked_decrement_pre(&ctx("Interlocked", "Decrement", None, &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(-1)))
));
}
#[test]
fn test_interlocked_exchange_no_pointer() {
let mut thread = create_test_thread();
let result =
interlocked_exchange_pre(&ctx("Interlocked", "Exchange", None, &[]), &mut thread);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::Null))));
}
#[test]
fn test_interlocked_compare_exchange_no_pointer() {
let mut thread = create_test_thread();
let result = interlocked_compare_exchange_pre(
&ctx("Interlocked", "CompareExchange", None, &[]),
&mut thread,
);
assert!(matches!(result, PreHookResult::Bypass(Some(EmValue::Null))));
}
#[test]
fn test_interlocked_add_no_pointer() {
let mut thread = create_test_thread();
let result = interlocked_add_pre(&ctx("Interlocked", "Add", None, &[]), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0)))
));
}
#[test]
fn test_interlocked_increment_via_static() {
let mut thread = create_test_thread();
let field_token = Token::new(0x04000001);
thread
.address_space()
.set_static(field_token, EmValue::I32(10))
.unwrap();
let ptr = ManagedPointer {
target: PointerTarget::StaticField(field_token),
offset: 0,
frame_depth: 0,
};
let args = [EmValue::ManagedPtr(ptr)];
let result =
interlocked_increment_pre(&ctx("Interlocked", "Increment", None, &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(11)))
));
}
#[test]
fn test_interlocked_decrement_via_static() {
let mut thread = create_test_thread();
let field_token = Token::new(0x04000001);
thread
.address_space()
.set_static(field_token, EmValue::I32(10))
.unwrap();
let ptr = ManagedPointer {
target: PointerTarget::StaticField(field_token),
offset: 0,
frame_depth: 0,
};
let args = [EmValue::ManagedPtr(ptr)];
let result =
interlocked_decrement_pre(&ctx("Interlocked", "Decrement", None, &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(9)))
));
}
#[test]
fn test_interlocked_exchange_via_static() {
let mut thread = create_test_thread();
let field_token = Token::new(0x04000001);
thread
.address_space()
.set_static(field_token, EmValue::I32(5))
.unwrap();
let ptr = ManagedPointer {
target: PointerTarget::StaticField(field_token),
offset: 0,
frame_depth: 0,
};
let args = [EmValue::ManagedPtr(ptr), EmValue::I32(99)];
let result =
interlocked_exchange_pre(&ctx("Interlocked", "Exchange", None, &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(5)))
));
assert_eq!(
thread.address_space().get_static(field_token).unwrap(),
Some(EmValue::I32(99))
);
}
#[test]
fn test_interlocked_compare_exchange_matching() {
let mut thread = create_test_thread();
let field_token = Token::new(0x04000001);
thread
.address_space()
.set_static(field_token, EmValue::I32(10))
.unwrap();
let ptr = ManagedPointer {
target: PointerTarget::StaticField(field_token),
offset: 0,
frame_depth: 0,
};
let args = [EmValue::ManagedPtr(ptr), EmValue::I32(20), EmValue::I32(10)];
let result = interlocked_compare_exchange_pre(
&ctx("Interlocked", "CompareExchange", None, &args),
&mut thread,
);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(10)))
));
assert_eq!(
thread.address_space().get_static(field_token).unwrap(),
Some(EmValue::I32(20))
);
}
#[test]
fn test_interlocked_compare_exchange_not_matching() {
let mut thread = create_test_thread();
let field_token = Token::new(0x04000001);
thread
.address_space()
.set_static(field_token, EmValue::I32(10))
.unwrap();
let ptr = ManagedPointer {
target: PointerTarget::StaticField(field_token),
offset: 0,
frame_depth: 0,
};
let args = [EmValue::ManagedPtr(ptr), EmValue::I32(20), EmValue::I32(99)];
let result = interlocked_compare_exchange_pre(
&ctx("Interlocked", "CompareExchange", None, &args),
&mut thread,
);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(10)))
));
assert_eq!(
thread.address_space().get_static(field_token).unwrap(),
Some(EmValue::I32(10))
);
}
#[test]
fn test_interlocked_increment_i64() {
let mut thread = create_test_thread();
let field_token = Token::new(0x04000001);
thread
.address_space()
.set_static(field_token, EmValue::I64(100))
.unwrap();
let ptr = ManagedPointer {
target: PointerTarget::StaticField(field_token),
offset: 0,
frame_depth: 0,
};
let args = [EmValue::ManagedPtr(ptr)];
let result =
interlocked_increment_pre(&ctx("Interlocked", "Increment", None, &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(101)))
));
}
#[test]
fn test_interlocked_decrement_i64() {
let mut thread = create_test_thread();
let field_token = Token::new(0x04000001);
thread
.address_space()
.set_static(field_token, EmValue::I64(100))
.unwrap();
let ptr = ManagedPointer {
target: PointerTarget::StaticField(field_token),
offset: 0,
frame_depth: 0,
};
let args = [EmValue::ManagedPtr(ptr)];
let result =
interlocked_decrement_pre(&ctx("Interlocked", "Decrement", None, &args), &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(99)))
));
}
}