wraith/manipulation/syscall/
mod.rs

1//! Direct and indirect syscall infrastructure
2//!
3//! This module provides the ability to invoke Windows syscalls directly,
4//! bypassing usermode hooks placed by EDRs on ntdll functions.
5//!
6//! # Modes
7//!
8//! - **Direct**: Inline `syscall` instruction with SSN in eax
9//! - **Indirect**: Jump to ntdll's syscall instruction for cleaner call stack
10//! - **Native**: Fall back to normal API calls
11//!
12//! # Usage
13//!
14//! ```no_run
15//! use wraith::manipulation::syscall::{get_syscall_table, DirectSyscall};
16//!
17//! let table = get_syscall_table().unwrap();
18//! let syscall = DirectSyscall::from_table(&table, "NtClose").unwrap();
19//! let status = unsafe { syscall.call1(handle) };
20//! ```
21
22mod direct;
23mod enumerator;
24mod indirect;
25mod table;
26mod wrappers;
27
28pub use direct::DirectSyscall;
29pub use enumerator::{enumerate_syscalls, EnumeratedSyscall, SyscallEnumerator};
30pub use indirect::IndirectSyscall;
31pub use table::{hashes, SyscallEntry, SyscallTable};
32pub use wrappers::*;
33
34use crate::error::Result;
35
36#[cfg(feature = "std")]
37use std::sync::OnceLock;
38
39#[cfg(all(not(feature = "std"), feature = "alloc"))]
40use alloc::format;
41
42#[cfg(feature = "std")]
43static SYSCALL_TABLE: OnceLock<Result<SyscallTable>> = OnceLock::new();
44
45/// get or initialize the global syscall table
46///
47/// note: in no_std mode, this creates a new table each call since
48/// global lazy initialization requires std. consider caching the
49/// result yourself in no_std environments.
50#[cfg(feature = "std")]
51pub fn get_syscall_table() -> Result<&'static SyscallTable> {
52    let result = SYSCALL_TABLE.get_or_init(SyscallTable::enumerate);
53    match result {
54        Ok(table) => Ok(table),
55        Err(e) => Err(crate::error::WraithError::SyscallEnumerationFailed {
56            reason: format!("{}", e),
57        }),
58    }
59}
60
61/// enumerate syscall table (no caching)
62///
63/// in no_std mode, you must cache the result yourself if needed.
64#[cfg(not(feature = "std"))]
65pub fn enumerate_syscall_table() -> Result<SyscallTable> {
66    SyscallTable::enumerate()
67}
68
69/// syscall invocation mode
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum SyscallMode {
72    /// inline syscall instruction (mov eax, ssn; syscall)
73    Direct,
74    /// jump to syscall instruction in ntdll
75    Indirect,
76    /// use normal API call (fallback)
77    Native,
78}
79
80impl Default for SyscallMode {
81    fn default() -> Self {
82        Self::Direct
83    }
84}
85
86/// preferred syscall mode (can be changed at runtime)
87static PREFERRED_MODE: core::sync::atomic::AtomicU8 = core::sync::atomic::AtomicU8::new(0);
88
89/// set preferred syscall mode
90pub fn set_syscall_mode(mode: SyscallMode) {
91    let value = match mode {
92        SyscallMode::Direct => 0,
93        SyscallMode::Indirect => 1,
94        SyscallMode::Native => 2,
95    };
96    PREFERRED_MODE.store(value, core::sync::atomic::Ordering::Relaxed);
97}
98
99/// get current syscall mode
100pub fn get_syscall_mode() -> SyscallMode {
101    match PREFERRED_MODE.load(core::sync::atomic::Ordering::Relaxed) {
102        0 => SyscallMode::Direct,
103        1 => SyscallMode::Indirect,
104        _ => SyscallMode::Native,
105    }
106}
107
108/// check NTSTATUS for success
109#[inline]
110pub const fn nt_success(status: i32) -> bool {
111    status >= 0
112}
113
114/// NTSTATUS codes
115pub mod status {
116    pub const STATUS_SUCCESS: i32 = 0;
117    pub const STATUS_INVALID_HANDLE: i32 = 0xC0000008_u32 as i32;
118    pub const STATUS_ACCESS_DENIED: i32 = 0xC0000022_u32 as i32;
119    pub const STATUS_BUFFER_TOO_SMALL: i32 = 0xC0000023_u32 as i32;
120    pub const STATUS_INFO_LENGTH_MISMATCH: i32 = 0xC0000004_u32 as i32;
121}