use alloc::{string::String, vec::Vec};
use core::ffi::c_void;
use anyhow::{Context, Result, bail};
use obfstr::obfstring as s;
use dinvk::module::{get_module_address, get_proc_address};
use dinvk::types::IMAGE_RUNTIME_FUNCTION;
use dinvk::hash::murmur3;
use dinvk::helper::PE;
#[cfg(feature = "desync")]
use crate::util::find_base_thread_return_address;
use crate::util::{find_gadget, shuffle, find_valid_instruction_offset};
use crate::types::{Config, Registers, UNWIND_OP_CODES::{self, *}};
use crate::types::{UNW_FLAG_CHAININFO, UNW_FLAG_EHANDLER};
use crate::types::{UNWIND_CODE, UNWIND_INFO};
use crate::types::Unwind;
#[cfg(feature = "desync")]
unsafe extern "C" {
fn Spoof(config: &mut Config) -> *mut c_void;
}
#[cfg(not(feature = "desync"))]
unsafe extern "C" {
fn SpoofSynthetic(config: &mut Config) -> *mut c_void;
}
#[macro_export]
macro_rules! spoof {
($addr:expr, $($arg:expr),+ $(,)?) => {
unsafe {
$crate::__private::spoof(
$addr,
&[$(::core::mem::transmute($arg as usize)),*],
$crate::SpoofKind::Function,
)
}
};
}
#[macro_export]
macro_rules! syscall {
($name:expr, $($arg:expr),* $(,)?) => {
unsafe {
$crate::__private::spoof(
core::ptr::null_mut(),
&[$(::core::mem::transmute($arg as usize)),*],
$crate::SpoofKind::Syscall($name),
)
}
};
}
#[doc(hidden)]
pub mod __private {
use core::ffi::c_void;
use super::*;
#[cfg(not(feature = "desync"))]
pub fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind) -> Result<*mut c_void> {
if args.len() > 11 {
bail!(s!("too many arguments"));
}
if let SpoofKind::Function = kind && addr.is_null() {
bail!(s!("null function address"));
}
let mut config = Config::default();
let kernelbase = get_module_address(2737729883u32, Some(murmur3));
let pe_kernelbase = Unwind::new(PE::parse(kernelbase));
let tables = pe_kernelbase
.entries()
.context(s!(
"failed to read IMAGE_RUNTIME_FUNCTION entries from .pdata section"
))?;
let ntdll = get_module_address(2788516083u32, Some(murmur3));
if ntdll.is_null() {
bail!(s!("ntdll.dll not found"));
}
let kernel32 = get_module_address(2808682670u32, Some(murmur3));
let rlt_user_addr = get_proc_address(ntdll, 1578834099u32, Some(murmur3));
let base_thread_addr = get_proc_address(kernel32, 4083630997u32, Some(murmur3));
config.rtl_user_addr = rlt_user_addr;
config.base_thread_addr = base_thread_addr;
let pe_ntdll = Unwind::new(PE::parse(ntdll));
let rtl_user_runtime = pe_ntdll
.function_by_offset(rlt_user_addr as u32 - ntdll as u32)
.context(s!("RtlUserThreadStart unwind info not found"))?;
let pe_kernel32 = Unwind::new(PE::parse(kernel32));
let base_thread_runtime = pe_kernel32
.function_by_offset(base_thread_addr as u32 - kernel32 as u32)
.context(s!("BaseThreadInitThunk unwind info not found"))?;
let rtl_user_size = ignoring_set_fpreg(ntdll, rtl_user_runtime)
.context(s!("RtlUserThreadStart stack size not found"))?;
let base_thread_size = ignoring_set_fpreg(kernel32, base_thread_runtime)
.context(s!("BaseThreadInitThunk stack size not found"))?;
config.rtl_user_thread_size = rtl_user_size as u64;
config.base_thread_size = base_thread_size as u64;
let first_prolog = Prolog::find_prolog(kernelbase, tables)
.context(s!("first prolog not found"))?;
config.first_frame_fp = (first_prolog.frame + first_prolog.offset as u64) as *const c_void;
config.first_frame_size = first_prolog.stack_size as u64;
let second_prolog = Prolog::find_push_rbp(kernelbase, tables)
.context(s!("second prolog not found"))?;
config.second_frame_fp = (second_prolog.frame + second_prolog.offset as u64) as *const c_void;
config.second_frame_size = second_prolog.stack_size as u64;
config.rbp_stack_offset = second_prolog.rbp_offset as u64;
let (add_rsp_addr, size) = find_gadget(kernelbase, &[0x48, 0x83, 0xC4, 0x58, 0xC3], tables)
.context(s!("add rsp gadget not found"))?;
config.add_rsp_gadget = add_rsp_addr as *const c_void;
config.add_rsp_frame_size = size as u64;
let (jmp_rbx_addr, size) = find_gadget(kernelbase, &[0xFF, 0x23], tables)
.context(s!("jmp rbx gadget not found"))?;
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
config.jmp_rbx_frame_size = size as u64;
let len = args.len();
config.number_args = len as u32;
for (i, &arg) in args.iter().take(len).enumerate() {
match i {
0 => config.arg01 = arg,
1 => config.arg02 = arg,
2 => config.arg03 = arg,
3 => config.arg04 = arg,
4 => config.arg05 = arg,
5 => config.arg06 = arg,
6 => config.arg07 = arg,
7 => config.arg08 = arg,
8 => config.arg09 = arg,
9 => config.arg10 = arg,
10 => config.arg11 = arg,
_ => break,
}
}
match kind {
SpoofKind::Function => config.spoof_function = addr,
SpoofKind::Syscall(name) => {
let addr = get_proc_address(ntdll, name, None);
if addr.is_null() {
bail!(s!("get_proc_address returned null"));
}
config.is_syscall = true as u32;
config.ssn = dinvk::ssn(name, ntdll).context(s!("ssn not found"))?;
config.spoof_function = dinvk::get_syscall_address(addr)
.context(s!("syscall address not found"))? as *const c_void;
}
}
Ok(unsafe { SpoofSynthetic(&mut config) })
}
#[cfg(feature = "desync")]
pub fn spoof(addr: *mut c_void, args: &[*const c_void], kind: SpoofKind) -> Result<*mut c_void> {
if args.len() > 11 {
bail!(s!("too many arguments"));
}
if let SpoofKind::Function = kind && addr.is_null() {
bail!(s!("null function address"));
}
let mut config = Config::default();
let kernelbase = get_module_address(2737729883u32, Some(murmur3));
let pe = Unwind::new(PE::parse(kernelbase));
let tables = pe
.entries()
.context(s!(
"failed to read IMAGE_RUNTIME_FUNCTION entries from .pdata section"
))?;
config.return_address = find_base_thread_return_address()
.context(s!("return address not found"))? as *const c_void;
let first_prolog = Prolog::find_prolog(kernelbase, tables)
.context(s!("first prolog not found"))?;
config.first_frame_fp = (first_prolog.frame + first_prolog.offset as u64) as *const c_void;
config.first_frame_size = first_prolog.stack_size as u64;
let second_prolog = Prolog::find_push_rbp(kernelbase, tables)
.context(s!("second prolog not found"))?;
config.second_frame_fp = (second_prolog.frame + second_prolog.offset as u64) as *const c_void;
config.second_frame_size = second_prolog.stack_size as u64;
config.rbp_stack_offset = second_prolog.rbp_offset as u64;
let (add_rsp_addr, size) = find_gadget(kernelbase, &[0x48, 0x83, 0xC4, 0x58, 0xC3], tables)
.context(s!("add rsp gadget not found"))?;
config.add_rsp_gadget = add_rsp_addr as *const c_void;
config.add_rsp_frame_size = size as u64;
let (jmp_rbx_addr, size) = find_gadget(kernelbase, &[0xFF, 0x23], tables)
.context(s!("jmp rbx gadget not found"))?;
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
config.jmp_rbx_frame_size = size as u64;
let len = args.len();
config.number_args = len as u32;
for (i, &arg) in args.iter().take(len).enumerate() {
match i {
0 => config.arg01 = arg,
1 => config.arg02 = arg,
2 => config.arg03 = arg,
3 => config.arg04 = arg,
4 => config.arg05 = arg,
5 => config.arg06 = arg,
6 => config.arg07 = arg,
7 => config.arg08 = arg,
8 => config.arg09 = arg,
9 => config.arg10 = arg,
10 => config.arg11 = arg,
_ => break,
}
}
match kind {
SpoofKind::Function => config.spoof_function = addr,
SpoofKind::Syscall(name) => {
let ntdll = get_module_address(2788516083u32, Some(murmur3));
if ntdll.is_null() {
bail!(s!("ntdll.dll not found"));
}
let addr = get_proc_address(ntdll, name, None);
if addr.is_null() {
bail!(s!("get_proc_address returned null"));
}
config.is_syscall = true as u32;
config.ssn = dinvk::ssn(name, ntdll).context(s!("ssn not found"))?;
config.spoof_function = dinvk::get_syscall_address(addr)
.context(s!("syscall address not found"))? as *const c_void;
}
}
Ok(unsafe { Spoof(&mut config) })
}
}
#[derive(Copy, Clone, Default)]
struct Prolog {
frame: u64,
stack_size: u32,
offset: u32,
rbp_offset: u32,
}
impl Prolog {
fn find_prolog(module_base: *mut c_void, runtime_table: &[IMAGE_RUNTIME_FUNCTION]) -> Option<Self> {
let mut prologs = runtime_table
.iter()
.filter_map(|runtime| {
let (is_valid, stack_size) = stack_frame(module_base, runtime)?;
if !is_valid {
return None;
}
let offset = find_valid_instruction_offset(module_base, runtime)?;
let frame = module_base as u64 + runtime.BeginAddress as u64;
Some(Self {
frame,
stack_size,
offset,
..Default::default()
})
})
.collect::<Vec<Self>>();
if prologs.is_empty() {
return None;
}
shuffle(&mut prologs);
prologs.first().copied()
}
fn find_push_rbp(module_base: *mut c_void, runtime_table: &[IMAGE_RUNTIME_FUNCTION]) -> Option<Self> {
let mut prologs = runtime_table
.iter()
.filter_map(|runtime| {
let (rbp_offset, stack_size) = rbp_offset(module_base, runtime)?;
if rbp_offset == 0 || stack_size == 0 || stack_size <= rbp_offset {
return None;
}
let offset = find_valid_instruction_offset(module_base, runtime)?;
let frame = module_base as u64 + runtime.BeginAddress as u64;
Some(
Self {
frame,
stack_size,
offset,
rbp_offset,
}
)
})
.collect::<Vec<Self>>();
if prologs.is_empty() {
return None;
}
prologs.remove(0);
shuffle(&mut prologs);
prologs.first().copied()
}
}
pub fn rbp_offset(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Option<(u32, u32)> {
unsafe {
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UNWIND_INFO;
let unwind_code = (unwind_info as *mut u8).add(4) as *mut UNWIND_CODE;
let flag = (*unwind_info).VersionFlags.Flags();
let mut i = 0usize;
let mut total_stack = 0u32;
let mut rbp_pushed = false;
let mut stack_offset = 0;
while i < (*unwind_info).CountOfCodes as usize {
let unwind_code = unwind_code.add(i);
let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
let unwind_op = (*unwind_code).Anonymous.UnwindOp();
match UNWIND_OP_CODES::try_from(unwind_op) {
Ok(UWOP_PUSH_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
if Registers::Rbp == op_info {
if rbp_pushed {
return None;
}
rbp_pushed = true;
stack_offset = total_stack;
}
total_stack += 8;
i += 1;
}
Ok(UWOP_ALLOC_LARGE) => {
if (*unwind_code).Anonymous.OpInfo() == 0 {
let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
total_stack += frame_offset as u32;
i += 2
} else {
let frame_offset = *(unwind_code.add(1) as *mut i32);
total_stack += frame_offset as u32;
i += 3
}
}
Ok(UWOP_ALLOC_SMALL) => {
total_stack += ((op_info + 1) * 8) as u32;
i += 1;
}
Ok(UWOP_SAVE_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
if Registers::Rbp == op_info {
if rbp_pushed {
return None;
}
let offset = (*unwind_code.add(1)).FrameOffset * 8;
stack_offset = total_stack + offset as u32;
rbp_pushed = true;
}
i += 2;
}
Ok(UWOP_SAVE_NONVOL_BIG) => {
if Registers::Rsp == op_info {
return None;
}
if Registers::Rbp == op_info {
if rbp_pushed {
return None;
}
let offset = *(unwind_code.add(1) as *mut u32);
stack_offset = total_stack + offset;
rbp_pushed = true;
}
i += 3;
}
Ok(UWOP_SET_FPREG) => return None,
Ok(UWOP_SAVE_XMM128) => i += 2,
Ok(UWOP_SAVE_XMM128BIG) => i += 3,
Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
Ok(UWOP_PUSH_MACH_FRAME) => {
total_stack += if op_info == 0 { 0x40 } else { 0x48 };
i += 1
}
_ => {}
}
}
if (flag & UNW_FLAG_CHAININFO) != 0 {
let count = (*unwind_info).CountOfCodes as usize;
let index = if count & 1 == 1 { count + 1 } else { count };
let runtime = unwind_code.add(index) as *const IMAGE_RUNTIME_FUNCTION;
if let Some((_, child_total)) = rbp_offset(module, &*runtime) {
total_stack += child_total;
} else {
return None;
}
}
Some((stack_offset, total_stack))
}
}
pub fn stack_frame(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Option<(bool, u32)> {
unsafe {
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UNWIND_INFO;
let unwind_code = (unwind_info as *mut u8).add(4) as *mut UNWIND_CODE;
let flag = (*unwind_info).VersionFlags.Flags();
let mut i = 0usize;
let mut set_fpreg_hit = false;
let mut total_stack = 0i32;
while i < (*unwind_info).CountOfCodes as usize {
let unwind_code = unwind_code.add(i);
let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
let unwind_op = (*unwind_code).Anonymous.UnwindOp();
match UNWIND_OP_CODES::try_from(unwind_op) {
Ok(UWOP_PUSH_NONVOL) => {
if Registers::Rsp == op_info && !set_fpreg_hit {
return None;
}
total_stack += 8;
i += 1;
}
Ok(UWOP_ALLOC_SMALL) => {
total_stack += ((op_info + 1) * 8) as i32;
i += 1;
}
Ok(UWOP_ALLOC_LARGE) => {
if (*unwind_code).Anonymous.OpInfo() == 0 {
let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
total_stack += frame_offset;
i += 2
} else {
let frame_offset = *(unwind_code.add(1) as *mut i32);
total_stack += frame_offset;
i += 3
}
}
Ok(UWOP_SAVE_NONVOL) => {
if Registers::Rsp == op_info || Registers::Rbp == op_info {
return None;
}
i += 2;
}
Ok(UWOP_SAVE_NONVOL_BIG) => {
if Registers::Rsp == op_info || Registers::Rbp == op_info {
return None;
}
i += 3;
}
Ok(UWOP_SAVE_XMM128) => i += 2,
Ok(UWOP_SAVE_XMM128BIG) => i += 3,
Ok(UWOP_SET_FPREG) => {
if (flag & UNW_FLAG_EHANDLER) != 0 && (flag & UNW_FLAG_CHAININFO) != 0 {
return None;
}
if (*unwind_info).FrameInfo.FrameRegister() != Registers::Rbp as u8 {
return None;
}
set_fpreg_hit = true;
let offset = ((*unwind_info).FrameInfo.FrameOffset() as i32) << 4;
total_stack -= offset;
i += 1
}
Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
Ok(UWOP_PUSH_MACH_FRAME) => {
total_stack += if op_info == 0 { 0x40 } else { 0x48 };
i += 1
}
_ => {}
}
}
if (flag & UNW_FLAG_CHAININFO) != 0 {
let count = (*unwind_info).CountOfCodes as usize;
let index = if count & 1 == 1 { count + 1 } else { count };
let runtime = unwind_code.add(index) as *const IMAGE_RUNTIME_FUNCTION;
if let Some((chained_fpreg_hit, chained_stack)) = stack_frame(module, &*runtime) {
total_stack += chained_stack as i32;
set_fpreg_hit |= chained_fpreg_hit;
} else {
return None;
}
}
Some((set_fpreg_hit, total_stack as u32))
}
}
pub fn ignoring_set_fpreg(module: *mut c_void, runtime: &IMAGE_RUNTIME_FUNCTION) -> Option<u32> {
unsafe {
let unwind_info = (module as usize + runtime.UnwindData as usize) as *mut UNWIND_INFO;
let unwind_code = (unwind_info as *mut u8).add(4) as *mut UNWIND_CODE;
let flag = (*unwind_info).VersionFlags.Flags();
let mut i = 0usize;
let mut total_stack = 0u32;
while i < (*unwind_info).CountOfCodes as usize {
let unwind_code = unwind_code.add(i);
let op_info = (*unwind_code).Anonymous.OpInfo() as usize;
let unwind_op = (*unwind_code).Anonymous.UnwindOp();
match UNWIND_OP_CODES::try_from(unwind_op) {
Ok(UWOP_PUSH_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
total_stack += 8;
i += 1;
}
Ok(UWOP_ALLOC_SMALL) => {
total_stack += ((op_info + 1) * 8) as u32;
i += 1;
}
Ok(UWOP_ALLOC_LARGE) => {
if (*unwind_code).Anonymous.OpInfo() == 0 {
let frame_offset = ((*unwind_code.add(1)).FrameOffset as i32) * 8;
total_stack += frame_offset as u32;
i += 2
} else {
let frame_offset = *(unwind_code.add(1) as *mut i32);
total_stack += frame_offset as u32;
i += 3
}
}
Ok(UWOP_SAVE_NONVOL) => {
if Registers::Rsp == op_info {
return None;
}
i += 2;
}
Ok(UWOP_SAVE_NONVOL_BIG) => {
if Registers::Rsp == op_info {
return None;
}
i += 3;
}
Ok(UWOP_SAVE_XMM128) => i += 2,
Ok(UWOP_SAVE_XMM128BIG) => i += 3,
Ok(UWOP_SET_FPREG) => i += 1,
Ok(UWOP_EPILOG) | Ok(UWOP_SPARE_CODE) => i += 1,
Ok(UWOP_PUSH_MACH_FRAME) => {
total_stack += if op_info == 0 { 0x40 } else { 0x48 };
i += 1
}
_ => {}
}
}
if (flag & UNW_FLAG_CHAININFO) != 0 {
let count = (*unwind_info).CountOfCodes as usize;
let index = if count & 1 == 1 { count + 1 } else { count };
let runtime = unwind_code.add(index) as *const IMAGE_RUNTIME_FUNCTION;
if let Some(chained_stack) = ignoring_set_fpreg(module, &*runtime) {
total_stack += chained_stack;
} else {
return None;
}
}
Some(total_stack)
}
}
pub trait AsPointer {
fn as_ptr_const(&self) -> *const c_void;
fn as_ptr_mut(&mut self) -> *mut c_void;
}
impl<T> AsPointer for T {
#[inline(always)]
fn as_ptr_const(&self) -> *const c_void {
self as *const _ as *const c_void
}
#[inline(always)]
fn as_ptr_mut(&mut self) -> *mut c_void {
self as *mut _ as *mut c_void
}
}
pub enum SpoofKind<'a> {
Function,
Syscall(&'a str),
}
#[cfg(test)]
mod tests {
use core::ptr;
use alloc::boxed::Box;
use super::*;
#[test]
fn test_spoof() -> Result<(), Box<dyn core::error::Error>> {
let kernel32 = get_module_address("kernel32.dll", None);
let virtual_alloc = get_proc_address(kernel32, "VirtualAlloc", None);
let addr = spoof!(virtual_alloc, ptr::null_mut::<c_void>(), 1 << 12, 0x3000, 0x04)?;
assert_ne!(addr, ptr::null_mut());
Ok(())
}
#[test]
fn test_syscall() -> Result<(), Box<dyn core::error::Error>> {
let mut addr = ptr::null_mut::<c_void>();
let mut size = (1 << 12) as usize;
let status = syscall!("NtAllocateVirtualMemory", -1isize, addr.as_ptr_mut(), 0, size.as_ptr_mut(), 0x3000, 0x04)? as i32;
assert_eq!(status, 0);
Ok(())
}
}