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;
35use std::sync::OnceLock;
36
37/// global syscall table (initialized on first use)
38static SYSCALL_TABLE: OnceLock<Result<SyscallTable>> = OnceLock::new();
39
40/// get or initialize the global syscall table
41pub fn get_syscall_table() -> Result<&'static SyscallTable> {
42    let result = SYSCALL_TABLE.get_or_init(SyscallTable::enumerate);
43    match result {
44        Ok(table) => Ok(table),
45        Err(e) => Err(crate::error::WraithError::SyscallEnumerationFailed {
46            reason: format!("{}", e),
47        }),
48    }
49}
50
51/// syscall invocation mode
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum SyscallMode {
54    /// inline syscall instruction (mov eax, ssn; syscall)
55    Direct,
56    /// jump to syscall instruction in ntdll
57    Indirect,
58    /// use normal API call (fallback)
59    Native,
60}
61
62impl Default for SyscallMode {
63    fn default() -> Self {
64        Self::Direct
65    }
66}
67
68/// preferred syscall mode (can be changed at runtime)
69static PREFERRED_MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
70
71/// set preferred syscall mode
72pub fn set_syscall_mode(mode: SyscallMode) {
73    let value = match mode {
74        SyscallMode::Direct => 0,
75        SyscallMode::Indirect => 1,
76        SyscallMode::Native => 2,
77    };
78    PREFERRED_MODE.store(value, std::sync::atomic::Ordering::Relaxed);
79}
80
81/// get current syscall mode
82pub fn get_syscall_mode() -> SyscallMode {
83    match PREFERRED_MODE.load(std::sync::atomic::Ordering::Relaxed) {
84        0 => SyscallMode::Direct,
85        1 => SyscallMode::Indirect,
86        _ => SyscallMode::Native,
87    }
88}
89
90/// check NTSTATUS for success
91#[inline]
92pub const fn nt_success(status: i32) -> bool {
93    status >= 0
94}
95
96/// NTSTATUS codes
97pub mod status {
98    pub const STATUS_SUCCESS: i32 = 0;
99    pub const STATUS_INVALID_HANDLE: i32 = 0xC0000008_u32 as i32;
100    pub const STATUS_ACCESS_DENIED: i32 = 0xC0000022_u32 as i32;
101    pub const STATUS_BUFFER_TOO_SMALL: i32 = 0xC0000023_u32 as i32;
102    pub const STATUS_INFO_LENGTH_MISMATCH: i32 = 0xC0000004_u32 as i32;
103}