use alloc::string::String;
use core::{ffi::c_void, mem::zeroed, ptr::null_mut};
use uwd::AsPointer;
use anyhow::{Result, bail};
use obfstr::{obfstr as obf, obfstring as s};
use dinvk::winapis::{
NtCurrentProcess,
NtCurrentThread,
NT_SUCCESS
};
use dinvk::types::{
LARGE_INTEGER, CONTEXT,
EVENT_ALL_ACCESS, EVENT_TYPE,
NTSTATUS
};
use crate::{types::*, winapis::*};
use crate::config::{Config, init_config, current_rsp};
use crate::gadget::GadgetContext;
use crate::allocator::HypnusHeap;
#[macro_export]
macro_rules! timer {
($base:expr, $size:expr, $time:expr) => {
$crate::__private::hypnus_entry(
$base,
$size,
$time,
$crate::Obfuscation::Timer,
$crate::ObfMode::None
)
};
($base:expr, $size:expr, $time:expr, $mode:expr) => {
$crate::__private::hypnus_entry(
$base,
$size,
$time,
$crate::Obfuscation::Timer,
$mode
)
};
}
#[macro_export]
macro_rules! wait {
($base:expr, $size:expr, $time:expr) => {
$crate::__private::hypnus_entry(
$base,
$size,
$time,
$crate::Obfuscation::Wait,
$crate::ObfMode::None
)
};
($base:expr, $size:expr, $time:expr, $mode:expr) => {
$crate::__private::hypnus_entry(
$base,
$size,
$time,
$crate::Obfuscation::Wait,
$mode
)
};
}
#[macro_export]
macro_rules! foliage {
($base:expr, $size:expr, $time:expr) => {
$crate::__private::hypnus_entry(
$base,
$size,
$time,
$crate::Obfuscation::Foliage,
$crate::ObfMode::None
)
};
($base:expr, $size:expr, $time:expr, $mode:expr) => {
$crate::__private::hypnus_entry(
$base,
$size,
$time,
$crate::Obfuscation::Foliage,
$mode
)
};
}
pub enum Obfuscation {
Timer,
Wait,
Foliage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct ObfMode(pub u32);
impl ObfMode {
pub const None: Self = ObfMode(0b0000);
pub const Heap: Self = ObfMode(0b0001);
pub const Rwx: Self = ObfMode(0b0010);
fn contains(self, other: ObfMode) -> bool {
(self.0 & other.0) == other.0
}
}
impl core::ops::BitOr for ObfMode {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
ObfMode(self.0 | rhs.0)
}
}
#[derive(Clone, Copy, Debug)]
struct Hypnus {
base: u64,
size: u64,
time: u64,
cfg: &'static Config,
mode: ObfMode,
}
impl Hypnus {
#[inline]
fn new(base: u64, size: u64, time: u64, mode: ObfMode) -> Result<Self> {
if base == 0 || size == 0 || time == 0 {
bail!(s!("invalid arguments"))
}
Ok(Self {
base,
size,
time,
mode,
cfg: init_config()?,
})
}
fn timer(&mut self) -> Result<()> {
unsafe {
let heap = self.mode.contains(ObfMode::Heap);
let protection = if self.mode.contains(ObfMode::Rwx) {
PAGE_EXECUTE_READWRITE
} else {
PAGE_EXECUTE_READ
};
let mut events = [null_mut(); 3];
for event in &mut events {
let status = NtCreateEvent(
event,
EVENT_ALL_ACCESS,
null_mut(),
EVENT_TYPE::NotificationEvent,
0
);
if !NT_SUCCESS(status) {
bail!(s!("NtCreateEvent Failed"));
}
}
let mut pool = null_mut();
let mut status = TpAllocPool(&mut pool, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("TpAllocPool Failed"));
}
let mut stack = TP_POOL_STACK_INFORMATION { StackCommit: 0x80000, StackReserve: 0x80000 };
status = TpSetPoolStackInformation(pool, &mut stack);
if !NT_SUCCESS(status) {
bail!(s!("TpSetPoolStackInformation Failed"));
}
TpSetPoolMinThreads(pool, 1);
TpSetPoolMaxThreads(pool, 1);
let mut env = TP_CALLBACK_ENVIRON_V3 { Pool: pool, ..Default::default() };
let mut timer_ctx = null_mut();
let mut ctx_init = CONTEXT {
ContextFlags: CONTEXT_FULL,
P1Home: self.cfg.rtl_capture_context.as_u64(),
..Default::default()
};
status = TpAllocTimer(
&mut timer_ctx,
self.cfg.trampoline as *mut c_void,
&mut ctx_init as *mut _ as *mut c_void,
&mut env
);
if !NT_SUCCESS(status) {
bail!(s!("TpAllocTimer [RtlCaptureContext] Failed"));
}
let mut delay = zeroed::<LARGE_INTEGER>();
delay.QuadPart = -(100i64 * 10_000);
TpSetTimer(timer_ctx, &mut delay, 0, 0);
let mut timer_event = null_mut();
status = TpAllocTimer(
&mut timer_event,
NtSetEvent2 as *mut c_void,
events[0],
&mut env
);
if !NT_SUCCESS(status) {
bail!(s!("TpAllocTimer [NtSetEvent] Failed"));
}
delay.QuadPart = -(200i64 * 10_000);
TpSetTimer(timer_event, &mut delay, 0, 0);
status = NtWaitForSingleObject(events[0], 0, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("NtWaitForSingleObject Failed"));
}
let mut ctxs = [ctx_init; 10];
for ctx in &mut ctxs {
ctx.Rax = self.cfg.nt_continue.as_u64();
ctx.Rsp -= 8;
}
let mut h_thread = null_mut();
status = NtDuplicateObject(
NtCurrentProcess(),
NtCurrentThread(),
NtCurrentProcess(),
&mut h_thread,
0,
0,
DUPLICATE_SAME_ACCESS,
);
if !NT_SUCCESS(status) {
bail!(s!("NtDuplicateObject Failed"));
}
ctx_init.Rsp = current_rsp();
let mut ctx_spoof = self.cfg.stack.spoof_context(self.cfg, ctx_init);
ctxs[0].jmp(self.cfg, self.cfg.nt_wait_for_single.into());
ctxs[0].Rcx = events[1] as u64;
ctxs[0].Rdx = 0;
ctxs[0].R8 = 0;
let mut old_protect = 0u32;
let (mut base, mut size) = (self.base, self.size);
ctxs[1].jmp(self.cfg, self.cfg.nt_protect_virtual_memory.into());
ctxs[1].Rcx = NtCurrentProcess() as u64;
ctxs[1].Rdx = base.as_u64();
ctxs[1].R8 = size.as_u64();
ctxs[1].R9 = PAGE_READWRITE as u64;
ctxs[2].jmp(self.cfg, self.cfg.system_function040.into());
ctxs[2].Rcx = base;
ctxs[2].Rdx = size;
ctxs[2].R8 = 0;
let mut ctx_backup = CONTEXT { ContextFlags: CONTEXT_FULL, ..Default::default() };
ctxs[3].jmp(self.cfg, self.cfg.nt_get_context_thread.into());
ctxs[3].Rcx = h_thread as u64;
ctxs[3].Rdx = ctx_backup.as_u64();
ctxs[4].jmp(self.cfg, self.cfg.nt_set_context_thread.into());
ctxs[4].Rcx = h_thread as u64;
ctxs[4].Rdx = ctx_spoof.as_u64();
ctxs[5].jmp(self.cfg, self.cfg.wait_for_single.into());
ctxs[5].Rcx = h_thread as u64;
ctxs[5].Rdx = self.time * 1000;
ctxs[5].R8 = 0;
ctxs[6].jmp(self.cfg, self.cfg.system_function041.into());
ctxs[6].Rcx = base;
ctxs[6].Rdx = size;
ctxs[6].R8 = 0;
ctxs[7].jmp(self.cfg, self.cfg.nt_protect_virtual_memory.into());
ctxs[7].Rcx = NtCurrentProcess() as u64;
ctxs[7].Rdx = base.as_u64();
ctxs[7].R8 = size.as_u64();
ctxs[7].R9 = protection;
ctxs[8].jmp(self.cfg, self.cfg.nt_set_context_thread.into());
ctxs[8].Rcx = h_thread as u64;
ctxs[8].Rdx = ctx_backup.as_u64();
ctxs[9].jmp(self.cfg, self.cfg.nt_set_event.into());
ctxs[9].Rcx = events[2] as u64;
ctxs[9].Rdx = 0;
self.cfg.stack.spoof(&mut ctxs, self.cfg, Obfuscation::Timer)?;
((ctxs[1].Rsp + 0x28) as *mut u64).write(old_protect.as_u64());
((ctxs[7].Rsp + 0x28) as *mut u64).write(old_protect.as_u64());
for ctx in &mut ctxs {
let mut timer = null_mut();
status = TpAllocTimer(
&mut timer,
self.cfg.callback as *mut c_void,
ctx as *mut _ as *mut c_void,
&mut env
);
if !NT_SUCCESS(status) {
bail!(s!("TpAllocTimer Failed"));
}
delay.QuadPart += -(100_i64 * 10_000);
TpSetTimer(timer, &mut delay, 0, 0);
}
let key = if heap {
let key = core::arch::x86_64::_rdtsc().to_le_bytes();
obfuscate_heap(&key);
Some(key)
} else {
None
};
status = NtSignalAndWaitForSingleObject(events[1], events[2], 0, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("NtSignalAndWaitForSingleObject Failed"));
}
if let Some(key) = key {
obfuscate_heap(&key);
}
NtClose(h_thread);
CloseThreadpool(pool);
events.iter().for_each(|h| {
NtClose(*h);
});
Ok(())
}
}
fn wait(&mut self) -> Result<()> {
unsafe {
let heap = self.mode.contains(ObfMode::Heap);
let protection = if self.mode.contains(ObfMode::Rwx) {
PAGE_EXECUTE_READWRITE
} else {
PAGE_EXECUTE_READ
};
let mut events = [null_mut(); 4];
for event in &mut events {
let status = NtCreateEvent(
event,
EVENT_ALL_ACCESS,
null_mut(),
EVENT_TYPE::NotificationEvent,
0
);
if !NT_SUCCESS(status) {
bail!(s!("NtCreateEvent Failed"));
}
}
let mut pool = null_mut();
let mut status = TpAllocPool(&mut pool, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("TpAllocPool Failed"));
}
let mut stack = TP_POOL_STACK_INFORMATION { StackCommit: 0x80000, StackReserve: 0x80000 };
status = TpSetPoolStackInformation(pool, &mut stack);
if !NT_SUCCESS(status) {
bail!(s!("TpSetPoolStackInformation Failed"));
}
TpSetPoolMinThreads(pool, 1);
TpSetPoolMaxThreads(pool, 1);
let mut env = TP_CALLBACK_ENVIRON_V3 { Pool: pool, ..Default::default() };
let mut wait_ctx = null_mut();
let mut ctx_init = CONTEXT {
ContextFlags: CONTEXT_FULL,
P1Home: self.cfg.rtl_capture_context.as_u64(),
..Default::default()
};
status = TpAllocWait(
&mut wait_ctx,
self.cfg.trampoline as *mut c_void,
&mut ctx_init as *mut _ as *mut c_void,
&mut env
);
if !NT_SUCCESS(status) {
bail!(s!("TpAllocWait [RtlCaptureContext] Failed"));
}
let mut delay = zeroed::<LARGE_INTEGER>();
delay.QuadPart = -(100i64 * 10_000);
TpSetWait(wait_ctx, events[0], &mut delay);
let mut wait_event = null_mut();
status = TpAllocWait(
&mut wait_event,
NtSetEvent2 as *mut c_void,
events[1],
&mut env
);
if !NT_SUCCESS(status) {
bail!(s!("TpAllocWait [NtSetEvent] Failed"));
}
delay.QuadPart = -(200i64 * 10_000);
TpSetWait(wait_event, events[0], &mut delay);
status = NtWaitForSingleObject(events[1], 0, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("NtWaitForSingleObject Failed"));
}
let mut ctxs = [ctx_init; 10];
for ctx in &mut ctxs {
ctx.Rax = self.cfg.nt_continue.as_u64();
ctx.Rsp -= 8;
}
let mut h_thread = null_mut();
status = NtDuplicateObject(
NtCurrentProcess(),
NtCurrentThread(),
NtCurrentProcess(),
&mut h_thread,
0,
0,
DUPLICATE_SAME_ACCESS,
);
if !NT_SUCCESS(status) {
bail!(s!("NtDuplicateObject Failed"));
}
ctx_init.Rsp = current_rsp();
let mut ctx_spoof = self.cfg.stack.spoof_context(self.cfg, ctx_init);
ctxs[0].jmp(self.cfg, self.cfg.nt_wait_for_single.into());
ctxs[0].Rcx = events[2] as u64;
ctxs[0].Rdx = 0;
ctxs[0].R8 = 0;
let mut old_protect = 0u32;
let (mut base, mut size) = (self.base, self.size);
ctxs[1].jmp(self.cfg, self.cfg.nt_protect_virtual_memory.into());
ctxs[1].Rcx = NtCurrentProcess() as u64;
ctxs[1].Rdx = base.as_u64();
ctxs[1].R8 = size.as_u64();
ctxs[1].R9 = PAGE_READWRITE as u64;
ctxs[2].jmp(self.cfg, self.cfg.system_function040.into());
ctxs[2].Rcx = base;
ctxs[2].Rdx = size;
ctxs[2].R8 = 0;
let mut ctx_backup = CONTEXT { ContextFlags: CONTEXT_FULL, ..Default::default() };
ctxs[3].jmp(self.cfg, self.cfg.nt_get_context_thread.into());
ctxs[3].Rcx = h_thread as u64;
ctxs[3].Rdx = ctx_backup.as_u64();
ctxs[4].jmp(self.cfg, self.cfg.nt_set_context_thread.into());
ctxs[4].Rcx = h_thread as u64;
ctxs[4].Rdx = ctx_spoof.as_u64();
ctxs[5].jmp(self.cfg, self.cfg.wait_for_single.into());
ctxs[5].Rcx = h_thread as u64;
ctxs[5].Rdx = self.time * 1000;
ctxs[5].R8 = 0;
ctxs[6].jmp(self.cfg, self.cfg.system_function041.into());
ctxs[6].Rcx = base;
ctxs[6].Rdx = size;
ctxs[6].R8 = 0;
ctxs[7].jmp(self.cfg, self.cfg.nt_protect_virtual_memory.into());
ctxs[7].Rcx = NtCurrentProcess() as u64;
ctxs[7].Rdx = base.as_u64();
ctxs[7].R8 = size.as_u64();
ctxs[7].R9 = protection;
ctxs[8].jmp(self.cfg, self.cfg.nt_set_context_thread.into());
ctxs[8].Rcx = h_thread as u64;
ctxs[8].Rdx = ctx_backup.as_u64();
ctxs[9].jmp(self.cfg, self.cfg.nt_set_event.into());
ctxs[9].Rcx = events[3] as u64;
ctxs[9].Rdx = 0;
self.cfg.stack.spoof(&mut ctxs, self.cfg, Obfuscation::Wait)?;
((ctxs[1].Rsp + 0x28) as *mut u64).write(old_protect.as_u64());
((ctxs[7].Rsp + 0x28) as *mut u64).write(old_protect.as_u64());
for ctx in &mut ctxs {
let mut wait = null_mut();
status = TpAllocWait(
&mut wait,
self.cfg.callback as *mut c_void,
ctx as *mut _ as *mut c_void,
&mut env
);
if !NT_SUCCESS(status) {
bail!(s!("TpAllocWait Failed"));
}
delay.QuadPart += -(100_i64 * 10_000);
TpSetWait(wait, events[0], &mut delay);
}
let key = if heap {
let key = core::arch::x86_64::_rdtsc().to_le_bytes();
obfuscate_heap(&key);
Some(key)
} else {
None
};
status = NtSignalAndWaitForSingleObject(events[2], events[3], 0, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("NtSignalAndWaitForSingleObject Failed"));
}
if let Some(key) = key {
obfuscate_heap(&key);
}
NtClose(h_thread);
CloseThreadpool(pool);
events.iter().for_each(|h| {
NtClose(*h);
});
Ok(())
}
}
fn foliage(&mut self) -> Result<()> {
unsafe {
let heap = self.mode.contains(ObfMode::Heap);
let protection = if self.mode.contains(ObfMode::Rwx) {
PAGE_EXECUTE_READWRITE
} else {
PAGE_EXECUTE_READ
};
let mut event = null_mut();
let mut status = NtCreateEvent(
&mut event,
EVENT_ALL_ACCESS,
null_mut(),
EVENT_TYPE::SynchronizationEvent,
0
);
if !NT_SUCCESS(status) {
bail!(s!("NtCreateEvent Failed"));
}
let mut h_thread = null_mut::<c_void>();
status = uwd::syscall!(
obf!("NtCreateThreadEx"),
h_thread.as_ptr_mut(),
THREAD_ALL_ACCESS,
null_mut::<c_void>(),
NtCurrentProcess(),
(self.cfg.tp_release_cleanup.as_ptr()).add(0x250),
null_mut::<c_void>(),
1,
0,
0x1000 * 20,
0x1000 * 20,
null_mut::<c_void>()
)? as NTSTATUS;
if !NT_SUCCESS(status) {
bail!(s!("NtCreateThreadEx Failed"));
}
let mut ctx_init = CONTEXT { ContextFlags: CONTEXT_FULL, ..Default::default() };
status = uwd::syscall!(obf!("NtGetContextThread"), h_thread, ctx_init.as_ptr_mut())? as NTSTATUS;
if !NT_SUCCESS(status) {
bail!(s!("NtGetContextThread Failed"));
}
let mut ctxs = [ctx_init; 10];
let mut thread = null_mut();
status = NtDuplicateObject(
NtCurrentProcess(),
NtCurrentThread(),
NtCurrentProcess(),
&mut thread,
0,
0,
DUPLICATE_SAME_ACCESS,
);
if !NT_SUCCESS(status) {
bail!(s!("NtDuplicateObject Failed"));
}
ctx_init.Rsp = current_rsp();
let mut ctx_spoof = self.cfg.stack.spoof_context(self.cfg, ctx_init);
ctxs[0].Rip = self.cfg.nt_wait_for_single.into();
ctxs[0].Rcx = event as u64;
ctxs[0].Rdx = 0;
ctxs[0].R8 = 0;
let mut old_protect = 0u32;
let (mut base, mut size) = (self.base, self.size);
ctxs[1].Rip = self.cfg.nt_protect_virtual_memory.into();
ctxs[1].Rcx = NtCurrentProcess() as u64;
ctxs[1].Rdx = base.as_u64();
ctxs[1].R8 = size.as_u64();
ctxs[1].R9 = PAGE_READWRITE as u64;
ctxs[2].Rip = self.cfg.system_function040.into();
ctxs[2].Rcx = base;
ctxs[2].Rdx = size;
ctxs[2].R8 = 0;
let mut ctx_backup = CONTEXT { ContextFlags: CONTEXT_FULL, ..Default::default() };
ctxs[3].Rip = self.cfg.nt_get_context_thread.into();
ctxs[3].Rcx = thread as u64;
ctxs[3].Rdx = ctx_backup.as_u64();
ctxs[4].Rip = self.cfg.nt_set_context_thread.into();
ctxs[4].Rcx = thread as u64;
ctxs[4].Rdx = ctx_spoof.as_u64();
ctxs[5].Rip = self.cfg.wait_for_single.into();
ctxs[5].Rcx = thread as u64;
ctxs[5].Rdx = self.time * 1000;
ctxs[5].R8 = 0;
ctxs[6].Rip = self.cfg.system_function041.into();
ctxs[6].Rcx = base;
ctxs[6].Rdx = size;
ctxs[6].R8 = 0;
ctxs[7].Rip = self.cfg.nt_protect_virtual_memory.into();
ctxs[7].Rcx = NtCurrentProcess() as u64;
ctxs[7].Rdx = base.as_u64();
ctxs[7].R8 = size.as_u64();
ctxs[7].R9 = protection;
ctxs[8].Rip = self.cfg.nt_set_context_thread.into();
ctxs[8].Rcx = thread as u64;
ctxs[8].Rdx = ctx_backup.as_u64();
ctxs[9].Rip = self.cfg.rtl_exit_user_thread.into();
ctxs[9].Rcx = h_thread as u64;
ctxs[9].Rdx = 0;
self.cfg.stack.spoof(&mut ctxs, self.cfg, Obfuscation::Foliage)?;
((ctxs[1].Rsp + 0x28) as *mut u64).write(old_protect.as_u64());
((ctxs[7].Rsp + 0x28) as *mut u64).write(old_protect.as_u64());
for ctx in &mut ctxs {
status = NtQueueApcThread(
h_thread,
self.cfg.nt_continue.as_ptr().cast_mut(),
ctx as *mut _ as *mut c_void,
null_mut(),
null_mut(),
);
if !NT_SUCCESS(status) {
bail!(s!("NtQueueApcThread Failed"));
}
}
status = NtAlertResumeThread(h_thread, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("NtAlertResumeThread Failed"));
}
let key = if heap {
let key = core::arch::x86_64::_rdtsc().to_le_bytes();
obfuscate_heap(&key);
Some(key)
} else {
None
};
status = NtSignalAndWaitForSingleObject(event, h_thread, 0, null_mut());
if !NT_SUCCESS(status) {
bail!(s!("NtSignalAndWaitForSingleObject Failed"));
}
if let Some(key) = key {
obfuscate_heap(&key);
}
NtClose(event);
NtClose(h_thread);
NtClose(thread);
}
Ok(())
}
}
#[doc(hidden)]
pub mod __private {
use alloc::boxed::Box;
use super::*;
pub fn hypnus_entry(base: *mut c_void, size: u64, time: u64, obf: Obfuscation, mode: ObfMode) {
let master = ConvertThreadToFiber(null_mut());
if master.is_null() {
return;
}
match Hypnus::new(base as u64, size, time, mode) {
Ok(hypnus) => {
let fiber_ctx = Box::new(FiberContext {
hypnus: Box::new(hypnus),
obf,
master,
});
let fiber = CreateFiber(
0x100000,
Some(hypnus_fiber),
Box::into_raw(fiber_ctx).cast()
);
if fiber.is_null() {
return;
}
SwitchToFiber(fiber);
DeleteFiber(fiber);
ConvertFiberToThread();
}
Err(_error) => {
#[cfg(debug_assertions)]
dinvk::println!("[Hypnus::new] {:?}", _error);
}
}
}
struct FiberContext {
hypnus: Box<Hypnus>,
obf: Obfuscation,
master: *mut c_void,
}
extern "system" fn hypnus_fiber(ctx: *mut c_void) {
unsafe {
let mut ctx = Box::from_raw(ctx as *mut FiberContext);
let _result = match ctx.obf {
Obfuscation::Timer => ctx.hypnus.timer(),
Obfuscation::Wait => ctx.hypnus.wait(),
Obfuscation::Foliage => ctx.hypnus.foliage(),
};
#[cfg(debug_assertions)]
if let Err(_error) = _result {
dinvk::println!("[Hypnus] {:?}", _error);
}
SwitchToFiber(ctx.master);
}
}
}
trait Asu64 {
fn as_u64(&mut self) -> u64;
}
impl<T> Asu64 for T {
fn as_u64(&mut self) -> u64 {
self as *mut _ as *mut c_void as u64
}
}
fn obfuscate_heap(key: &[u8; 8]) {
let heap = HypnusHeap::get();
if heap.is_null() {
return;
}
let mut entry = unsafe { zeroed::<RTL_HEAP_WALK_ENTRY>() };
while RtlWalkHeap(heap, &mut entry) != 0 {
if entry.Flags & 4 != 0 {
xor(entry.DataAddress as *mut u8, entry.DataSize, key);
}
}
}
fn xor(data: *mut u8, len: usize, key: &[u8; 8]) {
if data.is_null() {
return;
}
for i in 0..len {
unsafe {
*data.add(i) ^= key[i % key.len()];
}
}
}