wraith/
error.rs

1//! Unified error types for wraith-rs
2
3use core::fmt;
4
5/// all errors that can occur in wraith-rs
6#[derive(Debug)]
7pub enum WraithError {
8    // === version/architecture ===
9    /// windows version not supported by this library
10    UnsupportedWindowsVersion { major: u32, minor: u32, build: u32 },
11
12    /// architecture not supported (e.g., ARM)
13    UnsupportedArchitecture,
14
15    // === structure access ===
16    /// failed to access PEB
17    InvalidPebAccess,
18
19    /// failed to access TEB
20    InvalidTebAccess,
21
22    /// structure data appears corrupted
23    CorruptedStructure {
24        name: &'static str,
25        reason: &'static str,
26    },
27
28    /// null pointer where non-null expected
29    NullPointer { context: &'static str },
30
31    // === navigation ===
32    /// module with given name not found
33    ModuleNotFound { name: String },
34
35    /// address does not belong to any loaded module
36    AddressNotInModule { address: u64 },
37
38    /// thread with given ID not found
39    ThreadNotFound { tid: u32 },
40
41    // === manipulation ===
42    /// failed to unlink module from PEB lists
43    UnlinkFailed { module: String, reason: String },
44
45    /// failed to restore module links
46    RelinkFailed { module: String, reason: String },
47
48    /// PEB module list appears corrupted
49    ListCorrupted { list_type: ModuleListType },
50
51    // === manual mapping ===
52    /// PE file format invalid or unsupported
53    InvalidPeFormat { reason: String },
54
55    /// memory allocation failed
56    AllocationFailed { size: usize, protection: u32 },
57
58    /// failed to map PE section
59    MappingFailed { section: String, reason: String },
60
61    /// failed to process relocation entry
62    RelocationFailed { rva: u32, reason: String },
63
64    /// failed to resolve import
65    ImportResolutionFailed { dll: String, function: String },
66
67    /// export is forwarded to another module
68    ForwardedExport { forwarder: String },
69
70    /// TLS callback execution failed
71    TlsCallbackFailed { index: usize },
72
73    /// DllMain returned FALSE
74    EntryPointFailed { status: i32 },
75
76    // === syscalls ===
77    /// failed to enumerate syscalls from ntdll
78    SyscallEnumerationFailed { reason: String },
79
80    /// syscall with given name/hash not found
81    SyscallNotFound { name: String },
82
83    /// syscall returned error status
84    SyscallFailed { name: String, status: i32 },
85
86    /// ntdll.dll not found in loaded modules
87    NtdllNotFound,
88
89    // === hooks ===
90    /// failed to detect hooks in function
91    HookDetectionFailed { function: String, reason: String },
92
93    /// failed to remove hook
94    UnhookFailed { function: String, reason: String },
95
96    /// function integrity check failed
97    IntegrityCheckFailed { function: String },
98
99    /// clean copy of module not available
100    CleanCopyUnavailable,
101
102    // === inline hooks ===
103    /// failed to install inline hook
104    HookInstallFailed { target: u64, reason: String },
105
106    /// failed to restore hook
107    HookRestoreFailed { target: u64, reason: String },
108
109    /// instruction decoding failed
110    InstructionDecodeFailed { address: u64, reason: String },
111
112    /// trampoline allocation failed
113    TrampolineAllocationFailed { near: u64, size: usize },
114
115    /// hook conflict detected (target already hooked)
116    HookConflict { target: u64, existing_type: String },
117
118    /// insufficient space for hook at target
119    InsufficientHookSpace { target: u64, available: usize, required: usize },
120
121    // === memory ===
122    /// memory read operation failed
123    ReadFailed { address: u64, size: usize },
124
125    /// memory write operation failed
126    WriteFailed { address: u64, size: usize },
127
128    /// failed to change memory protection
129    ProtectionChangeFailed { address: u64, size: usize },
130
131    // === pattern scanning ===
132    /// failed to parse pattern string
133    PatternParseFailed { reason: String },
134
135    // === remote process ===
136    /// failed to open remote process
137    ProcessOpenFailed { pid: u32, reason: String },
138
139    /// process with given ID not found
140    ProcessNotFound { pid: u32 },
141
142    /// failed to create remote thread
143    RemoteThreadFailed { reason: String },
144
145    /// failed to inject into remote process
146    InjectionFailed { method: String, reason: String },
147
148    /// failed to enumerate remote modules
149    RemoteModuleEnumFailed { reason: String },
150
151    /// failed to duplicate handle
152    HandleDuplicateFailed { reason: String },
153
154    /// section mapping failed
155    SectionMappingFailed { reason: String },
156
157    /// APC queue operation failed
158    ApcQueueFailed { reason: String },
159
160    /// thread context operation failed
161    ThreadContextFailed { reason: String },
162
163    /// thread suspend/resume failed
164    ThreadSuspendResumeFailed { reason: String },
165
166    // === spoofing ===
167    /// no suitable gadget found for spoofing
168    GadgetNotFound { gadget_type: &'static str },
169
170    /// failed to build spoof trampoline
171    SpoofTrampolineFailed { reason: String },
172
173    /// stack frame synthesis failed
174    StackSynthesisFailed { reason: String },
175
176    // === win32 ===
177    /// underlying Win32 API returned error
178    Win32Error { code: u32, context: &'static str },
179}
180
181/// which PEB module list had an issue
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum ModuleListType {
184    InLoadOrder,
185    InMemoryOrder,
186    InInitializationOrder,
187}
188
189impl fmt::Display for ModuleListType {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match self {
192            Self::InLoadOrder => write!(f, "InLoadOrderModuleList"),
193            Self::InMemoryOrder => write!(f, "InMemoryOrderModuleList"),
194            Self::InInitializationOrder => write!(f, "InInitializationOrderModuleList"),
195        }
196    }
197}
198
199impl std::error::Error for WraithError {}
200
201impl fmt::Display for WraithError {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            Self::UnsupportedWindowsVersion { major, minor, build } => {
205                write!(f, "unsupported Windows version: {major}.{minor}.{build}")
206            }
207            Self::UnsupportedArchitecture => {
208                write!(f, "unsupported architecture (only x86/x64 supported)")
209            }
210            Self::InvalidPebAccess => {
211                write!(f, "failed to access PEB")
212            }
213            Self::InvalidTebAccess => {
214                write!(f, "failed to access TEB")
215            }
216            Self::CorruptedStructure { name, reason } => {
217                write!(f, "corrupted structure {name}: {reason}")
218            }
219            Self::NullPointer { context } => {
220                write!(f, "unexpected null pointer in {context}")
221            }
222            Self::ModuleNotFound { name } => {
223                write!(f, "module not found: {name}")
224            }
225            Self::AddressNotInModule { address } => {
226                write!(f, "address {address:#x} not in any loaded module")
227            }
228            Self::ThreadNotFound { tid } => {
229                write!(f, "thread {tid} not found")
230            }
231            Self::UnlinkFailed { module, reason } => {
232                write!(f, "failed to unlink {module}: {reason}")
233            }
234            Self::RelinkFailed { module, reason } => {
235                write!(f, "failed to relink {module}: {reason}")
236            }
237            Self::ListCorrupted { list_type } => {
238                write!(f, "PEB {list_type} appears corrupted")
239            }
240            Self::InvalidPeFormat { reason } => {
241                write!(f, "invalid PE format: {reason}")
242            }
243            Self::AllocationFailed { size, protection } => {
244                write!(
245                    f,
246                    "failed to allocate {size} bytes with protection {protection:#x}"
247                )
248            }
249            Self::MappingFailed { section, reason } => {
250                write!(f, "failed to map section {section}: {reason}")
251            }
252            Self::RelocationFailed { rva, reason } => {
253                write!(f, "relocation failed at RVA {rva:#x}: {reason}")
254            }
255            Self::ImportResolutionFailed { dll, function } => {
256                write!(f, "failed to resolve {dll}!{function}")
257            }
258            Self::ForwardedExport { forwarder } => {
259                write!(f, "export forwarded to {forwarder}")
260            }
261            Self::TlsCallbackFailed { index } => {
262                write!(f, "TLS callback {index} failed")
263            }
264            Self::EntryPointFailed { status } => {
265                write!(f, "entry point returned {status}")
266            }
267            Self::SyscallEnumerationFailed { reason } => {
268                write!(f, "syscall enumeration failed: {reason}")
269            }
270            Self::SyscallNotFound { name } => {
271                write!(f, "syscall not found: {name}")
272            }
273            Self::SyscallFailed { name, status } => {
274                write!(f, "syscall {name} failed with status {status:#x}")
275            }
276            Self::NtdllNotFound => {
277                write!(f, "ntdll.dll not found in loaded modules")
278            }
279            Self::HookDetectionFailed { function, reason } => {
280                write!(f, "hook detection failed for {function}: {reason}")
281            }
282            Self::UnhookFailed { function, reason } => {
283                write!(f, "unhook failed for {function}: {reason}")
284            }
285            Self::IntegrityCheckFailed { function } => {
286                write!(f, "integrity check failed for {function}")
287            }
288            Self::CleanCopyUnavailable => {
289                write!(f, "clean copy of module not available for comparison")
290            }
291            Self::HookInstallFailed { target, reason } => {
292                write!(f, "failed to install hook at {target:#x}: {reason}")
293            }
294            Self::HookRestoreFailed { target, reason } => {
295                write!(f, "failed to restore hook at {target:#x}: {reason}")
296            }
297            Self::InstructionDecodeFailed { address, reason } => {
298                write!(f, "instruction decode failed at {address:#x}: {reason}")
299            }
300            Self::TrampolineAllocationFailed { near, size } => {
301                write!(f, "failed to allocate {size} bytes trampoline near {near:#x}")
302            }
303            Self::HookConflict { target, existing_type } => {
304                write!(f, "hook conflict at {target:#x}: already hooked ({existing_type})")
305            }
306            Self::InsufficientHookSpace { target, available, required } => {
307                write!(
308                    f,
309                    "insufficient hook space at {target:#x}: need {required} bytes, have {available}"
310                )
311            }
312            Self::ReadFailed { address, size } => {
313                write!(f, "failed to read {size} bytes at {address:#x}")
314            }
315            Self::WriteFailed { address, size } => {
316                write!(f, "failed to write {size} bytes at {address:#x}")
317            }
318            Self::ProtectionChangeFailed { address, size } => {
319                write!(
320                    f,
321                    "failed to change protection for {size} bytes at {address:#x}"
322                )
323            }
324            Self::PatternParseFailed { reason } => {
325                write!(f, "failed to parse pattern: {reason}")
326            }
327            Self::ProcessOpenFailed { pid, reason } => {
328                write!(f, "failed to open process {pid}: {reason}")
329            }
330            Self::ProcessNotFound { pid } => {
331                write!(f, "process {pid} not found")
332            }
333            Self::RemoteThreadFailed { reason } => {
334                write!(f, "remote thread creation failed: {reason}")
335            }
336            Self::InjectionFailed { method, reason } => {
337                write!(f, "injection via {method} failed: {reason}")
338            }
339            Self::RemoteModuleEnumFailed { reason } => {
340                write!(f, "remote module enumeration failed: {reason}")
341            }
342            Self::HandleDuplicateFailed { reason } => {
343                write!(f, "handle duplication failed: {reason}")
344            }
345            Self::SectionMappingFailed { reason } => {
346                write!(f, "section mapping failed: {reason}")
347            }
348            Self::ApcQueueFailed { reason } => {
349                write!(f, "APC queue operation failed: {reason}")
350            }
351            Self::ThreadContextFailed { reason } => {
352                write!(f, "thread context operation failed: {reason}")
353            }
354            Self::ThreadSuspendResumeFailed { reason } => {
355                write!(f, "thread suspend/resume failed: {reason}")
356            }
357            Self::GadgetNotFound { gadget_type } => {
358                write!(f, "no suitable {gadget_type} gadget found")
359            }
360            Self::SpoofTrampolineFailed { reason } => {
361                write!(f, "spoof trampoline failed: {reason}")
362            }
363            Self::StackSynthesisFailed { reason } => {
364                write!(f, "stack frame synthesis failed: {reason}")
365            }
366            Self::Win32Error { code, context } => {
367                write!(f, "Win32 error {code:#x} in {context}")
368            }
369        }
370    }
371}
372
373/// result type alias using WraithError
374pub type Result<T> = std::result::Result<T, WraithError>;
375
376impl WraithError {
377    /// create Win32Error from GetLastError
378    pub fn from_last_error(context: &'static str) -> Self {
379        // SAFETY: GetLastError is always safe to call
380        let code = unsafe { GetLastError() };
381        Self::Win32Error { code, context }
382    }
383}
384
385#[link(name = "kernel32")]
386extern "system" {
387    fn GetLastError() -> u32;
388}