use crate::{
emulation::{
capture::CaptureSource,
memory::EncodingType,
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
},
metadata::token::Token,
};
pub fn register(manager: &mut HookManager) {
manager.register(
Hook::new("System.Text.Encoding.GetBytes")
.match_name("System.Text", "Encoding", "GetBytes")
.pre(encoding_get_bytes_pre),
);
manager.register(
Hook::new("System.Text.Encoding.GetString")
.match_name("System.Text", "Encoding", "GetString")
.pre(encoding_get_string_pre),
);
manager.register(
Hook::new("System.Text.Encoding.GetByteCount")
.match_name("System.Text", "Encoding", "GetByteCount")
.pre(encoding_get_byte_count_pre),
);
manager.register(
Hook::new("System.Text.Encoding.GetCharCount")
.match_name("System.Text", "Encoding", "GetCharCount")
.pre(encoding_get_char_count_pre),
);
manager.register(
Hook::new("System.Text.Encoding.get_UTF8")
.match_name("System.Text", "Encoding", "get_UTF8")
.pre(encoding_get_utf8_pre),
);
manager.register(
Hook::new("System.Text.Encoding.get_ASCII")
.match_name("System.Text", "Encoding", "get_ASCII")
.pre(encoding_get_ascii_pre),
);
manager.register(
Hook::new("System.Text.Encoding.get_Unicode")
.match_name("System.Text", "Encoding", "get_Unicode")
.pre(encoding_get_unicode_pre),
);
manager.register(
Hook::new("System.Text.Encoding.get_BigEndianUnicode")
.match_name("System.Text", "Encoding", "get_BigEndianUnicode")
.pre(encoding_get_big_endian_unicode_pre),
);
manager.register(
Hook::new("System.Text.Encoding.get_UTF32")
.match_name("System.Text", "Encoding", "get_UTF32")
.pre(encoding_get_utf32_pre),
);
manager.register(
Hook::new("System.Text.Encoding.GetEncoding")
.match_name("System.Text", "Encoding", "GetEncoding")
.pre(encoding_get_encoding_pre),
);
manager.register(
Hook::new("System.Text.UTF8Encoding.GetBytes")
.match_name("System.Text", "UTF8Encoding", "GetBytes")
.pre(utf8_get_bytes_pre),
);
manager.register(
Hook::new("System.Text.UTF8Encoding.GetString")
.match_name("System.Text", "UTF8Encoding", "GetString")
.pre(utf8_get_string_pre),
);
manager.register(
Hook::new("System.Text.ASCIIEncoding.GetBytes")
.match_name("System.Text", "ASCIIEncoding", "GetBytes")
.pre(ascii_get_bytes_pre),
);
manager.register(
Hook::new("System.Text.ASCIIEncoding.GetString")
.match_name("System.Text", "ASCIIEncoding", "GetString")
.pre(ascii_get_string_pre),
);
manager.register(
Hook::new("System.Text.UnicodeEncoding.GetBytes")
.match_name("System.Text", "UnicodeEncoding", "GetBytes")
.pre(unicode_get_bytes_pre),
);
manager.register(
Hook::new("System.Text.UnicodeEncoding.GetString")
.match_name("System.Text", "UnicodeEncoding", "GetString")
.pre(unicode_get_string_pre),
);
}
fn encoding_get_bytes_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) {
let bytes: Vec<u8> = s.as_bytes().to_vec();
match thread.heap_mut().alloc_byte_array(&bytes) {
Ok(arr_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn encoding_get_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(all_bytes) = thread.heap().get_byte_array(*handle) {
let bytes = if ctx.args.len() >= 3 {
#[allow(clippy::cast_sign_loss)]
let offset = match &ctx.args[1] {
EmValue::I32(o) => *o as usize,
_ => 0,
};
#[allow(clippy::cast_sign_loss)]
let count = match &ctx.args[2] {
EmValue::I32(c) => *c as usize,
_ => all_bytes.len(),
};
if offset + count <= all_bytes.len() {
all_bytes[offset..offset + count].to_vec()
} else {
all_bytes
}
} else {
all_bytes
};
let s = String::from_utf8_lossy(&bytes).into_owned();
let source = CaptureSource::new(
thread.current_method().unwrap_or(Token::new(0)),
thread.id(),
thread.current_offset().unwrap_or(0),
0,
);
thread.capture().capture_string(s.clone(), source);
match thread.heap_mut().alloc_string(&s) {
Ok(str_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn encoding_get_byte_count_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Ok(s) = thread.heap().get_string(*handle) {
let len = i32::try_from(s.len()).unwrap_or(i32::MAX);
return PreHookResult::Bypass(Some(EmValue::I32(len)));
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn encoding_get_char_count_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
if ctx.args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
if let EmValue::ObjectRef(handle) = &ctx.args[0] {
if let Some(bytes) = thread.heap().get_byte_array(*handle) {
let s = String::from_utf8_lossy(&bytes);
let count = i32::try_from(s.chars().count()).unwrap_or(i32::MAX);
return PreHookResult::Bypass(Some(EmValue::I32(count)));
}
}
PreHookResult::Bypass(Some(EmValue::I32(0)))
}
fn encoding_get_utf8_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
match thread.heap_mut().alloc_encoding(EncodingType::Utf8) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn encoding_get_ascii_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
match thread.heap_mut().alloc_encoding(EncodingType::Ascii) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn encoding_get_unicode_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
match thread.heap_mut().alloc_encoding(EncodingType::Utf16Le) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn encoding_get_big_endian_unicode_pre(
_ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
match thread.heap_mut().alloc_encoding(EncodingType::Utf16Be) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn encoding_get_utf32_pre(_ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
match thread.heap_mut().alloc_encoding(EncodingType::Utf32) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn encoding_get_encoding_pre(ctx: &HookContext<'_>, thread: &mut EmulationThread) -> PreHookResult {
let encoding_type = if let Some(EmValue::I32(code_page)) = ctx.args.first() {
match *code_page {
20127 => EncodingType::Ascii, 1200 => EncodingType::Utf16Le, 1201 => EncodingType::Utf16Be, 12000 => EncodingType::Utf32, _ => EncodingType::Utf8,
}
} else {
EncodingType::Utf8
};
match thread.heap_mut().alloc_encoding(encoding_type) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(_) => PreHookResult::Bypass(Some(EmValue::Null)),
}
}
fn utf8_get_bytes_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) {
let bytes: Vec<u8> = s.as_bytes().to_vec();
match thread.heap_mut().alloc_byte_array(&bytes) {
Ok(arr_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn utf8_get_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(all_bytes) = thread.heap().get_byte_array(*handle) {
let bytes = if ctx.args.len() >= 3 {
#[allow(clippy::cast_sign_loss)]
let offset = match &ctx.args[1] {
EmValue::I32(o) => *o as usize,
_ => 0,
};
#[allow(clippy::cast_sign_loss)]
let count = match &ctx.args[2] {
EmValue::I32(c) => *c as usize,
_ => all_bytes.len(),
};
if offset + count <= all_bytes.len() {
all_bytes[offset..offset + count].to_vec()
} else {
all_bytes
}
} else {
all_bytes
};
let s = String::from_utf8_lossy(&bytes).into_owned();
let source = CaptureSource::new(
thread.current_method().unwrap_or(Token::new(0)),
thread.id(),
thread.current_offset().unwrap_or(0),
0,
);
thread.capture().capture_string(s.clone(), source);
match thread.heap_mut().alloc_string(&s) {
Ok(str_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn ascii_get_bytes_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) {
let bytes: Vec<u8> = s
.chars()
.map(|c| if c.is_ascii() { c as u8 } else { b'?' })
.collect();
match thread.heap_mut().alloc_byte_array(&bytes) {
Ok(arr_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn ascii_get_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(all_bytes) = thread.heap().get_byte_array(*handle) {
let bytes = if ctx.args.len() >= 3 {
#[allow(clippy::cast_sign_loss)]
let offset = match &ctx.args[1] {
EmValue::I32(o) => *o as usize,
_ => 0,
};
#[allow(clippy::cast_sign_loss)]
let count = match &ctx.args[2] {
EmValue::I32(c) => *c as usize,
_ => all_bytes.len(),
};
if offset + count <= all_bytes.len() {
all_bytes[offset..offset + count].to_vec()
} else {
all_bytes
}
} else {
all_bytes
};
let s: String = bytes
.iter()
.map(|&b| if b < 128 { char::from(b) } else { '?' })
.collect();
let source = CaptureSource::new(
thread.current_method().unwrap_or(Token::new(0)),
thread.id(),
thread.current_offset().unwrap_or(0),
0,
);
thread.capture().capture_string(s.clone(), source);
match thread.heap_mut().alloc_string(&s) {
Ok(str_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn unicode_get_bytes_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) {
let bytes: Vec<u8> = s.encode_utf16().flat_map(u16::to_le_bytes).collect();
match thread.heap_mut().alloc_byte_array(&bytes) {
Ok(arr_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(arr_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
fn unicode_get_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(all_bytes) = thread.heap().get_byte_array(*handle) {
let bytes = if ctx.args.len() >= 3 {
#[allow(clippy::cast_sign_loss)]
let offset = match &ctx.args[1] {
EmValue::I32(o) => *o as usize,
_ => 0,
};
#[allow(clippy::cast_sign_loss)]
let count = match &ctx.args[2] {
EmValue::I32(c) => *c as usize,
_ => all_bytes.len(),
};
if offset + count <= all_bytes.len() {
all_bytes[offset..offset + count].to_vec()
} else {
all_bytes
}
} else {
all_bytes
};
if bytes.len() % 2 != 0 {
return PreHookResult::Bypass(Some(EmValue::Null));
}
let u16s: Vec<u16> = bytes
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect();
let s = String::from_utf16_lossy(&u16s);
let source = CaptureSource::new(
thread.current_method().unwrap_or(Token::new(0)),
thread.id(),
thread.current_offset().unwrap_or(0),
0,
);
thread.capture().capture_string(s.clone(), source);
match thread.heap_mut().alloc_string(&s) {
Ok(str_handle) => {
return PreHookResult::Bypass(Some(EmValue::ObjectRef(str_handle)))
}
Err(_) => return PreHookResult::Bypass(Some(EmValue::Null)),
}
}
}
PreHookResult::Bypass(Some(EmValue::Null))
}
#[cfg(test)]
mod tests {
use crate::{
emulation::{
runtime::{
bcl::text::{
ascii_get_bytes_pre, encoding_get_encoding_pre, register,
unicode_get_bytes_pre, unicode_get_string_pre, utf8_get_bytes_pre,
utf8_get_string_pre,
},
hook::{HookContext, HookManager, PreHookResult},
},
EmValue,
},
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(), 16);
}
#[test]
fn test_utf8_get_bytes_hook() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello").unwrap();
let args = [EmValue::ObjectRef(s)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Text",
"UTF8Encoding",
"GetBytes",
PointerSize::Bit64,
)
.with_args(&args);
let result = utf8_get_bytes_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_utf8_get_string_hook() {
let mut thread = create_test_thread();
let data = thread.heap_mut().alloc_byte_array(b"World").unwrap();
let args = [EmValue::ObjectRef(data)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Text",
"UTF8Encoding",
"GetString",
PointerSize::Bit64,
)
.with_args(&args);
let result = utf8_get_string_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))) = result {
assert_eq!(&*thread.heap().get_string(handle).unwrap(), "World");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_ascii_get_bytes_hook() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("Hello").unwrap();
let args = [EmValue::ObjectRef(s)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Text",
"ASCIIEncoding",
"GetBytes",
PointerSize::Bit64,
)
.with_args(&args);
let result = ascii_get_bytes_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_unicode_get_bytes_hook() {
let mut thread = create_test_thread();
let s = thread.heap_mut().alloc_string("AB").unwrap();
let args = [EmValue::ObjectRef(s)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Text",
"UnicodeEncoding",
"GetBytes",
PointerSize::Bit64,
)
.with_args(&args);
let result = unicode_get_bytes_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))) = result {
assert_eq!(
thread.heap().get_byte_array(handle),
Some(vec![0x41, 0x00, 0x42, 0x00])
);
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_unicode_get_string_hook() {
let mut thread = create_test_thread();
let data = thread
.heap_mut()
.alloc_byte_array(&[0x41, 0x00, 0x42, 0x00])
.unwrap();
let args = [EmValue::ObjectRef(data)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Text",
"UnicodeEncoding",
"GetString",
PointerSize::Bit64,
)
.with_args(&args);
let result = unicode_get_string_pre(&ctx, &mut thread);
if let PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))) = result {
assert_eq!(&*thread.heap().get_string(handle).unwrap(), "AB");
} else {
panic!("Expected Bypass with ObjectRef");
}
}
#[test]
fn test_get_encoding_hook() {
let mut thread = create_test_thread();
let args = [EmValue::I32(65001)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Text",
"Encoding",
"GetEncoding",
PointerSize::Bit64,
)
.with_args(&args);
let result = encoding_get_encoding_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
let args = [EmValue::I32(20127)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Text",
"Encoding",
"GetEncoding",
PointerSize::Bit64,
)
.with_args(&args);
let result = encoding_get_encoding_pre(&ctx, &mut thread);
assert!(matches!(
result,
PreHookResult::Bypass(Some(EmValue::ObjectRef(_)))
));
}
}