#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
extern crate alloc;
#[cfg(not(windows))]
compile_error!("dyncvoke-spoof is Windows-only");
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use data::lc;
pub mod pe;
pub mod types;
pub mod unwind;
pub mod util;
use crate::pe::{function_by_rva, runtime_function_table};
use crate::types::{Config, ImageRuntimeFunction};
use crate::unwind::{rbp_offset, stack_frame};
#[cfg(not(feature = "desync"))]
use crate::unwind::ignoring_set_fpreg;
use crate::util::{find_gadget, find_valid_instruction_offset, shuffle};
#[cfg(feature = "desync")]
use crate::util::find_base_thread_return_address;
#[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 as *mut ::core::ffi::c_void,
&[$(::core::mem::transmute($arg as usize)),*],
$crate::SpoofKind::Function,
)
}
};
}
#[macro_export]
macro_rules! spoof_syscall {
($name:expr, $($arg:expr),* $(,)?) => {
unsafe {
$crate::__private::spoof(
::core::ptr::null_mut(),
&[$(::core::mem::transmute($arg as usize)),*],
$crate::SpoofKind::Syscall($name),
)
}
};
}
pub enum SpoofKind<'a> {
Function,
Syscall(&'a str),
}
#[derive(Debug, Clone, Copy)]
pub enum SpoofTag {
Kernelbase,
Kernel32,
Ntdll,
RtlUserThreadStart,
BaseThreadInitThunk,
AddRspGadget,
JmpRbxGadget,
}
#[derive(Debug)]
pub enum SpoofError {
TooManyArguments,
NullFunctionAddress,
ModuleNotLoaded(SpoofTag),
FunctionNotFound(SpoofTag),
PdataMissing(SpoofTag),
UnwindInfoMissing(SpoofTag),
PrologueNotFound,
PushRbpPrologueNotFound,
GadgetNotFound(SpoofTag),
BaseThreadReturnNotFound,
SsnResolutionFailed,
}
impl core::fmt::Display for SpoofError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let tag_str = |t: SpoofTag| match t {
SpoofTag::Kernelbase => lc!("kernelbase.dll"),
SpoofTag::Kernel32 => lc!("kernel32.dll"),
SpoofTag::Ntdll => lc!("ntdll.dll"),
SpoofTag::RtlUserThreadStart => lc!("RtlUserThreadStart"),
SpoofTag::BaseThreadInitThunk => lc!("BaseThreadInitThunk"),
SpoofTag::AddRspGadget => lc!("add rsp,0x58; ret"),
SpoofTag::JmpRbxGadget => lc!("jmp [rbx]"),
};
match self {
SpoofError::TooManyArguments => write!(f, "{}", lc!("too many arguments")),
SpoofError::NullFunctionAddress => write!(f, "{}", lc!("null function address")),
SpoofError::ModuleNotLoaded(t) => write!(f, "{}: {}", lc!("module"), tag_str(*t)),
SpoofError::FunctionNotFound(t) => write!(f, "{}: {}", lc!("export"), tag_str(*t)),
SpoofError::PdataMissing(t) => write!(f, "{}: {}", lc!("pdata"), tag_str(*t)),
SpoofError::UnwindInfoMissing(t) => write!(f, "{}: {}", lc!("unwind"), tag_str(*t)),
SpoofError::PrologueNotFound => write!(f, "{}", lc!("no prologue")),
SpoofError::PushRbpPrologueNotFound => write!(f, "{}", lc!("no rbp prologue")),
SpoofError::GadgetNotFound(t) => write!(f, "{}: {}", lc!("gadget"), tag_str(*t)),
SpoofError::BaseThreadReturnNotFound => write!(f, "{}", lc!("no thread return")),
SpoofError::SsnResolutionFailed => write!(f, "{}", lc!("ssn fail")),
}
}
}
impl core::error::Error for SpoofError {}
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
}
}
#[doc(hidden)]
pub mod __private {
use super::*;
#[allow(unused_imports)]
use alloc::string::String;
#[cfg(not(feature = "desync"))]
pub unsafe fn spoof(
addr: *mut c_void,
args: &[*const c_void],
kind: SpoofKind,
) -> Result<*mut c_void, SpoofError> {
if args.len() > 11 {
return Err(SpoofError::TooManyArguments);
}
if matches!(kind, SpoofKind::Function) && addr.is_null() {
return Err(SpoofError::NullFunctionAddress);
}
let mut config = Config::default();
let kernelbase = dyncvoke_core::get_module_base_address(&lc!("kernelbase.dll"));
if kernelbase == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernelbase));
}
let ntdll = dyncvoke_core::get_module_base_address(&lc!("ntdll.dll"));
if ntdll == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Ntdll));
}
let kernel32 = dyncvoke_core::get_module_base_address(&lc!("kernel32.dll"));
if kernel32 == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernel32));
}
let rtl_user_addr =
dyncvoke_core::get_function_address(ntdll, &lc!("RtlUserThreadStart"));
if rtl_user_addr == 0 {
return Err(SpoofError::FunctionNotFound(SpoofTag::RtlUserThreadStart));
}
let base_thread_addr =
dyncvoke_core::get_function_address(kernel32, &lc!("BaseThreadInitThunk"));
if base_thread_addr == 0 {
return Err(SpoofError::FunctionNotFound(SpoofTag::BaseThreadInitThunk));
}
config.rtl_user_addr = rtl_user_addr as *const c_void;
config.base_thread_addr = base_thread_addr as *const c_void;
let kb_table = runtime_function_table(kernelbase as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernelbase))?;
let ntdll_table = runtime_function_table(ntdll as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Ntdll))?;
let k32_table = runtime_function_table(kernel32 as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernel32))?;
let rtl_user_runtime =
function_by_rva(ntdll_table, (rtl_user_addr - ntdll) as u32)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::RtlUserThreadStart))?;
let base_thread_runtime =
function_by_rva(k32_table, (base_thread_addr - kernel32) as u32)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))?;
config.rtl_user_thread_size =
ignoring_set_fpreg(ntdll as *mut c_void, rtl_user_runtime)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::RtlUserThreadStart))? as u64;
config.base_thread_size =
ignoring_set_fpreg(kernel32 as *mut c_void, base_thread_runtime)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))? as u64;
let first_prolog = find_prolog(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PrologueNotFound)?;
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 = find_push_rbp(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PushRbpPrologueNotFound)?;
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 as *mut c_void,
&[0x48, 0x83, 0xC4, 0x58, 0xC3],
kb_table,
Some(0x58),
)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::AddRspGadget))?;
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 as *mut c_void, &[0xFF, 0x23], kb_table, None)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::JmpRbxGadget))?;
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
config.jmp_rbx_frame_size = size as u64;
config.number_args = args.len() as u64;
write_args(&mut config, args);
match kind {
SpoofKind::Function => config.spoof_function = addr as *const c_void,
SpoofKind::Syscall(name) => {
let (ssn, syscall_addr) = dyncvoke_core::resolve_syscall(name)
.map_err(|_| SpoofError::SsnResolutionFailed)?;
config.is_syscall = 1;
config.ssn = ssn as u32;
config.spoof_function = syscall_addr as *const c_void;
}
}
Ok(SpoofSynthetic(&mut config))
}
#[cfg(feature = "desync")]
pub unsafe fn spoof(
addr: *mut c_void,
args: &[*const c_void],
kind: SpoofKind,
) -> Result<*mut c_void, SpoofError> {
if args.len() > 11 {
return Err(SpoofError::TooManyArguments);
}
if matches!(kind, SpoofKind::Function) && addr.is_null() {
return Err(SpoofError::NullFunctionAddress);
}
let mut config = Config::default();
let kernelbase = dyncvoke_core::get_module_base_address(&lc!("kernelbase.dll"));
if kernelbase == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernelbase));
}
let kernel32 = dyncvoke_core::get_module_base_address(&lc!("kernel32.dll"));
if kernel32 == 0 {
return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernel32));
}
let base_thread_addr =
dyncvoke_core::get_function_address(kernel32, &lc!("BaseThreadInitThunk"));
if base_thread_addr == 0 {
return Err(SpoofError::FunctionNotFound(SpoofTag::BaseThreadInitThunk));
}
let k32_table = runtime_function_table(kernel32 as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernel32))?;
let base_thread_runtime =
function_by_rva(k32_table, (base_thread_addr - kernel32) as u32)
.ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))?;
let base_thread_size =
(base_thread_runtime.EndAddress - base_thread_runtime.BeginAddress) as usize;
let stack_slot = find_base_thread_return_address(
kernel32 as *mut c_void,
base_thread_addr as *mut c_void,
base_thread_size,
)
.ok_or(SpoofError::BaseThreadReturnNotFound)?;
config.return_address = stack_slot as *const c_void;
let kb_table = runtime_function_table(kernelbase as *mut c_void)
.ok_or(SpoofError::PdataMissing(SpoofTag::Kernelbase))?;
let first_prolog = find_prolog(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PrologueNotFound)?;
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 = find_push_rbp(kernelbase as *mut c_void, kb_table)
.ok_or(SpoofError::PushRbpPrologueNotFound)?;
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 as *mut c_void,
&[0x48, 0x83, 0xC4, 0x58, 0xC3],
kb_table,
Some(0x58),
)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::AddRspGadget))?;
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 as *mut c_void, &[0xFF, 0x23], kb_table, None)
.ok_or(SpoofError::GadgetNotFound(SpoofTag::JmpRbxGadget))?;
config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
config.jmp_rbx_frame_size = size as u64;
config.number_args = args.len() as u64;
write_args(&mut config, args);
match kind {
SpoofKind::Function => config.spoof_function = addr as *const c_void,
SpoofKind::Syscall(name) => {
let (ssn, syscall_addr) = dyncvoke_core::resolve_syscall(name)
.map_err(|_| SpoofError::SsnResolutionFailed)?;
config.is_syscall = 1;
config.ssn = ssn as u32;
config.spoof_function = syscall_addr as *const c_void;
}
}
Ok(Spoof(&mut config))
}
#[inline]
fn write_args(cfg: &mut Config, args: &[*const c_void]) {
for (i, &a) in args.iter().enumerate() {
match i {
0 => cfg.arg01 = a,
1 => cfg.arg02 = a,
2 => cfg.arg03 = a,
3 => cfg.arg04 = a,
4 => cfg.arg05 = a,
5 => cfg.arg06 = a,
6 => cfg.arg07 = a,
7 => cfg.arg08 = a,
8 => cfg.arg09 = a,
9 => cfg.arg10 = a,
10 => cfg.arg11 = a,
_ => break,
}
}
}
}
#[derive(Copy, Clone, Default)]
struct Prolog {
frame: u64,
stack_size: u32,
offset: u32,
rbp_offset: u32,
}
fn find_prolog(module_base: *mut c_void, runtime_table: &[ImageRuntimeFunction]) -> Option<Prolog> {
let mut prologs: Vec<Prolog> = runtime_table
.iter()
.filter_map(|runtime| {
let (is_valid, stack_size) = unsafe { 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(Prolog {
frame,
stack_size,
offset,
..Default::default()
})
})
.collect();
if prologs.is_empty() {
return None;
}
shuffle(&mut prologs);
prologs.first().copied()
}
fn find_push_rbp(
module_base: *mut c_void,
runtime_table: &[ImageRuntimeFunction],
) -> Option<Prolog> {
let mut prologs: Vec<Prolog> = runtime_table
.iter()
.filter_map(|runtime| {
let (rbp_off, stack_size) = unsafe { rbp_offset(module_base, runtime) }?;
if rbp_off == 0 || stack_size == 0 || stack_size <= rbp_off {
return None;
}
let offset = find_valid_instruction_offset(module_base, runtime)?;
let frame = module_base as u64 + runtime.BeginAddress as u64;
Some(Prolog {
frame,
stack_size,
offset,
rbp_offset: rbp_off,
})
})
.collect();
if prologs.is_empty() {
return None;
}
prologs.remove(0);
if prologs.is_empty() {
return None;
}
shuffle(&mut prologs);
prologs.first().copied()
}