1#[cfg(all(not(feature = "std"), feature = "alloc"))]
7use alloc::{format, string::String, vec::Vec};
8
9#[cfg(feature = "std")]
10use std::{format, string::String, vec::Vec};
11
12#[cfg(feature = "std")]
13use std::sync::OnceLock;
14
15use super::gadget::GadgetFinder;
16use super::trampoline::{SpoofTrampoline, TrampolineAllocator};
17use crate::error::{Result, WraithError};
18use crate::manipulation::syscall::SyscallEntry;
19use core::arch::asm;
20
21#[cfg(feature = "std")]
23static TRAMPOLINE_ALLOC: OnceLock<Result<TrampolineAllocator>> = OnceLock::new();
24
25#[cfg(feature = "std")]
26fn get_trampoline_allocator() -> Result<&'static TrampolineAllocator> {
27 let result = TRAMPOLINE_ALLOC.get_or_init(TrampolineAllocator::new);
28 match result {
29 Ok(alloc) => Ok(alloc),
30 Err(_e) => Err(WraithError::TrampolineAllocationFailed {
31 near: 0,
32 size: 0,
33 }),
34 }
35}
36
37#[cfg(not(feature = "std"))]
38fn get_trampoline_allocator() -> Result<&'static TrampolineAllocator> {
39 Err(WraithError::TrampolineAllocationFailed {
40 near: 0,
41 size: 0,
42 })
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum SpoofMode {
48 Gadget,
50 SyntheticStack,
52 SimpleSpoof,
54 None,
56}
57
58impl Default for SpoofMode {
59 fn default() -> Self {
60 Self::Gadget
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct SpoofConfig {
67 pub mode: SpoofMode,
69 pub prefer_ntdll: bool,
71 pub custom_spoof_addr: Option<usize>,
73 pub stack_pattern: Option<Vec<&'static str>>,
75}
76
77impl Default for SpoofConfig {
78 fn default() -> Self {
79 Self {
80 mode: SpoofMode::Gadget,
81 prefer_ntdll: true,
82 custom_spoof_addr: None,
83 stack_pattern: None,
84 }
85 }
86}
87
88impl SpoofConfig {
89 pub fn gadget() -> Self {
91 Self {
92 mode: SpoofMode::Gadget,
93 ..Default::default()
94 }
95 }
96
97 pub fn simple(spoof_addr: usize) -> Self {
99 Self {
100 mode: SpoofMode::SimpleSpoof,
101 custom_spoof_addr: Some(spoof_addr),
102 ..Default::default()
103 }
104 }
105
106 pub fn synthetic(pattern: Vec<&'static str>) -> Self {
108 Self {
109 mode: SpoofMode::SyntheticStack,
110 stack_pattern: Some(pattern),
111 ..Default::default()
112 }
113 }
114}
115
116pub struct SpoofedSyscall {
120 ssn: u16,
122 syscall_addr: usize,
124 gadget_addr: usize,
126 spoof_addr: usize,
128 name: String,
130 trampoline: Option<SpoofTrampoline>,
132 mode: SpoofMode,
134}
135
136impl SpoofedSyscall {
137 pub fn new(name: &str) -> Result<Self> {
139 Self::with_config(name, SpoofConfig::default())
140 }
141
142 pub fn with_config(name: &str, config: SpoofConfig) -> Result<Self> {
144 let table = crate::manipulation::syscall::get_syscall_table()?;
145 let entry = table.get(name).ok_or_else(|| WraithError::SyscallNotFound {
146 name: name.to_string(),
147 })?;
148
149 Self::from_entry_with_config(entry, config)
150 }
151
152 pub fn from_entry_with_config(entry: &SyscallEntry, config: SpoofConfig) -> Result<Self> {
154 let syscall_addr = entry.syscall_address.ok_or_else(|| {
155 WraithError::SyscallEnumerationFailed {
156 reason: format!("no syscall address for {}", entry.name),
157 }
158 })?;
159
160 let (gadget_addr, spoof_addr) = match config.mode {
162 SpoofMode::Gadget => {
163 let finder = GadgetFinder::new()?;
164 let gadget = finder.find_best_jmp_gadget()?;
165 (gadget.address(), 0)
166 }
167 SpoofMode::SimpleSpoof => {
168 let spoof = config.custom_spoof_addr.unwrap_or_else(|| {
169 let finder = GadgetFinder::new().ok();
171 finder
172 .and_then(|f| f.find_ret("kernel32.dll").ok())
173 .and_then(|r| r.into_iter().next())
174 .map(|g| g.address())
175 .unwrap_or(0)
176 });
177 (0, spoof)
178 }
179 SpoofMode::SyntheticStack => {
180 let finder = GadgetFinder::new()?;
182 let gadget = finder.find_best_jmp_gadget()?;
183 (gadget.address(), 0)
184 }
185 SpoofMode::None => (0, 0),
186 };
187
188 let trampoline = if config.mode != SpoofMode::None {
190 let alloc = get_trampoline_allocator()?;
191 let tramp = alloc.allocate()?;
192
193 match config.mode {
195 SpoofMode::Gadget => {
196 tramp.write_spoofed_syscall(entry.ssn, syscall_addr, gadget_addr)?;
197 }
198 SpoofMode::SimpleSpoof => {
199 tramp.write_simple_spoofed_syscall(entry.ssn, syscall_addr, spoof_addr)?;
200 }
201 SpoofMode::SyntheticStack => {
202 tramp.write_spoofed_syscall(entry.ssn, syscall_addr, gadget_addr)?;
203 }
204 SpoofMode::None => {}
205 }
206
207 Some(tramp)
208 } else {
209 None
210 };
211
212 Ok(Self {
213 ssn: entry.ssn,
214 syscall_addr,
215 gadget_addr,
216 spoof_addr,
217 name: entry.name.clone(),
218 trampoline,
219 mode: config.mode,
220 })
221 }
222
223 pub fn from_entry(entry: &SyscallEntry) -> Result<Self> {
225 Self::from_entry_with_config(entry, SpoofConfig::default())
226 }
227
228 pub fn ssn(&self) -> u16 {
230 self.ssn
231 }
232
233 pub fn name(&self) -> &str {
235 &self.name
236 }
237
238 pub fn mode(&self) -> SpoofMode {
240 self.mode
241 }
242
243 pub fn gadget_addr(&self) -> Option<usize> {
245 if self.gadget_addr != 0 {
246 Some(self.gadget_addr)
247 } else {
248 None
249 }
250 }
251}
252
253#[cfg(target_arch = "x86_64")]
255impl SpoofedSyscall {
256 #[inline(never)]
261 pub unsafe fn call0(&self) -> i32 {
262 if let Some(ref tramp) = self.trampoline {
263 type Fn0 = unsafe extern "system" fn() -> i32;
264 let f: Fn0 = unsafe { tramp.as_fn_ptr() };
265 unsafe { f() }
266 } else {
267 unsafe { self.call0_direct() }
268 }
269 }
270
271 #[inline(never)]
276 pub unsafe fn call1(&self, arg1: usize) -> i32 {
277 if let Some(ref tramp) = self.trampoline {
278 type Fn1 = unsafe extern "system" fn(usize) -> i32;
279 let f: Fn1 = unsafe { tramp.as_fn_ptr() };
280 unsafe { f(arg1) }
281 } else {
282 unsafe { self.call1_direct(arg1) }
283 }
284 }
285
286 #[inline(never)]
291 pub unsafe fn call2(&self, arg1: usize, arg2: usize) -> i32 {
292 if let Some(ref tramp) = self.trampoline {
293 type Fn2 = unsafe extern "system" fn(usize, usize) -> i32;
294 let f: Fn2 = unsafe { tramp.as_fn_ptr() };
295 unsafe { f(arg1, arg2) }
296 } else {
297 unsafe { self.call2_direct(arg1, arg2) }
298 }
299 }
300
301 #[inline(never)]
306 pub unsafe fn call3(&self, arg1: usize, arg2: usize, arg3: usize) -> i32 {
307 if let Some(ref tramp) = self.trampoline {
308 type Fn3 = unsafe extern "system" fn(usize, usize, usize) -> i32;
309 let f: Fn3 = unsafe { tramp.as_fn_ptr() };
310 unsafe { f(arg1, arg2, arg3) }
311 } else {
312 unsafe { self.call3_direct(arg1, arg2, arg3) }
313 }
314 }
315
316 #[inline(never)]
321 pub unsafe fn call4(&self, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> i32 {
322 if let Some(ref tramp) = self.trampoline {
323 type Fn4 = unsafe extern "system" fn(usize, usize, usize, usize) -> i32;
324 let f: Fn4 = unsafe { tramp.as_fn_ptr() };
325 unsafe { f(arg1, arg2, arg3, arg4) }
326 } else {
327 unsafe { self.call4_direct(arg1, arg2, arg3, arg4) }
328 }
329 }
330
331 #[inline(never)]
336 pub unsafe fn call5(
337 &self,
338 arg1: usize,
339 arg2: usize,
340 arg3: usize,
341 arg4: usize,
342 arg5: usize,
343 ) -> i32 {
344 if let Some(ref tramp) = self.trampoline {
345 type Fn5 = unsafe extern "system" fn(usize, usize, usize, usize, usize) -> i32;
346 let f: Fn5 = unsafe { tramp.as_fn_ptr() };
347 unsafe { f(arg1, arg2, arg3, arg4, arg5) }
348 } else {
349 unsafe { self.call5_direct(arg1, arg2, arg3, arg4, arg5) }
350 }
351 }
352
353 #[inline(never)]
358 pub unsafe fn call6(
359 &self,
360 arg1: usize,
361 arg2: usize,
362 arg3: usize,
363 arg4: usize,
364 arg5: usize,
365 arg6: usize,
366 ) -> i32 {
367 if let Some(ref tramp) = self.trampoline {
368 type Fn6 = unsafe extern "system" fn(usize, usize, usize, usize, usize, usize) -> i32;
369 let f: Fn6 = unsafe { tramp.as_fn_ptr() };
370 unsafe { f(arg1, arg2, arg3, arg4, arg5, arg6) }
371 } else {
372 unsafe { self.call6_direct(arg1, arg2, arg3, arg4, arg5, arg6) }
373 }
374 }
375
376 #[inline(never)]
381 pub unsafe fn call_many(&self, args: &[usize]) -> i32 {
382 match args.len() {
383 0 => unsafe { self.call0() },
384 1 => unsafe { self.call1(args[0]) },
385 2 => unsafe { self.call2(args[0], args[1]) },
386 3 => unsafe { self.call3(args[0], args[1], args[2]) },
387 4 => unsafe { self.call4(args[0], args[1], args[2], args[3]) },
388 5 => unsafe { self.call5(args[0], args[1], args[2], args[3], args[4]) },
389 6 => unsafe { self.call6(args[0], args[1], args[2], args[3], args[4], args[5]) },
390 _ => unsafe { self.call6(args[0], args[1], args[2], args[3], args[4], args[5]) },
391 }
392 }
393
394 #[inline(never)]
396 unsafe fn call0_direct(&self) -> i32 {
397 let status: i32;
398 unsafe {
399 asm!(
400 "sub rsp, 0x28",
401 "mov r10, rcx",
402 "mov eax, {ssn:e}",
403 "call {addr}",
404 "add rsp, 0x28",
405 ssn = in(reg) self.ssn as u32,
406 addr = in(reg) self.syscall_addr,
407 out("eax") status,
408 out("rcx") _,
409 out("r10") _,
410 out("r11") _,
411 );
412 }
413 status
414 }
415
416 #[inline(never)]
417 unsafe fn call1_direct(&self, arg1: usize) -> i32 {
418 let status: i32;
419 unsafe {
420 asm!(
421 "sub rsp, 0x28",
422 "mov r10, rcx",
423 "mov eax, {ssn:e}",
424 "call {addr}",
425 "add rsp, 0x28",
426 ssn = in(reg) self.ssn as u32,
427 addr = in(reg) self.syscall_addr,
428 in("rcx") arg1,
429 out("eax") status,
430 out("r10") _,
431 out("r11") _,
432 );
433 }
434 status
435 }
436
437 #[inline(never)]
438 unsafe fn call2_direct(&self, arg1: usize, arg2: usize) -> i32 {
439 let status: i32;
440 unsafe {
441 asm!(
442 "sub rsp, 0x28",
443 "mov r10, rcx",
444 "mov eax, {ssn:e}",
445 "call {addr}",
446 "add rsp, 0x28",
447 ssn = in(reg) self.ssn as u32,
448 addr = in(reg) self.syscall_addr,
449 in("rcx") arg1,
450 in("rdx") arg2,
451 out("eax") status,
452 out("r10") _,
453 out("r11") _,
454 );
455 }
456 status
457 }
458
459 #[inline(never)]
460 unsafe fn call3_direct(&self, arg1: usize, arg2: usize, arg3: usize) -> i32 {
461 let status: i32;
462 unsafe {
463 asm!(
464 "sub rsp, 0x28",
465 "mov r10, rcx",
466 "mov eax, {ssn:e}",
467 "call {addr}",
468 "add rsp, 0x28",
469 ssn = in(reg) self.ssn as u32,
470 addr = in(reg) self.syscall_addr,
471 in("rcx") arg1,
472 in("rdx") arg2,
473 in("r8") arg3,
474 out("eax") status,
475 out("r10") _,
476 out("r11") _,
477 );
478 }
479 status
480 }
481
482 #[inline(never)]
483 unsafe fn call4_direct(&self, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> i32 {
484 let status: i32;
485 unsafe {
486 asm!(
487 "sub rsp, 0x28",
488 "mov r10, rcx",
489 "mov eax, {ssn:e}",
490 "call {addr}",
491 "add rsp, 0x28",
492 ssn = in(reg) self.ssn as u32,
493 addr = in(reg) self.syscall_addr,
494 in("rcx") arg1,
495 in("rdx") arg2,
496 in("r8") arg3,
497 in("r9") arg4,
498 out("eax") status,
499 out("r10") _,
500 out("r11") _,
501 );
502 }
503 status
504 }
505
506 #[inline(never)]
507 unsafe fn call5_direct(
508 &self,
509 arg1: usize,
510 arg2: usize,
511 arg3: usize,
512 arg4: usize,
513 arg5: usize,
514 ) -> i32 {
515 let status: i32;
516 unsafe {
517 asm!(
518 "sub rsp, 0x28",
519 "mov [rsp+0x20], {arg5}",
520 "mov r10, rcx",
521 "mov eax, {ssn:e}",
522 "call {addr}",
523 "add rsp, 0x28",
524 ssn = in(reg) self.ssn as u32,
525 addr = in(reg) self.syscall_addr,
526 arg5 = in(reg) arg5,
527 in("rcx") arg1,
528 in("rdx") arg2,
529 in("r8") arg3,
530 in("r9") arg4,
531 out("eax") status,
532 out("r10") _,
533 out("r11") _,
534 );
535 }
536 status
537 }
538
539 #[inline(never)]
540 unsafe fn call6_direct(
541 &self,
542 arg1: usize,
543 arg2: usize,
544 arg3: usize,
545 arg4: usize,
546 arg5: usize,
547 arg6: usize,
548 ) -> i32 {
549 let status: i32;
550 unsafe {
551 asm!(
552 "sub rsp, 0x30",
553 "mov [rsp+0x20], {arg5}",
554 "mov [rsp+0x28], {arg6}",
555 "mov r10, rcx",
556 "mov eax, {ssn:e}",
557 "call {addr}",
558 "add rsp, 0x30",
559 ssn = in(reg) self.ssn as u32,
560 addr = in(reg) self.syscall_addr,
561 arg5 = in(reg) arg5,
562 arg6 = in(reg) arg6,
563 in("rcx") arg1,
564 in("rdx") arg2,
565 in("r8") arg3,
566 in("r9") arg4,
567 out("eax") status,
568 out("r10") _,
569 out("r11") _,
570 );
571 }
572 status
573 }
574}
575
576#[cfg(target_arch = "x86")]
578impl SpoofedSyscall {
579 #[inline(never)]
584 pub unsafe fn call(&self, args: &[usize]) -> i32 {
585 let status: i32;
587 let args_ptr = args.as_ptr();
588
589 unsafe {
590 asm!(
591 "mov eax, {ssn:e}",
592 "mov edx, {args}",
593 "call {addr}",
594 ssn = in(reg) self.ssn as u32,
595 args = in(reg) args_ptr,
596 addr = in(reg) self.syscall_addr,
597 out("eax") status,
598 options(nostack)
599 );
600 }
601 status
602 }
603
604 pub unsafe fn call0(&self) -> i32 {
605 unsafe { self.call(&[]) }
606 }
607
608 pub unsafe fn call1(&self, arg1: usize) -> i32 {
609 unsafe { self.call(&[arg1]) }
610 }
611
612 pub unsafe fn call2(&self, arg1: usize, arg2: usize) -> i32 {
613 unsafe { self.call(&[arg1, arg2]) }
614 }
615
616 pub unsafe fn call3(&self, arg1: usize, arg2: usize, arg3: usize) -> i32 {
617 unsafe { self.call(&[arg1, arg2, arg3]) }
618 }
619
620 pub unsafe fn call4(&self, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> i32 {
621 unsafe { self.call(&[arg1, arg2, arg3, arg4]) }
622 }
623
624 pub unsafe fn call5(
625 &self,
626 arg1: usize,
627 arg2: usize,
628 arg3: usize,
629 arg4: usize,
630 arg5: usize,
631 ) -> i32 {
632 unsafe { self.call(&[arg1, arg2, arg3, arg4, arg5]) }
633 }
634
635 pub unsafe fn call6(
636 &self,
637 arg1: usize,
638 arg2: usize,
639 arg3: usize,
640 arg4: usize,
641 arg5: usize,
642 arg6: usize,
643 ) -> i32 {
644 unsafe { self.call(&[arg1, arg2, arg3, arg4, arg5, arg6]) }
645 }
646
647 pub unsafe fn call_many(&self, args: &[usize]) -> i32 {
648 unsafe { self.call(args) }
649 }
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655
656 #[test]
657 fn test_spoofed_syscall_creation() {
658 match SpoofedSyscall::new("NtClose") {
659 Ok(syscall) => {
660 assert!(syscall.ssn() > 0);
661 assert!(syscall.gadget_addr().is_some());
662 }
663 Err(_) => {
664 }
666 }
667 }
668
669 #[test]
670 fn test_spoofed_syscall_ntclose() {
671 if let Ok(syscall) = SpoofedSyscall::new("NtClose") {
672 let status = unsafe { syscall.call1(0xDEADBEEF) };
674 assert_eq!(
675 status, 0xC0000008_u32 as i32,
676 "should return STATUS_INVALID_HANDLE"
677 );
678 }
679 }
680
681 #[test]
682 fn test_simple_spoof_mode() {
683 if let Ok(syscall) = SpoofedSyscall::with_config("NtClose", SpoofConfig {
684 mode: SpoofMode::SimpleSpoof,
685 custom_spoof_addr: Some(0x7FFE0000), ..Default::default()
687 }) {
688 let status = unsafe { syscall.call1(0xDEADBEEF) };
689 assert_eq!(status, 0xC0000008_u32 as i32);
690 }
691 }
692
693 #[test]
694 fn test_none_mode_fallback() {
695 if let Ok(syscall) = SpoofedSyscall::with_config("NtClose", SpoofConfig {
696 mode: SpoofMode::None,
697 ..Default::default()
698 }) {
699 let status = unsafe { syscall.call1(0xDEADBEEF) };
700 assert_eq!(status, 0xC0000008_u32 as i32);
701 }
702 }
703}