use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
},
utils::LeBytes,
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
manager.register(
Hook::new("System.BitConverter.GetBytes")
.match_name("System", "BitConverter", "GetBytes")
.pre(bitconverter_get_bytes_pre),
)?;
manager.register(
Hook::new("System.BitConverter.ToInt32")
.match_name("System", "BitConverter", "ToInt32")
.pre(bitconverter_to_int32_pre),
)?;
manager.register(
Hook::new("System.BitConverter.ToInt64")
.match_name("System", "BitConverter", "ToInt64")
.pre(bitconverter_to_int64_pre),
)?;
manager.register(
Hook::new("System.BitConverter.ToUInt32")
.match_name("System", "BitConverter", "ToUInt32")
.pre(bitconverter_to_uint32_pre),
)?;
Ok(())
}
fn bitconverter_get_bytes_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::Null));
}
let bytes = match &ctx.args[0] {
EmValue::I32(v) => LeBytes::from_4(v.to_le_bytes()),
EmValue::I64(v) | EmValue::NativeInt(v) => LeBytes::from_8(v.to_le_bytes()),
EmValue::F32(v) => LeBytes::from_4(v.to_le_bytes()),
EmValue::F64(v) => LeBytes::from_8(v.to_le_bytes()),
EmValue::NativeUInt(v) => LeBytes::from_8(v.to_le_bytes()),
EmValue::Bool(v) => LeBytes::from_byte(u8::from(*v)),
_ => return PreHookResult::Bypass(Some(EmValue::Null)),
};
match thread.heap().alloc_byte_array(&bytes) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn bitconverter_to_int32_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let Some(bytes) = (match &ctx.args[0] {
EmValue::ObjectRef(handle) => try_hook!(thread.heap().get_byte_array(*handle)),
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
}) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let start_index = usize::try_from(&ctx.args[1]).unwrap_or(usize::MAX);
if start_index.saturating_add(4) > bytes.len() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let value = i32::from_le_bytes([
bytes[start_index],
bytes[start_index + 1],
bytes[start_index + 2],
bytes[start_index + 3],
]);
PreHookResult::Bypass(Some(EmValue::I32(value)))
}
fn bitconverter_to_int64_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
}
let Some(bytes) = (match &ctx.args[0] {
EmValue::ObjectRef(handle) => try_hook!(thread.heap().get_byte_array(*handle)),
_ => return PreHookResult::Bypass(Some(EmValue::I64(0))),
}) else {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
};
let start_index = usize::try_from(&ctx.args[1]).unwrap_or(usize::MAX);
if start_index.saturating_add(8) > bytes.len() {
return PreHookResult::Bypass(Some(EmValue::I64(0)));
}
let value = i64::from_le_bytes([
bytes[start_index],
bytes[start_index + 1],
bytes[start_index + 2],
bytes[start_index + 3],
bytes[start_index + 4],
bytes[start_index + 5],
bytes[start_index + 6],
bytes[start_index + 7],
]);
PreHookResult::Bypass(Some(EmValue::I64(value)))
}
fn bitconverter_to_uint32_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let Some(bytes) = (match &ctx.args[0] {
EmValue::ObjectRef(handle) => try_hook!(thread.heap().get_byte_array(*handle)),
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
}) else {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
};
let start_index = usize::try_from(&ctx.args[1]).unwrap_or(usize::MAX);
if start_index.saturating_add(4) > bytes.len() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let value = u32::from_le_bytes([
bytes[start_index],
bytes[start_index + 1],
bytes[start_index + 2],
bytes[start_index + 3],
]);
#[allow(clippy::cast_possible_wrap)]
let signed_value = value as i32;
PreHookResult::Bypass(Some(EmValue::I32(signed_value)))
}
#[cfg(test)]
mod tests {
use crate::{
emulation::{
runtime::hook::{HookContext, PreHookResult},
EmValue,
},
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
#[test]
fn test_bitconverter_get_bytes_hook() {
let mut thread = create_test_thread();
let args = [EmValue::I32(0x12345678)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"BitConverter",
"GetBytes",
PointerSize::Bit64,
)
.with_args(&args);
let result = super::bitconverter_get_bytes_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))) = result {
let bytes = thread.heap().get_byte_array(handle).unwrap().unwrap();
assert_eq!(bytes, vec![0x78, 0x56, 0x34, 0x12]);
} else {
panic!("Expected ObjectRef");
}
}
#[test]
fn test_bitconverter_to_int32_hook() {
let mut thread = create_test_thread();
let bytes = thread
.heap()
.alloc_byte_array(&[0x78, 0x56, 0x34, 0x12])
.unwrap();
let args = [EmValue::ObjectRef(bytes), EmValue::I32(0)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"BitConverter",
"ToInt32",
PointerSize::Bit64,
)
.with_args(&args);
let result = super::bitconverter_to_int32_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(0x12345678)))
));
}
#[test]
fn test_bitconverter_to_int64_hook() {
let mut thread = create_test_thread();
let bytes = thread
.heap()
.alloc_byte_array(&[0xF0, 0xDE, 0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12])
.unwrap();
let args = [EmValue::ObjectRef(bytes), EmValue::I32(0)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"BitConverter",
"ToInt64",
PointerSize::Bit64,
)
.with_args(&args);
let result = super::bitconverter_to_int64_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I64(0x123456789ABCDEF0)))
));
}
#[test]
fn test_bitconverter_to_uint32_hook() {
let mut thread = create_test_thread();
let bytes = thread
.heap()
.alloc_byte_array(&[0xFF, 0xFF, 0xFF, 0xFF])
.unwrap();
let args = [EmValue::ObjectRef(bytes), EmValue::I32(0)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"BitConverter",
"ToUInt32",
PointerSize::Bit64,
)
.with_args(&args);
let result = super::bitconverter_to_uint32_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(-1)))
));
}
}