use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
},
utils::{base64_decode, base64_encode},
};
pub fn register(manager: &mut HookManager) {
manager.register(
Hook::new("System.Convert.ToBase64String")
.match_name("System", "Convert", "ToBase64String")
.pre(to_base64_string_pre),
);
manager.register(
Hook::new("System.Convert.FromBase64String")
.match_name("System", "Convert", "FromBase64String")
.pre(from_base64_string_pre),
);
manager.register(
Hook::new("System.Convert.ToByte")
.match_name("System", "Convert", "ToByte")
.pre(to_byte_pre),
);
manager.register(
Hook::new("System.Convert.ToSByte")
.match_name("System", "Convert", "ToSByte")
.pre(to_sbyte_pre),
);
manager.register(
Hook::new("System.Convert.ToInt16")
.match_name("System", "Convert", "ToInt16")
.pre(to_int16_pre),
);
manager.register(
Hook::new("System.Convert.ToUInt16")
.match_name("System", "Convert", "ToUInt16")
.pre(to_uint16_pre),
);
manager.register(
Hook::new("System.Convert.ToInt32")
.match_name("System", "Convert", "ToInt32")
.pre(to_int32_pre),
);
manager.register(
Hook::new("System.Convert.ToUInt32")
.match_name("System", "Convert", "ToUInt32")
.pre(to_uint32_pre),
);
manager.register(
Hook::new("System.Convert.ToInt64")
.match_name("System", "Convert", "ToInt64")
.pre(to_int64_pre),
);
manager.register(
Hook::new("System.Convert.ToUInt64")
.match_name("System", "Convert", "ToUInt64")
.pre(to_uint64_pre),
);
manager.register(
Hook::new("System.Convert.ToSingle")
.match_name("System", "Convert", "ToSingle")
.pre(to_single_pre),
);
manager.register(
Hook::new("System.Convert.ToDouble")
.match_name("System", "Convert", "ToDouble")
.pre(to_double_pre),
);
manager.register(
Hook::new("System.Convert.ToChar")
.match_name("System", "Convert", "ToChar")
.pre(to_char_pre),
);
manager.register(
Hook::new("System.Convert.ToBoolean")
.match_name("System", "Convert", "ToBoolean")
.pre(to_boolean_pre),
);
manager.register(
Hook::new("System.Convert.ToString")
.match_name("System", "Convert", "ToString")
.pre(to_string_pre),
);
}
fn to_base64_string_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::Null));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Some(bytes) = thread.heap_mut().get_byte_array(*handle) {
let encoded = base64_encode(&bytes);
match thread.heap_mut().alloc_string(&encoded) {
Ok(str_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn from_base64_string_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::Null));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Ok(s) = thread.heap().get_string(*handle) {
if let Some(decoded) = base64_decode(&s) {
match thread.heap_mut().alloc_byte_array(&decoded) {
Ok(arr_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn to_byte_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_u8.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_u8_cil().into()))
}
fn to_sbyte_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_i8.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_i8_cil().into()))
}
fn to_int16_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_i16.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_i16_cil().into()))
}
fn to_uint16_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_u16.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_u16_cil().into()))
}
fn to_int32_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_i32.into()));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Ok(s) = thread.heap_mut().get_string(*handle) {
if let Ok(n) = s.parse::<i32>() {
return PreHookResult::Bypass(Some(n.into()));
}
}
return PreHookResult::Bypass(Some(0_i32.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_i32_cil().into()))
}
fn to_uint32_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_i32.into()));
}
#[allow(clippy::cast_possible_wrap)]
let value = ctx.args[0].to_u32_cil() as i32;
PreHookResult::Bypass(Some(value.into()))
}
fn to_int64_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_i64.into()));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Ok(s) = thread.heap_mut().get_string(*handle) {
if let Ok(n) = s.parse::<i64>() {
return PreHookResult::Bypass(Some(n.into()));
}
}
return PreHookResult::Bypass(Some(0_i64.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_i64_cil().into()))
}
fn to_uint64_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_i64.into()));
}
#[allow(clippy::cast_possible_wrap)]
let value = ctx.args[0].to_u64_cil() as i64;
PreHookResult::Bypass(Some(value.into()))
}
fn to_single_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0.0_f32.into()));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Ok(s) = thread.heap_mut().get_string(*handle) {
if let Ok(f) = s.parse::<f32>() {
return PreHookResult::Bypass(Some(f.into()));
}
}
return PreHookResult::Bypass(Some(0.0_f32.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_f32_cil().into()))
}
fn to_double_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0.0_f64.into()));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Ok(s) = thread.heap_mut().get_string(*handle) {
if let Ok(f) = s.parse::<f64>() {
return PreHookResult::Bypass(Some(f.into()));
}
}
return PreHookResult::Bypass(Some(0.0_f64.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_f64_cil().into()))
}
fn to_char_pre(ctx: &HookContext<'_>, _thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(0_u16.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_u16_cil().into()))
}
fn to_boolean_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(false.into()));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Ok(s) = thread.heap().get_string(*handle) {
return PreHookResult::Bypass(Some(
(s.eq_ignore_ascii_case("true") || &*s == "1").into(),
));
}
return PreHookResult::Bypass(Some(false.into()));
}
PreHookResult::Bypass(Some(ctx.args[0].to_bool_cil().into()))
}
fn to_string_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
if ctx.args.is_empty() {
return match thread.heap_mut().alloc_string("") {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
};
}
let s = match &ctx.args[0] {
EmValue::I32(n) => n.to_string(),
EmValue::I64(n) => n.to_string(),
EmValue::F32(f) => f.to_string(),
EmValue::F64(f) => f.to_string(),
EmValue::ObjectRef(handle) => {
if let Ok(existing) = thread.heap_mut().get_string(*handle) {
existing.to_string()
} else {
String::new()
}
}
_ => String::new(),
};
match thread.heap_mut().alloc_string(&s) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
emulation::runtime::hook::HookManager,
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
#[test]
fn test_register_hooks() {
let mut manager = HookManager::new();
register(&mut manager);
assert_eq!(manager.len(), 15);
}
#[test]
fn test_to_base64_string_hook() {
let mut thread = create_test_thread();
let data = thread.heap_mut().alloc_byte_array(b"Hello").unwrap();
let args = [EmValue::ObjectRef(data)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Convert",
"ToBase64String",
PointerSize::Bit64,
)
.with_args(&args);
let result = to_base64_string_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))) = result {
assert_eq!(&*thread.heap().get_string(handle).unwrap(), "SGVsbG8=");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_from_base64_string_hook() {
let mut thread = create_test_thread();
let encoded = thread.heap_mut().alloc_string("SGVsbG8=").unwrap();
let args = [EmValue::ObjectRef(encoded)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Convert",
"FromBase64String",
PointerSize::Bit64,
)
.with_args(&args);
let result = from_base64_string_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))) = result {
assert_eq!(
thread.heap().get_byte_array(handle),
Some(b"Hello".to_vec())
);
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_to_int32_hook() {
let mut thread = create_test_thread();
let args = [EmValue::I32(42)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Convert",
"ToInt32",
PointerSize::Bit64,
)
.with_args(&args);
let result = to_int32_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(42)))
));
let s = thread.heap_mut().alloc_string("123").unwrap();
let args = [EmValue::ObjectRef(s)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Convert",
"ToInt32",
PointerSize::Bit64,
)
.with_args(&args);
let result = to_int32_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::I32(123)))
));
}
#[test]
fn test_to_boolean_hook() {
let mut thread = create_test_thread();
let args = [EmValue::I32(0)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Convert",
"ToBoolean",
PointerSize::Bit64,
)
.with_args(&args);
let result = to_boolean_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(false)))
));
let args = [EmValue::I32(1)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Convert",
"ToBoolean",
PointerSize::Bit64,
)
.with_args(&args);
let result = to_boolean_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(true)))
));
let s = thread.heap_mut().alloc_string("true").unwrap();
let args = [EmValue::ObjectRef(s)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System",
"Convert",
"ToBoolean",
PointerSize::Bit64,
)
.with_args(&args);
let result = to_boolean_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::Bool(true)))
));
}
}