1#![no_std]
37#![cfg_attr(docsrs, feature(doc_cfg))]
38extern crate alloc;
39
40#[cfg(not(windows))]
41compile_error!("dyncvoke-spoof is Windows-only");
42
43use alloc::string::String;
44use alloc::vec::Vec;
45use core::ffi::c_void;
46
47use data::lc;
48
49pub mod pe;
50pub mod types;
51pub mod unwind;
52pub mod util;
53
54use crate::pe::{function_by_rva, runtime_function_table};
55use crate::types::{Config, ImageRuntimeFunction};
56use crate::unwind::{rbp_offset, stack_frame};
57#[cfg(not(feature = "desync"))]
58use crate::unwind::ignoring_set_fpreg;
59use crate::util::{find_gadget, find_valid_instruction_offset, shuffle};
60
61#[cfg(feature = "desync")]
62use crate::util::find_base_thread_return_address;
63
64#[cfg(feature = "desync")]
68unsafe extern "C" {
69 fn Spoof(config: &mut Config) -> *mut c_void;
70}
71
72#[cfg(not(feature = "desync"))]
73unsafe extern "C" {
74 fn SpoofSynthetic(config: &mut Config) -> *mut c_void;
75}
76
77#[macro_export]
94macro_rules! spoof {
95 ($addr:expr, $($arg:expr),+ $(,)?) => {
96 unsafe {
97 $crate::__private::spoof(
98 $addr as *mut ::core::ffi::c_void,
99 &[$(::core::mem::transmute($arg as usize)),*],
100 $crate::SpoofKind::Function,
101 )
102 }
103 };
104}
105
106#[macro_export]
127macro_rules! spoof_syscall {
128 ($name:expr, $($arg:expr),* $(,)?) => {
129 unsafe {
130 $crate::__private::spoof(
131 ::core::ptr::null_mut(),
132 &[$(::core::mem::transmute($arg as usize)),*],
133 $crate::SpoofKind::Syscall($name),
134 )
135 }
136 };
137}
138
139pub enum SpoofKind<'a> {
141 Function,
143 Syscall(&'a str),
145}
146
147#[derive(Debug, Clone, Copy)]
151pub enum SpoofTag {
152 Kernelbase,
153 Kernel32,
154 Ntdll,
155 RtlUserThreadStart,
156 BaseThreadInitThunk,
157 AddRspGadget,
158 JmpRbxGadget,
159}
160
161#[derive(Debug)]
163pub enum SpoofError {
164 TooManyArguments,
165 NullFunctionAddress,
166 ModuleNotLoaded(SpoofTag),
167 FunctionNotFound(SpoofTag),
168 PdataMissing(SpoofTag),
169 UnwindInfoMissing(SpoofTag),
170 PrologueNotFound,
171 PushRbpPrologueNotFound,
172 GadgetNotFound(SpoofTag),
173 BaseThreadReturnNotFound,
174 SsnResolutionFailed,
175}
176
177impl core::fmt::Display for SpoofError {
178 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179 let tag_str = |t: SpoofTag| match t {
180 SpoofTag::Kernelbase => lc!("kernelbase.dll"),
181 SpoofTag::Kernel32 => lc!("kernel32.dll"),
182 SpoofTag::Ntdll => lc!("ntdll.dll"),
183 SpoofTag::RtlUserThreadStart => lc!("RtlUserThreadStart"),
184 SpoofTag::BaseThreadInitThunk => lc!("BaseThreadInitThunk"),
185 SpoofTag::AddRspGadget => lc!("add rsp,0x58; ret"),
186 SpoofTag::JmpRbxGadget => lc!("jmp [rbx]"),
187 };
188 match self {
189 SpoofError::TooManyArguments => write!(f, "{}", lc!("too many arguments")),
190 SpoofError::NullFunctionAddress => write!(f, "{}", lc!("null function address")),
191 SpoofError::ModuleNotLoaded(t) => write!(f, "{}: {}", lc!("module"), tag_str(*t)),
192 SpoofError::FunctionNotFound(t) => write!(f, "{}: {}", lc!("export"), tag_str(*t)),
193 SpoofError::PdataMissing(t) => write!(f, "{}: {}", lc!("pdata"), tag_str(*t)),
194 SpoofError::UnwindInfoMissing(t) => write!(f, "{}: {}", lc!("unwind"), tag_str(*t)),
195 SpoofError::PrologueNotFound => write!(f, "{}", lc!("no prologue")),
196 SpoofError::PushRbpPrologueNotFound => write!(f, "{}", lc!("no rbp prologue")),
197 SpoofError::GadgetNotFound(t) => write!(f, "{}: {}", lc!("gadget"), tag_str(*t)),
198 SpoofError::BaseThreadReturnNotFound => write!(f, "{}", lc!("no thread return")),
199 SpoofError::SsnResolutionFailed => write!(f, "{}", lc!("ssn fail")),
200 }
201 }
202}
203
204impl core::error::Error for SpoofError {}
205
206pub trait AsPointer {
209 fn as_ptr_const(&self) -> *const c_void;
210 fn as_ptr_mut(&mut self) -> *mut c_void;
211}
212
213impl<T> AsPointer for T {
214 #[inline(always)]
215 fn as_ptr_const(&self) -> *const c_void {
216 self as *const _ as *const c_void
217 }
218 #[inline(always)]
219 fn as_ptr_mut(&mut self) -> *mut c_void {
220 self as *mut _ as *mut c_void
221 }
222}
223
224#[doc(hidden)]
225pub mod __private {
226 use super::*;
227 #[allow(unused_imports)]
228 use alloc::string::String;
229
230 #[cfg(not(feature = "desync"))]
233 pub unsafe fn spoof(
234 addr: *mut c_void,
235 args: &[*const c_void],
236 kind: SpoofKind,
237 ) -> Result<*mut c_void, SpoofError> {
238 if args.len() > 11 {
239 return Err(SpoofError::TooManyArguments);
240 }
241 if matches!(kind, SpoofKind::Function) && addr.is_null() {
242 return Err(SpoofError::NullFunctionAddress);
243 }
244
245 let mut config = Config::default();
246
247 let kernelbase = dyncvoke_core::get_module_base_address(&lc!("kernelbase.dll"));
248 if kernelbase == 0 {
249 return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernelbase));
250 }
251 let ntdll = dyncvoke_core::get_module_base_address(&lc!("ntdll.dll"));
252 if ntdll == 0 {
253 return Err(SpoofError::ModuleNotLoaded(SpoofTag::Ntdll));
254 }
255 let kernel32 = dyncvoke_core::get_module_base_address(&lc!("kernel32.dll"));
256 if kernel32 == 0 {
257 return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernel32));
258 }
259
260 let rtl_user_addr =
261 dyncvoke_core::get_function_address(ntdll, &lc!("RtlUserThreadStart"));
262 if rtl_user_addr == 0 {
263 return Err(SpoofError::FunctionNotFound(SpoofTag::RtlUserThreadStart));
264 }
265 let base_thread_addr =
266 dyncvoke_core::get_function_address(kernel32, &lc!("BaseThreadInitThunk"));
267 if base_thread_addr == 0 {
268 return Err(SpoofError::FunctionNotFound(SpoofTag::BaseThreadInitThunk));
269 }
270
271 config.rtl_user_addr = rtl_user_addr as *const c_void;
272 config.base_thread_addr = base_thread_addr as *const c_void;
273
274 let kb_table = runtime_function_table(kernelbase as *mut c_void)
275 .ok_or(SpoofError::PdataMissing(SpoofTag::Kernelbase))?;
276 let ntdll_table = runtime_function_table(ntdll as *mut c_void)
277 .ok_or(SpoofError::PdataMissing(SpoofTag::Ntdll))?;
278 let k32_table = runtime_function_table(kernel32 as *mut c_void)
279 .ok_or(SpoofError::PdataMissing(SpoofTag::Kernel32))?;
280
281 let rtl_user_runtime =
282 function_by_rva(ntdll_table, (rtl_user_addr - ntdll) as u32)
283 .ok_or(SpoofError::UnwindInfoMissing(SpoofTag::RtlUserThreadStart))?;
284 let base_thread_runtime =
285 function_by_rva(k32_table, (base_thread_addr - kernel32) as u32)
286 .ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))?;
287
288 config.rtl_user_thread_size =
289 ignoring_set_fpreg(ntdll as *mut c_void, rtl_user_runtime)
290 .ok_or(SpoofError::UnwindInfoMissing(SpoofTag::RtlUserThreadStart))? as u64;
291 config.base_thread_size =
292 ignoring_set_fpreg(kernel32 as *mut c_void, base_thread_runtime)
293 .ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))? as u64;
294
295 let first_prolog = find_prolog(kernelbase as *mut c_void, kb_table)
296 .ok_or(SpoofError::PrologueNotFound)?;
297 config.first_frame_fp =
298 (first_prolog.frame + first_prolog.offset as u64) as *const c_void;
299 config.first_frame_size = first_prolog.stack_size as u64;
300
301 let second_prolog = find_push_rbp(kernelbase as *mut c_void, kb_table)
302 .ok_or(SpoofError::PushRbpPrologueNotFound)?;
303 config.second_frame_fp =
304 (second_prolog.frame + second_prolog.offset as u64) as *const c_void;
305 config.second_frame_size = second_prolog.stack_size as u64;
306 config.rbp_stack_offset = second_prolog.rbp_offset as u64;
307
308 let (add_rsp_addr, size) = find_gadget(
309 kernelbase as *mut c_void,
310 &[0x48, 0x83, 0xC4, 0x58, 0xC3],
311 kb_table,
312 Some(0x58),
313 )
314 .ok_or(SpoofError::GadgetNotFound(SpoofTag::AddRspGadget))?;
315 config.add_rsp_gadget = add_rsp_addr as *const c_void;
316 config.add_rsp_frame_size = size as u64;
317
318 let (jmp_rbx_addr, size) =
319 find_gadget(kernelbase as *mut c_void, &[0xFF, 0x23], kb_table, None)
320 .ok_or(SpoofError::GadgetNotFound(SpoofTag::JmpRbxGadget))?;
321 config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
322 config.jmp_rbx_frame_size = size as u64;
323
324 config.number_args = args.len() as u64;
325 write_args(&mut config, args);
326
327 match kind {
328 SpoofKind::Function => config.spoof_function = addr as *const c_void,
329 SpoofKind::Syscall(name) => {
330 let (ssn, syscall_addr) = dyncvoke_core::resolve_syscall(name)
331 .map_err(|_| SpoofError::SsnResolutionFailed)?;
332 config.is_syscall = 1;
333 config.ssn = ssn as u32;
334 config.spoof_function = syscall_addr as *const c_void;
335 }
336 }
337
338 Ok(SpoofSynthetic(&mut config))
339 }
340
341 #[cfg(feature = "desync")]
344 pub unsafe fn spoof(
345 addr: *mut c_void,
346 args: &[*const c_void],
347 kind: SpoofKind,
348 ) -> Result<*mut c_void, SpoofError> {
349 if args.len() > 11 {
350 return Err(SpoofError::TooManyArguments);
351 }
352 if matches!(kind, SpoofKind::Function) && addr.is_null() {
353 return Err(SpoofError::NullFunctionAddress);
354 }
355
356 let mut config = Config::default();
357
358 let kernelbase = dyncvoke_core::get_module_base_address(&lc!("kernelbase.dll"));
359 if kernelbase == 0 {
360 return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernelbase));
361 }
362 let kernel32 = dyncvoke_core::get_module_base_address(&lc!("kernel32.dll"));
363 if kernel32 == 0 {
364 return Err(SpoofError::ModuleNotLoaded(SpoofTag::Kernel32));
365 }
366
367 let base_thread_addr =
368 dyncvoke_core::get_function_address(kernel32, &lc!("BaseThreadInitThunk"));
369 if base_thread_addr == 0 {
370 return Err(SpoofError::FunctionNotFound(SpoofTag::BaseThreadInitThunk));
371 }
372
373 let k32_table = runtime_function_table(kernel32 as *mut c_void)
374 .ok_or(SpoofError::PdataMissing(SpoofTag::Kernel32))?;
375 let base_thread_runtime =
376 function_by_rva(k32_table, (base_thread_addr - kernel32) as u32)
377 .ok_or(SpoofError::UnwindInfoMissing(SpoofTag::BaseThreadInitThunk))?;
378
379 let base_thread_size =
380 (base_thread_runtime.EndAddress - base_thread_runtime.BeginAddress) as usize;
381 let stack_slot = find_base_thread_return_address(
382 kernel32 as *mut c_void,
383 base_thread_addr as *mut c_void,
384 base_thread_size,
385 )
386 .ok_or(SpoofError::BaseThreadReturnNotFound)?;
387 config.return_address = stack_slot as *const c_void;
388
389 let kb_table = runtime_function_table(kernelbase as *mut c_void)
390 .ok_or(SpoofError::PdataMissing(SpoofTag::Kernelbase))?;
391
392 let first_prolog = find_prolog(kernelbase as *mut c_void, kb_table)
393 .ok_or(SpoofError::PrologueNotFound)?;
394 config.first_frame_fp =
395 (first_prolog.frame + first_prolog.offset as u64) as *const c_void;
396 config.first_frame_size = first_prolog.stack_size as u64;
397
398 let second_prolog = find_push_rbp(kernelbase as *mut c_void, kb_table)
399 .ok_or(SpoofError::PushRbpPrologueNotFound)?;
400 config.second_frame_fp =
401 (second_prolog.frame + second_prolog.offset as u64) as *const c_void;
402 config.second_frame_size = second_prolog.stack_size as u64;
403 config.rbp_stack_offset = second_prolog.rbp_offset as u64;
404
405 let (add_rsp_addr, size) = find_gadget(
406 kernelbase as *mut c_void,
407 &[0x48, 0x83, 0xC4, 0x58, 0xC3],
408 kb_table,
409 Some(0x58),
410 )
411 .ok_or(SpoofError::GadgetNotFound(SpoofTag::AddRspGadget))?;
412 config.add_rsp_gadget = add_rsp_addr as *const c_void;
413 config.add_rsp_frame_size = size as u64;
414
415 let (jmp_rbx_addr, size) =
416 find_gadget(kernelbase as *mut c_void, &[0xFF, 0x23], kb_table, None)
417 .ok_or(SpoofError::GadgetNotFound(SpoofTag::JmpRbxGadget))?;
418 config.jmp_rbx_gadget = jmp_rbx_addr as *const c_void;
419 config.jmp_rbx_frame_size = size as u64;
420
421 config.number_args = args.len() as u64;
422 write_args(&mut config, args);
423
424 match kind {
425 SpoofKind::Function => config.spoof_function = addr as *const c_void,
426 SpoofKind::Syscall(name) => {
427 let (ssn, syscall_addr) = dyncvoke_core::resolve_syscall(name)
428 .map_err(|_| SpoofError::SsnResolutionFailed)?;
429 config.is_syscall = 1;
430 config.ssn = ssn as u32;
431 config.spoof_function = syscall_addr as *const c_void;
432 }
433 }
434
435 Ok(Spoof(&mut config))
436 }
437
438 #[inline]
439 fn write_args(cfg: &mut Config, args: &[*const c_void]) {
440 for (i, &a) in args.iter().enumerate() {
441 match i {
442 0 => cfg.arg01 = a,
443 1 => cfg.arg02 = a,
444 2 => cfg.arg03 = a,
445 3 => cfg.arg04 = a,
446 4 => cfg.arg05 = a,
447 5 => cfg.arg06 = a,
448 6 => cfg.arg07 = a,
449 7 => cfg.arg08 = a,
450 8 => cfg.arg09 = a,
451 9 => cfg.arg10 = a,
452 10 => cfg.arg11 = a,
453 _ => break,
454 }
455 }
456 }
457}
458
459#[derive(Copy, Clone, Default)]
461struct Prolog {
462 frame: u64,
463 stack_size: u32,
464 offset: u32,
465 rbp_offset: u32,
466}
467
468fn find_prolog(module_base: *mut c_void, runtime_table: &[ImageRuntimeFunction]) -> Option<Prolog> {
472 let mut prologs: Vec<Prolog> = runtime_table
473 .iter()
474 .filter_map(|runtime| {
475 let (is_valid, stack_size) = unsafe { stack_frame(module_base, runtime) }?;
476 if !is_valid {
477 return None;
478 }
479 let offset = find_valid_instruction_offset(module_base, runtime)?;
480 let frame = module_base as u64 + runtime.BeginAddress as u64;
481 Some(Prolog {
482 frame,
483 stack_size,
484 offset,
485 ..Default::default()
486 })
487 })
488 .collect();
489
490 if prologs.is_empty() {
491 return None;
492 }
493 shuffle(&mut prologs);
494 prologs.first().copied()
495}
496
497fn find_push_rbp(
501 module_base: *mut c_void,
502 runtime_table: &[ImageRuntimeFunction],
503) -> Option<Prolog> {
504 let mut prologs: Vec<Prolog> = runtime_table
505 .iter()
506 .filter_map(|runtime| {
507 let (rbp_off, stack_size) = unsafe { rbp_offset(module_base, runtime) }?;
508 if rbp_off == 0 || stack_size == 0 || stack_size <= rbp_off {
509 return None;
510 }
511 let offset = find_valid_instruction_offset(module_base, runtime)?;
512 let frame = module_base as u64 + runtime.BeginAddress as u64;
513 Some(Prolog {
514 frame,
515 stack_size,
516 offset,
517 rbp_offset: rbp_off,
518 })
519 })
520 .collect();
521
522 if prologs.is_empty() {
523 return None;
524 }
525 prologs.remove(0);
527 if prologs.is_empty() {
528 return None;
529 }
530 shuffle(&mut prologs);
531 prologs.first().copied()
532}