1#[cfg(all(not(feature = "std"), feature = "alloc"))]
9use alloc::{format, string::{String, ToString}};
10
11#[cfg(feature = "std")]
12use std::{format, string::{String, ToString}};
13
14use super::table::{SyscallEntry, SyscallTable};
15use crate::error::{Result, WraithError};
16use core::arch::asm;
17
18#[cfg(target_arch = "x86_64")]
19const SYSCALL_BYTES: [u8; 2] = [0x0F, 0x05]; #[cfg(target_arch = "x86")]
22const SYSCALL_BYTES: [u8; 2] = [0x0F, 0x34]; pub struct IndirectSyscall {
29 ssn: u16,
30 syscall_addr: usize,
31}
32
33impl IndirectSyscall {
34 pub const unsafe fn new_unchecked(ssn: u16, syscall_addr: usize) -> Self {
39 Self { ssn, syscall_addr }
40 }
41
42 pub fn new(ssn: u16, syscall_addr: usize) -> Result<Self> {
44 if !Self::validate_syscall_address(syscall_addr) {
46 return Err(WraithError::SyscallEnumerationFailed {
47 reason: format!(
48 "address {:#x} does not contain valid syscall instruction",
49 syscall_addr
50 ),
51 });
52 }
53
54 Ok(Self { ssn, syscall_addr })
55 }
56
57 fn validate_syscall_address(addr: usize) -> bool {
59 if addr == 0 {
60 return false;
61 }
62
63 let bytes: [u8; 2] = unsafe { *(addr as *const [u8; 2]) };
67 bytes == SYSCALL_BYTES
68 }
69
70 pub fn from_entry(entry: &SyscallEntry) -> Result<Self> {
72 let syscall_addr = entry.syscall_address.ok_or_else(|| {
73 WraithError::SyscallEnumerationFailed {
74 reason: format!("no syscall address for {}", entry.name),
75 }
76 })?;
77
78 Self::new(entry.ssn, syscall_addr)
79 }
80
81 pub fn from_table(table: &SyscallTable, name: &str) -> Result<Self> {
83 let entry = table.get(name).ok_or_else(|| WraithError::SyscallNotFound {
84 name: name.to_string(),
85 })?;
86 Self::from_entry(entry)
87 }
88
89 pub const fn ssn(&self) -> u16 {
91 self.ssn
92 }
93
94 pub const fn syscall_address(&self) -> usize {
96 self.syscall_addr
97 }
98}
99
100#[cfg(target_arch = "x86_64")]
101impl IndirectSyscall {
102 #[inline(never)]
107 pub unsafe fn call0(&self) -> i32 {
108 let status: i32;
109 unsafe {
112 asm!(
113 "sub rsp, 0x28", "mov r10, rcx",
115 "mov eax, {ssn:e}",
116 "call {addr}",
117 "add rsp, 0x28",
118 ssn = in(reg) self.ssn as u32,
119 addr = in(reg) self.syscall_addr,
120 out("eax") status,
121 out("rcx") _,
122 out("r10") _,
123 out("r11") _,
124 );
125 }
126 status
127 }
128
129 #[inline(never)]
134 pub unsafe fn call1(&self, arg1: usize) -> i32 {
135 let status: i32;
136 unsafe {
138 asm!(
139 "sub rsp, 0x28",
140 "mov r10, rcx",
141 "mov eax, {ssn:e}",
142 "call {addr}",
143 "add rsp, 0x28",
144 ssn = in(reg) self.ssn as u32,
145 addr = in(reg) self.syscall_addr,
146 in("rcx") arg1,
147 out("eax") status,
148 out("r10") _,
149 out("r11") _,
150 );
151 }
152 status
153 }
154
155 #[inline(never)]
160 pub unsafe fn call2(&self, arg1: usize, arg2: usize) -> i32 {
161 let status: i32;
162 unsafe {
164 asm!(
165 "sub rsp, 0x28",
166 "mov r10, rcx",
167 "mov eax, {ssn:e}",
168 "call {addr}",
169 "add rsp, 0x28",
170 ssn = in(reg) self.ssn as u32,
171 addr = in(reg) self.syscall_addr,
172 in("rcx") arg1,
173 in("rdx") arg2,
174 out("eax") status,
175 out("r10") _,
176 out("r11") _,
177 );
178 }
179 status
180 }
181
182 #[inline(never)]
187 pub unsafe fn call3(&self, arg1: usize, arg2: usize, arg3: usize) -> i32 {
188 let status: i32;
189 unsafe {
191 asm!(
192 "sub rsp, 0x28",
193 "mov r10, rcx",
194 "mov eax, {ssn:e}",
195 "call {addr}",
196 "add rsp, 0x28",
197 ssn = in(reg) self.ssn as u32,
198 addr = in(reg) self.syscall_addr,
199 in("rcx") arg1,
200 in("rdx") arg2,
201 in("r8") arg3,
202 out("eax") status,
203 out("r10") _,
204 out("r11") _,
205 );
206 }
207 status
208 }
209
210 #[inline(never)]
215 pub unsafe fn call4(&self, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> i32 {
216 let status: i32;
217 unsafe {
219 asm!(
220 "sub rsp, 0x28",
221 "mov r10, rcx",
222 "mov eax, {ssn:e}",
223 "call {addr}",
224 "add rsp, 0x28",
225 ssn = in(reg) self.ssn as u32,
226 addr = in(reg) self.syscall_addr,
227 in("rcx") arg1,
228 in("rdx") arg2,
229 in("r8") arg3,
230 in("r9") arg4,
231 out("eax") status,
232 out("r10") _,
233 out("r11") _,
234 );
235 }
236 status
237 }
238
239 #[inline(never)]
244 pub unsafe fn call5(
245 &self,
246 arg1: usize,
247 arg2: usize,
248 arg3: usize,
249 arg4: usize,
250 arg5: usize,
251 ) -> i32 {
252 let status: i32;
253 unsafe {
257 asm!(
258 "sub rsp, 0x28",
259 "mov [rsp+0x20], {arg5}",
260 "mov r10, rcx",
261 "mov eax, {ssn:e}",
262 "call {addr}",
263 "add rsp, 0x28",
264 ssn = in(reg) self.ssn as u32,
265 addr = in(reg) self.syscall_addr,
266 arg5 = in(reg) arg5,
267 in("rcx") arg1,
268 in("rdx") arg2,
269 in("r8") arg3,
270 in("r9") arg4,
271 out("eax") status,
272 out("r10") _,
273 out("r11") _,
274 );
275 }
276 status
277 }
278
279 #[inline(never)]
284 pub unsafe fn call6(
285 &self,
286 arg1: usize,
287 arg2: usize,
288 arg3: usize,
289 arg4: usize,
290 arg5: usize,
291 arg6: usize,
292 ) -> i32 {
293 let status: i32;
294 unsafe {
298 asm!(
299 "sub rsp, 0x30",
300 "mov [rsp+0x20], {arg5}",
301 "mov [rsp+0x28], {arg6}",
302 "mov r10, rcx",
303 "mov eax, {ssn:e}",
304 "call {addr}",
305 "add rsp, 0x30",
306 ssn = in(reg) self.ssn as u32,
307 addr = in(reg) self.syscall_addr,
308 arg5 = in(reg) arg5,
309 arg6 = in(reg) arg6,
310 in("rcx") arg1,
311 in("rdx") arg2,
312 in("r8") arg3,
313 in("r9") arg4,
314 out("eax") status,
315 out("r10") _,
316 out("r11") _,
317 );
318 }
319 status
320 }
321
322 #[inline(never)]
327 pub unsafe fn call_many(&self, args: &[usize]) -> i32 {
328 match args.len() {
329 0 => unsafe { self.call0() },
330 1 => unsafe { self.call1(args[0]) },
331 2 => unsafe { self.call2(args[0], args[1]) },
332 3 => unsafe { self.call3(args[0], args[1], args[2]) },
333 4 => unsafe { self.call4(args[0], args[1], args[2], args[3]) },
334 5 => unsafe { self.call5(args[0], args[1], args[2], args[3], args[4]) },
335 6 => unsafe { self.call6(args[0], args[1], args[2], args[3], args[4], args[5]) },
336 _ => unsafe { self.call6(args[0], args[1], args[2], args[3], args[4], args[5]) },
337 }
338 }
339}
340
341#[cfg(target_arch = "x86")]
342impl IndirectSyscall {
343 #[inline(never)]
348 pub unsafe fn call(&self, args: &[usize]) -> i32 {
349 let status: i32;
350 let args_ptr = args.as_ptr();
351
352 unsafe {
354 asm!(
355 "mov eax, {ssn:e}",
356 "mov edx, {args}",
357 "call {addr}",
358 ssn = in(reg) self.ssn as u32,
359 args = in(reg) args_ptr,
360 addr = in(reg) self.syscall_addr,
361 out("eax") status,
362 options(nostack)
363 );
364 }
365 status
366 }
367
368 pub unsafe fn call0(&self) -> i32 {
369 unsafe { self.call(&[]) }
370 }
371
372 pub unsafe fn call1(&self, arg1: usize) -> i32 {
373 unsafe { self.call(&[arg1]) }
374 }
375
376 pub unsafe fn call2(&self, arg1: usize, arg2: usize) -> i32 {
377 unsafe { self.call(&[arg1, arg2]) }
378 }
379
380 pub unsafe fn call3(&self, arg1: usize, arg2: usize, arg3: usize) -> i32 {
381 unsafe { self.call(&[arg1, arg2, arg3]) }
382 }
383
384 pub unsafe fn call4(&self, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> i32 {
385 unsafe { self.call(&[arg1, arg2, arg3, arg4]) }
386 }
387
388 pub unsafe fn call5(
389 &self,
390 arg1: usize,
391 arg2: usize,
392 arg3: usize,
393 arg4: usize,
394 arg5: usize,
395 ) -> i32 {
396 unsafe { self.call(&[arg1, arg2, arg3, arg4, arg5]) }
397 }
398
399 pub unsafe fn call6(
400 &self,
401 arg1: usize,
402 arg2: usize,
403 arg3: usize,
404 arg4: usize,
405 arg5: usize,
406 arg6: usize,
407 ) -> i32 {
408 unsafe { self.call(&[arg1, arg2, arg3, arg4, arg5, arg6]) }
409 }
410
411 pub unsafe fn call_many(&self, args: &[usize]) -> i32 {
412 unsafe { self.call(args) }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn test_indirect_syscall_ntclose() {
422 let table = SyscallTable::enumerate().expect("should enumerate");
423
424 if let Ok(syscall) = IndirectSyscall::from_table(&table, "NtClose") {
425 let status = unsafe { syscall.call1(0xDEADBEEF) };
427 assert_eq!(status, 0xC0000008_u32 as i32);
428 }
429 }
430
431 #[test]
432 fn test_syscall_address_in_ntdll() {
433 let table = SyscallTable::enumerate().expect("should enumerate");
434
435 if let Some(entry) = table.get("NtClose") {
436 if let Some(addr) = entry.syscall_address {
437 assert!(addr > entry.address, "syscall should be after function start");
439 assert!(
440 addr < entry.address + 32,
441 "syscall should be within stub"
442 );
443 }
444 }
445 }
446}