wraith/manipulation/syscall/
table.rs1use super::enumerator::{enumerate_syscalls, EnumeratedSyscall};
6use crate::error::{Result, WraithError};
7use crate::util::hash::djb2_hash;
8
9#[cfg(feature = "std")]
10use std::collections::HashMap;
11
12#[cfg(all(not(feature = "std"), feature = "alloc"))]
13use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
14
15#[cfg(feature = "std")]
16use std::{format, string::String, vec::Vec};
17
18#[derive(Debug, Clone)]
20pub struct SyscallEntry {
21 pub name: String,
23 pub name_hash: u32,
25 pub ssn: u16,
27 pub address: usize,
29 pub syscall_address: Option<usize>,
31}
32
33impl From<EnumeratedSyscall> for SyscallEntry {
34 fn from(sc: EnumeratedSyscall) -> Self {
35 Self {
36 name: sc.name,
37 name_hash: sc.name_hash,
38 ssn: sc.ssn,
39 address: sc.address,
40 syscall_address: sc.syscall_address,
41 }
42 }
43}
44
45#[cfg(feature = "std")]
47pub struct SyscallTable {
48 by_hash: HashMap<u32, SyscallEntry>,
50 by_ssn: HashMap<u16, SyscallEntry>,
52 entries: Vec<SyscallEntry>,
54}
55
56#[cfg(all(not(feature = "std"), feature = "alloc"))]
58pub struct SyscallTable {
59 by_hash: BTreeMap<u32, SyscallEntry>,
61 by_ssn: BTreeMap<u16, SyscallEntry>,
63 entries: Vec<SyscallEntry>,
65}
66
67impl SyscallTable {
68 #[cfg(feature = "std")]
70 pub fn enumerate() -> Result<Self> {
71 let syscalls = enumerate_syscalls()?;
72
73 let mut by_hash = HashMap::with_capacity(syscalls.len());
74 let mut by_ssn = HashMap::with_capacity(syscalls.len());
75 let mut entries = Vec::with_capacity(syscalls.len());
76
77 for sc in syscalls {
78 let entry = SyscallEntry::from(sc);
79
80 by_hash.insert(entry.name_hash, entry.clone());
81 by_ssn.insert(entry.ssn, entry.clone());
82 entries.push(entry);
83 }
84
85 Ok(Self {
86 by_hash,
87 by_ssn,
88 entries,
89 })
90 }
91
92 #[cfg(all(not(feature = "std"), feature = "alloc"))]
94 pub fn enumerate() -> Result<Self> {
95 let syscalls = enumerate_syscalls()?;
96
97 let mut by_hash = BTreeMap::new();
98 let mut by_ssn = BTreeMap::new();
99 let mut entries = Vec::with_capacity(syscalls.len());
100
101 for sc in syscalls {
102 let entry = SyscallEntry::from(sc);
103
104 by_hash.insert(entry.name_hash, entry.clone());
105 by_ssn.insert(entry.ssn, entry.clone());
106 entries.push(entry);
107 }
108
109 Ok(Self {
110 by_hash,
111 by_ssn,
112 entries,
113 })
114 }
115
116 pub fn get(&self, name: &str) -> Option<&SyscallEntry> {
118 let hash = djb2_hash(name.as_bytes());
119 self.by_hash.get(&hash)
120 }
121
122 pub fn get_by_hash(&self, hash: u32) -> Option<&SyscallEntry> {
124 self.by_hash.get(&hash)
125 }
126
127 pub fn get_by_ssn(&self, ssn: u16) -> Option<&SyscallEntry> {
129 self.by_ssn.get(&ssn)
130 }
131
132 pub fn entries(&self) -> &[SyscallEntry] {
134 &self.entries
135 }
136
137 pub fn len(&self) -> usize {
139 self.entries.len()
140 }
141
142 pub fn is_empty(&self) -> bool {
144 self.entries.is_empty()
145 }
146
147 pub fn get_ssn(&self, name: &str) -> Option<u16> {
149 self.get(name).map(|e| e.ssn)
150 }
151
152 pub fn get_syscall_address(&self, name: &str) -> Option<usize> {
154 self.get(name).and_then(|e| e.syscall_address)
155 }
156
157 pub fn find_by_address(&self, addr: usize) -> Option<&SyscallEntry> {
159 self.entries
161 .iter()
162 .find(|e| addr >= e.address && addr < e.address + 32)
163 }
164
165 pub fn require(&self, name: &str) -> Result<&SyscallEntry> {
167 self.get(name).ok_or_else(|| WraithError::SyscallNotFound {
168 name: name.to_string(),
169 })
170 }
171
172 pub fn require_by_hash(&self, hash: u32) -> Result<&SyscallEntry> {
174 self.get_by_hash(hash)
175 .ok_or_else(|| WraithError::SyscallNotFound {
176 name: format!("hash {hash:#x}"),
177 })
178 }
179}
180
181pub mod hashes {
183 use crate::util::hash::djb2_hash;
184
185 pub const NT_OPEN_PROCESS: u32 = djb2_hash(b"NtOpenProcess");
186 pub const NT_CLOSE: u32 = djb2_hash(b"NtClose");
187 pub const NT_READ_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtReadVirtualMemory");
188 pub const NT_WRITE_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtWriteVirtualMemory");
189 pub const NT_ALLOCATE_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtAllocateVirtualMemory");
190 pub const NT_FREE_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtFreeVirtualMemory");
191 pub const NT_PROTECT_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtProtectVirtualMemory");
192 pub const NT_QUERY_INFORMATION_PROCESS: u32 = djb2_hash(b"NtQueryInformationProcess");
193 pub const NT_SET_INFORMATION_THREAD: u32 = djb2_hash(b"NtSetInformationThread");
194 pub const NT_CREATE_THREAD_EX: u32 = djb2_hash(b"NtCreateThreadEx");
195 pub const NT_QUERY_SYSTEM_INFORMATION: u32 = djb2_hash(b"NtQuerySystemInformation");
196 pub const NT_QUERY_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtQueryVirtualMemory");
197 pub const NT_OPEN_FILE: u32 = djb2_hash(b"NtOpenFile");
198 pub const NT_CREATE_FILE: u32 = djb2_hash(b"NtCreateFile");
199 pub const NT_READ_FILE: u32 = djb2_hash(b"NtReadFile");
200 pub const NT_WRITE_FILE: u32 = djb2_hash(b"NtWriteFile");
201 pub const NT_CREATE_SECTION: u32 = djb2_hash(b"NtCreateSection");
202 pub const NT_MAP_VIEW_OF_SECTION: u32 = djb2_hash(b"NtMapViewOfSection");
203 pub const NT_UNMAP_VIEW_OF_SECTION: u32 = djb2_hash(b"NtUnmapViewOfSection");
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_enumerate_table() {
212 let table = SyscallTable::enumerate().expect("should enumerate");
213 assert!(!table.is_empty());
214 }
215
216 #[test]
217 fn test_lookup_by_name() {
218 let table = SyscallTable::enumerate().expect("should enumerate");
219
220 let entry = table.get("NtClose").expect("should find NtClose");
221 assert_eq!(entry.name, "NtClose");
222 }
223
224 #[test]
225 fn test_lookup_by_hash() {
226 let table = SyscallTable::enumerate().expect("should enumerate");
227
228 let hash = djb2_hash(b"NtClose");
229 let entry = table.get_by_hash(hash).expect("should find by hash");
230 assert_eq!(entry.name, "NtClose");
231 }
232
233 #[test]
234 fn test_precomputed_hash() {
235 let table = SyscallTable::enumerate().expect("should enumerate");
236
237 let entry = table
238 .get_by_hash(hashes::NT_CLOSE)
239 .expect("should find by precomputed hash");
240 assert_eq!(entry.name, "NtClose");
241 }
242
243 #[test]
244 fn test_require() {
245 let table = SyscallTable::enumerate().expect("should enumerate");
246
247 assert!(table.require("NtClose").is_ok());
248 assert!(table.require("NonExistentSyscall").is_err());
249 }
250}