use super::table::{SyscallEntry, SyscallTable};
use crate::error::{Result, WraithError};
use core::arch::asm;
pub struct DirectSyscall {
ssn: u16,
}
impl DirectSyscall {
pub const fn new(ssn: u16) -> Self {
Self { ssn }
}
pub fn from_entry(entry: &SyscallEntry) -> Self {
Self { ssn: entry.ssn }
}
pub fn from_table(table: &SyscallTable, name: &str) -> Result<Self> {
let entry = table.get(name).ok_or_else(|| WraithError::SyscallNotFound {
name: name.to_string(),
})?;
Ok(Self::from_entry(entry))
}
pub const fn ssn(&self) -> u16 {
self.ssn
}
}
#[cfg(target_arch = "x86_64")]
impl DirectSyscall {
#[inline(never)]
pub unsafe fn call0(&self) -> i32 {
let status: i32;
unsafe {
asm!(
"mov r10, rcx",
"mov eax, {ssn:e}",
"syscall",
ssn = in(reg) self.ssn as u32,
out("eax") status,
out("rcx") _,
out("r10") _,
out("r11") _,
options(nostack)
);
}
status
}
#[inline(never)]
pub unsafe fn call1(&self, arg1: usize) -> i32 {
let status: i32;
unsafe {
asm!(
"mov r10, rcx",
"mov eax, {ssn:e}",
"syscall",
ssn = in(reg) self.ssn as u32,
in("rcx") arg1,
out("eax") status,
out("r10") _,
out("r11") _,
options(nostack)
);
}
status
}
#[inline(never)]
pub unsafe fn call2(&self, arg1: usize, arg2: usize) -> i32 {
let status: i32;
unsafe {
asm!(
"mov r10, rcx",
"mov eax, {ssn:e}",
"syscall",
ssn = in(reg) self.ssn as u32,
in("rcx") arg1,
in("rdx") arg2,
out("eax") status,
out("r10") _,
out("r11") _,
options(nostack)
);
}
status
}
#[inline(never)]
pub unsafe fn call3(&self, arg1: usize, arg2: usize, arg3: usize) -> i32 {
let status: i32;
unsafe {
asm!(
"mov r10, rcx",
"mov eax, {ssn:e}",
"syscall",
ssn = in(reg) self.ssn as u32,
in("rcx") arg1,
in("rdx") arg2,
in("r8") arg3,
out("eax") status,
out("r10") _,
out("r11") _,
options(nostack)
);
}
status
}
#[inline(never)]
pub unsafe fn call4(&self, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> i32 {
let status: i32;
unsafe {
asm!(
"mov r10, rcx",
"mov eax, {ssn:e}",
"syscall",
ssn = in(reg) self.ssn as u32,
in("rcx") arg1,
in("rdx") arg2,
in("r8") arg3,
in("r9") arg4,
out("eax") status,
out("r10") _,
out("r11") _,
options(nostack)
);
}
status
}
#[inline(never)]
pub unsafe fn call5(
&self,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
) -> i32 {
let status: i32;
unsafe {
asm!(
"sub rsp, 0x30", "mov [rsp+0x28], {arg5}",
"mov r10, rcx",
"mov eax, {ssn:e}",
"syscall",
"add rsp, 0x30",
ssn = in(reg) self.ssn as u32,
arg5 = in(reg) arg5,
in("rcx") arg1,
in("rdx") arg2,
in("r8") arg3,
in("r9") arg4,
out("eax") status,
out("r10") _,
out("r11") _,
);
}
status
}
#[inline(never)]
pub unsafe fn call6(
&self,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
arg6: usize,
) -> i32 {
let status: i32;
unsafe {
asm!(
"sub rsp, 0x38", "mov [rsp+0x28], {arg5}",
"mov [rsp+0x30], {arg6}",
"mov r10, rcx",
"mov eax, {ssn:e}",
"syscall",
"add rsp, 0x38",
ssn = in(reg) self.ssn as u32,
arg5 = in(reg) arg5,
arg6 = in(reg) arg6,
in("rcx") arg1,
in("rdx") arg2,
in("r8") arg3,
in("r9") arg4,
out("eax") status,
out("r10") _,
out("r11") _,
);
}
status
}
#[inline(never)]
pub unsafe fn call_many(&self, args: &[usize]) -> i32 {
match args.len() {
0 => unsafe { self.call0() },
1 => unsafe { self.call1(args[0]) },
2 => unsafe { self.call2(args[0], args[1]) },
3 => unsafe { self.call3(args[0], args[1], args[2]) },
4 => unsafe { self.call4(args[0], args[1], args[2], args[3]) },
5 => unsafe { self.call5(args[0], args[1], args[2], args[3], args[4]) },
6 => unsafe { self.call6(args[0], args[1], args[2], args[3], args[4], args[5]) },
_ => {
unsafe { self.call6(args[0], args[1], args[2], args[3], args[4], args[5]) }
}
}
}
}
#[cfg(target_arch = "x86")]
impl DirectSyscall {
#[inline(never)]
pub unsafe fn call(&self, args: &[usize]) -> i32 {
let status: i32;
let args_ptr = args.as_ptr();
unsafe {
asm!(
"mov eax, {ssn:e}",
"mov edx, {args}",
"int 0x2e",
ssn = in(reg) self.ssn as u32,
args = in(reg) args_ptr,
out("eax") status,
options(nostack)
);
}
status
}
pub unsafe fn call0(&self) -> i32 {
unsafe { self.call(&[]) }
}
pub unsafe fn call1(&self, arg1: usize) -> i32 {
unsafe { self.call(&[arg1]) }
}
pub unsafe fn call2(&self, arg1: usize, arg2: usize) -> i32 {
unsafe { self.call(&[arg1, arg2]) }
}
pub unsafe fn call3(&self, arg1: usize, arg2: usize, arg3: usize) -> i32 {
unsafe { self.call(&[arg1, arg2, arg3]) }
}
pub unsafe fn call4(&self, arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> i32 {
unsafe { self.call(&[arg1, arg2, arg3, arg4]) }
}
pub unsafe fn call5(
&self,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
) -> i32 {
unsafe { self.call(&[arg1, arg2, arg3, arg4, arg5]) }
}
pub unsafe fn call6(
&self,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
arg6: usize,
) -> i32 {
unsafe { self.call(&[arg1, arg2, arg3, arg4, arg5, arg6]) }
}
pub unsafe fn call_many(&self, args: &[usize]) -> i32 {
unsafe { self.call(args) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_direct_syscall_ntclose() {
let table = SyscallTable::enumerate().expect("should enumerate");
let syscall = DirectSyscall::from_table(&table, "NtClose").expect("should find NtClose");
let status = unsafe { syscall.call1(0xDEADBEEF) };
assert_eq!(
status, 0xC0000008_u32 as i32,
"should return STATUS_INVALID_HANDLE"
);
}
}