#[cfg(feature = "legacy-crypto")]
use crate::utils::derive_pbkdf1_key;
use crate::{
emulation::{
runtime::hook::{Hook, HookContext, HookManager, PreHookResult},
thread::EmulationThread,
EmValue,
},
utils::derive_pbkdf2_key,
Result,
};
pub fn register(manager: &HookManager) -> Result<()> {
#[cfg(feature = "legacy-crypto")]
manager.register(
Hook::new("System.Security.Cryptography.PasswordDeriveBytes..ctor")
.match_name(
"System.Security.Cryptography",
"PasswordDeriveBytes",
".ctor",
)
.pre(password_derive_bytes_ctor_pre),
)?;
#[cfg(feature = "legacy-crypto")]
manager.register(
Hook::new("System.Security.Cryptography.PasswordDeriveBytes.GetBytes")
.match_name(
"System.Security.Cryptography",
"PasswordDeriveBytes",
"GetBytes",
)
.pre(password_derive_bytes_get_bytes_pre),
)?;
manager.register(
Hook::new("System.Security.Cryptography.Rfc2898DeriveBytes..ctor")
.match_name(
"System.Security.Cryptography",
"Rfc2898DeriveBytes",
".ctor",
)
.pre(rfc2898_derive_bytes_ctor_pre),
)?;
manager.register(
Hook::new("System.Security.Cryptography.Rfc2898DeriveBytes.GetBytes")
.match_name(
"System.Security.Cryptography",
"Rfc2898DeriveBytes",
"GetBytes",
)
.pre(rfc2898_derive_bytes_get_bytes_pre),
)?;
Ok(())
}
#[cfg(feature = "legacy-crypto")]
fn password_derive_bytes_ctor_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let heap_ref = match ctx.this {
Some(EmValue::ObjectRef(hr)) => *hr,
_ => return PreHookResult::Bypass(None),
};
let password: Vec<u8> = match ctx.args.first() {
Some(EmValue::ObjectRef(pwd_ref)) => {
if let Some(s) = thread.heap().get_string_opt(*pwd_ref) {
s.as_bytes().to_vec()
} else {
try_hook!(thread.heap().get_byte_array(*pwd_ref)).unwrap_or_default()
}
}
_ => Vec::new(),
};
let salt: Vec<u8> = match ctx.args.get(1) {
Some(EmValue::ObjectRef(salt_ref)) => {
try_hook!(thread.heap().get_byte_array(*salt_ref)).unwrap_or_default()
}
_ => Vec::new(),
};
let iterations: u32 = 100;
try_hook!(thread
.heap()
.replace_with_key_derivation(heap_ref, password, salt, iterations, "SHA1"));
PreHookResult::Bypass(None)
}
#[cfg(feature = "legacy-crypto")]
fn password_derive_bytes_get_bytes_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let size = ctx
.args
.first()
.map(usize::try_from)
.transpose()
.ok()
.flatten()
.unwrap_or(16);
let heap_ref = if let Some(EmValue::ObjectRef(hr)) = ctx.this {
*hr
} else {
let zeros = vec![0u8; size];
match thread.heap().alloc_byte_array(&zeros) {
Ok(handle) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
};
let params = thread.heap().get_key_derivation_params(heap_ref);
let derived_key = match params {
Ok(Some((password, salt, iterations, _hash_algorithm))) => {
derive_pbkdf1_key(&password, &salt, iterations, size)
}
_ => vec![0u8; size],
};
match thread.heap().alloc_byte_array(&derived_key) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
fn rfc2898_derive_bytes_ctor_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let heap_ref = match ctx.this {
Some(EmValue::ObjectRef(hr)) => *hr,
_ => return PreHookResult::Bypass(None),
};
let password: Vec<u8> = match ctx.args.first() {
Some(EmValue::ObjectRef(pwd_ref)) => {
if let Some(s) = thread.heap().get_string_opt(*pwd_ref) {
s.as_bytes().to_vec()
} else {
try_hook!(thread.heap().get_byte_array(*pwd_ref)).unwrap_or_default()
}
}
_ => Vec::new(),
};
let salt: Vec<u8> = match ctx.args.get(1) {
Some(EmValue::ObjectRef(salt_ref)) => {
try_hook!(thread.heap().get_byte_array(*salt_ref)).unwrap_or_default()
}
_ => Vec::new(),
};
#[allow(clippy::cast_sign_loss)]
let iterations: u32 = match ctx.args.get(2) {
Some(val) => i32::try_from(val).unwrap_or(1000) as u32,
None => 1000,
};
let hash_algorithm = "SHA1";
try_hook!(thread.heap().replace_with_key_derivation(
heap_ref,
password,
salt,
iterations,
hash_algorithm,
));
PreHookResult::Bypass(None)
}
fn rfc2898_derive_bytes_get_bytes_pre(
ctx: &HookContext<'_>,
thread: &mut EmulationThread,
) -> PreHookResult {
let size = ctx
.args
.first()
.map(usize::try_from)
.transpose()
.ok()
.flatten()
.unwrap_or(16);
let heap_ref = if let Some(EmValue::ObjectRef(hr)) = ctx.this {
*hr
} else {
let zeros = vec![0u8; size];
match thread.heap().alloc_byte_array(&zeros) {
Ok(handle) => return PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(e) => return PreHookResult::Error(format!("heap allocation failed: {e}")),
}
};
let params = thread.heap().get_key_derivation_params(heap_ref);
let derived_key = match params {
Ok(Some((password, salt, iterations, hash_algorithm))) => {
derive_pbkdf2_key(&password, &salt, iterations, size, &hash_algorithm)
}
_ => vec![0u8; size],
};
match thread.heap().alloc_byte_array(&derived_key) {
Ok(handle) => PreHookResult::Bypass(Some(EmValue::ObjectRef(handle))),
Err(e) => PreHookResult::Error(format!("heap allocation failed: {e}")),
}
}
#[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_rfc2898_derive_bytes_get_bytes_hook() {
let mut thread = create_test_thread();
let args = [EmValue::I32(24)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Security.Cryptography",
"Rfc2898DeriveBytes",
"GetBytes",
PointerSize::Bit64,
)
.with_args(&args);
let result = super::rfc2898_derive_bytes_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.len(), 24);
} else {
panic!("Expected ObjectRef");
}
}
}
#[cfg(test)]
#[cfg(feature = "legacy-crypto")]
mod legacy_tests {
use crate::{
emulation::{
runtime::hook::{HookContext, PreHookResult},
EmValue,
},
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
#[test]
fn test_password_derive_bytes_get_bytes_hook() {
let mut thread = create_test_thread();
let args = [EmValue::I32(32)];
let ctx = HookContext::new(
Token::new(0x0A000001),
"System.Security.Cryptography",
"PasswordDeriveBytes",
"GetBytes",
PointerSize::Bit64,
)
.with_args(&args);
let result = super::password_derive_bytes_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.len(), 32);
} else {
panic!("Expected ObjectRef");
}
}
}