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