wraith/manipulation/syscall/
table.rs1use super::enumerator::{enumerate_syscalls, EnumeratedSyscall};
6use crate::error::{Result, WraithError};
7use crate::util::hash::djb2_hash;
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
12pub struct SyscallEntry {
13 pub name: String,
15 pub name_hash: u32,
17 pub ssn: u16,
19 pub address: usize,
21 pub syscall_address: Option<usize>,
23}
24
25impl From<EnumeratedSyscall> for SyscallEntry {
26 fn from(sc: EnumeratedSyscall) -> Self {
27 Self {
28 name: sc.name,
29 name_hash: sc.name_hash,
30 ssn: sc.ssn,
31 address: sc.address,
32 syscall_address: sc.syscall_address,
33 }
34 }
35}
36
37pub struct SyscallTable {
39 by_hash: HashMap<u32, SyscallEntry>,
41 by_ssn: HashMap<u16, SyscallEntry>,
43 entries: Vec<SyscallEntry>,
45}
46
47impl SyscallTable {
48 pub fn enumerate() -> Result<Self> {
50 let syscalls = enumerate_syscalls()?;
51
52 let mut by_hash = HashMap::with_capacity(syscalls.len());
53 let mut by_ssn = HashMap::with_capacity(syscalls.len());
54 let mut entries = Vec::with_capacity(syscalls.len());
55
56 for sc in syscalls {
57 let entry = SyscallEntry::from(sc);
58
59 by_hash.insert(entry.name_hash, entry.clone());
60 by_ssn.insert(entry.ssn, entry.clone());
61 entries.push(entry);
62 }
63
64 Ok(Self {
65 by_hash,
66 by_ssn,
67 entries,
68 })
69 }
70
71 pub fn get(&self, name: &str) -> Option<&SyscallEntry> {
73 let hash = djb2_hash(name.as_bytes());
74 self.by_hash.get(&hash)
75 }
76
77 pub fn get_by_hash(&self, hash: u32) -> Option<&SyscallEntry> {
79 self.by_hash.get(&hash)
80 }
81
82 pub fn get_by_ssn(&self, ssn: u16) -> Option<&SyscallEntry> {
84 self.by_ssn.get(&ssn)
85 }
86
87 pub fn entries(&self) -> &[SyscallEntry] {
89 &self.entries
90 }
91
92 pub fn len(&self) -> usize {
94 self.entries.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.entries.is_empty()
100 }
101
102 pub fn get_ssn(&self, name: &str) -> Option<u16> {
104 self.get(name).map(|e| e.ssn)
105 }
106
107 pub fn get_syscall_address(&self, name: &str) -> Option<usize> {
109 self.get(name).and_then(|e| e.syscall_address)
110 }
111
112 pub fn find_by_address(&self, addr: usize) -> Option<&SyscallEntry> {
114 self.entries
116 .iter()
117 .find(|e| addr >= e.address && addr < e.address + 32)
118 }
119
120 pub fn require(&self, name: &str) -> Result<&SyscallEntry> {
122 self.get(name).ok_or_else(|| WraithError::SyscallNotFound {
123 name: name.to_string(),
124 })
125 }
126
127 pub fn require_by_hash(&self, hash: u32) -> Result<&SyscallEntry> {
129 self.get_by_hash(hash)
130 .ok_or_else(|| WraithError::SyscallNotFound {
131 name: format!("hash {hash:#x}"),
132 })
133 }
134}
135
136pub mod hashes {
138 use crate::util::hash::djb2_hash;
139
140 pub const NT_OPEN_PROCESS: u32 = djb2_hash(b"NtOpenProcess");
141 pub const NT_CLOSE: u32 = djb2_hash(b"NtClose");
142 pub const NT_READ_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtReadVirtualMemory");
143 pub const NT_WRITE_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtWriteVirtualMemory");
144 pub const NT_ALLOCATE_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtAllocateVirtualMemory");
145 pub const NT_FREE_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtFreeVirtualMemory");
146 pub const NT_PROTECT_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtProtectVirtualMemory");
147 pub const NT_QUERY_INFORMATION_PROCESS: u32 = djb2_hash(b"NtQueryInformationProcess");
148 pub const NT_SET_INFORMATION_THREAD: u32 = djb2_hash(b"NtSetInformationThread");
149 pub const NT_CREATE_THREAD_EX: u32 = djb2_hash(b"NtCreateThreadEx");
150 pub const NT_QUERY_SYSTEM_INFORMATION: u32 = djb2_hash(b"NtQuerySystemInformation");
151 pub const NT_QUERY_VIRTUAL_MEMORY: u32 = djb2_hash(b"NtQueryVirtualMemory");
152 pub const NT_OPEN_FILE: u32 = djb2_hash(b"NtOpenFile");
153 pub const NT_CREATE_FILE: u32 = djb2_hash(b"NtCreateFile");
154 pub const NT_READ_FILE: u32 = djb2_hash(b"NtReadFile");
155 pub const NT_WRITE_FILE: u32 = djb2_hash(b"NtWriteFile");
156 pub const NT_CREATE_SECTION: u32 = djb2_hash(b"NtCreateSection");
157 pub const NT_MAP_VIEW_OF_SECTION: u32 = djb2_hash(b"NtMapViewOfSection");
158 pub const NT_UNMAP_VIEW_OF_SECTION: u32 = djb2_hash(b"NtUnmapViewOfSection");
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_enumerate_table() {
167 let table = SyscallTable::enumerate().expect("should enumerate");
168 assert!(!table.is_empty());
169 }
170
171 #[test]
172 fn test_lookup_by_name() {
173 let table = SyscallTable::enumerate().expect("should enumerate");
174
175 let entry = table.get("NtClose").expect("should find NtClose");
176 assert_eq!(entry.name, "NtClose");
177 }
178
179 #[test]
180 fn test_lookup_by_hash() {
181 let table = SyscallTable::enumerate().expect("should enumerate");
182
183 let hash = djb2_hash(b"NtClose");
184 let entry = table.get_by_hash(hash).expect("should find by hash");
185 assert_eq!(entry.name, "NtClose");
186 }
187
188 #[test]
189 fn test_precomputed_hash() {
190 let table = SyscallTable::enumerate().expect("should enumerate");
191
192 let entry = table
193 .get_by_hash(hashes::NT_CLOSE)
194 .expect("should find by precomputed hash");
195 assert_eq!(entry.name, "NtClose");
196 }
197
198 #[test]
199 fn test_require() {
200 let table = SyscallTable::enumerate().expect("should enumerate");
201
202 assert!(table.require("NtClose").is_ok());
203 assert!(table.require("NonExistentSyscall").is_err());
204 }
205}