#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
use crate::error::{Result, WraithError};
use core::sync::atomic::{AtomicUsize, Ordering};
const TRAMPOLINE_SIZE: usize = 256;
const SLOTS_PER_BLOCK: usize = 16;
const PAGE_SIZE: usize = 4096;
pub struct TrampolineAllocator {
base: usize,
size: usize,
next_slot: AtomicUsize,
total_slots: usize,
}
impl TrampolineAllocator {
pub fn new() -> Result<Self> {
let needed = TRAMPOLINE_SIZE * SLOTS_PER_BLOCK;
let size = (needed + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
let base = unsafe {
VirtualAlloc(
core::ptr::null_mut(),
size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
)
};
if base.is_null() {
return Err(WraithError::AllocationFailed {
size,
protection: PAGE_EXECUTE_READWRITE,
});
}
Ok(Self {
base: base as usize,
size,
next_slot: AtomicUsize::new(0),
total_slots: SLOTS_PER_BLOCK,
})
}
pub fn allocate(&self) -> Result<SpoofTrampoline> {
let slot = self.next_slot.fetch_add(1, Ordering::SeqCst);
if slot >= self.total_slots {
return Err(WraithError::TrampolineAllocationFailed {
near: self.base as u64,
size: TRAMPOLINE_SIZE,
});
}
let address = self.base + slot * TRAMPOLINE_SIZE;
Ok(SpoofTrampoline {
address,
size: TRAMPOLINE_SIZE,
})
}
pub fn base(&self) -> usize {
self.base
}
pub fn remaining_slots(&self) -> usize {
self.total_slots.saturating_sub(self.next_slot.load(Ordering::SeqCst))
}
}
impl Drop for TrampolineAllocator {
fn drop(&mut self) {
if self.base != 0 {
unsafe {
VirtualFree(self.base as *mut _, 0, MEM_RELEASE);
}
}
}
}
unsafe impl Send for TrampolineAllocator {}
unsafe impl Sync for TrampolineAllocator {}
#[derive(Debug)]
pub struct SpoofTrampoline {
address: usize,
size: usize,
}
impl SpoofTrampoline {
pub fn address(&self) -> usize {
self.address
}
pub fn size(&self) -> usize {
self.size
}
pub unsafe fn write_code(&self, code: &[u8]) -> Result<()> {
if code.len() > self.size {
return Err(WraithError::WriteFailed {
address: u64::try_from(self.address).unwrap_or(u64::MAX),
size: code.len(),
});
}
unsafe {
core::ptr::copy_nonoverlapping(
code.as_ptr(),
self.address as *mut u8,
code.len(),
);
}
Ok(())
}
#[cfg(target_arch = "x86_64")]
pub fn write_spoofed_syscall(
&self,
ssn: u16,
syscall_addr: usize,
gadget_addr: usize,
) -> Result<()> {
let storage_rbx = self.address + 0xC0;
let storage_r12 = self.address + 0xC8;
let storage_ret = self.address + 0xD0;
let mut code = Vec::with_capacity(192);
code.push(0x49); code.push(0xBB); code.extend_from_slice(&(storage_rbx as u64).to_le_bytes());
code.extend_from_slice(&[0x49, 0x89, 0x1B]);
code.push(0x49); code.push(0xBB);
code.extend_from_slice(&(storage_r12 as u64).to_le_bytes());
code.extend_from_slice(&[0x4D, 0x89, 0x23]);
code.extend_from_slice(&[0x4C, 0x8B, 0x24, 0x24]); code.push(0x49); code.push(0xBB);
code.extend_from_slice(&(storage_ret as u64).to_le_bytes());
code.extend_from_slice(&[0x4D, 0x89, 0x23]);
code.extend_from_slice(&[0x48, 0x8D, 0x1D]); let cleanup_offset_pos = code.len();
code.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
code.push(0x49); code.push(0xBB); code.extend_from_slice(&(gadget_addr as u64).to_le_bytes());
code.extend_from_slice(&[0x4C, 0x89, 0x1C, 0x24]);
code.extend_from_slice(&[0x49, 0x89, 0xCA]); code.push(0xB8); code.extend_from_slice(&(ssn as u32).to_le_bytes());
code.push(0x49); code.push(0xBB); code.extend_from_slice(&(syscall_addr as u64).to_le_bytes());
code.extend_from_slice(&[0x41, 0xFF, 0xE3]);
let cleanup_pos = code.len();
code.push(0x49); code.push(0xBB); code.extend_from_slice(&(storage_rbx as u64).to_le_bytes());
code.extend_from_slice(&[0x49, 0x8B, 0x1B]);
code.push(0x49); code.push(0xBB); code.extend_from_slice(&(storage_r12 as u64).to_le_bytes());
code.extend_from_slice(&[0x4D, 0x8B, 0x23]);
code.push(0x49); code.push(0xBB); code.extend_from_slice(&(storage_ret as u64).to_le_bytes());
code.extend_from_slice(&[0x4D, 0x8B, 0x1B]); code.extend_from_slice(&[0x41, 0xFF, 0xE3]);
let rel_offset = (cleanup_pos as i32) - ((cleanup_offset_pos + 4) as i32);
code[cleanup_offset_pos..cleanup_offset_pos + 4]
.copy_from_slice(&rel_offset.to_le_bytes());
unsafe { self.write_code(&code) }
}
#[cfg(target_arch = "x86_64")]
pub fn write_simple_spoofed_syscall(
&self,
ssn: u16,
syscall_addr: usize,
spoof_addr: usize,
) -> Result<()> {
if spoof_addr == 0 {
return self.write_basic_indirect_syscall(ssn, syscall_addr);
}
self.write_basic_indirect_syscall(ssn, syscall_addr)
}
#[cfg(target_arch = "x86_64")]
fn write_basic_indirect_syscall(&self, ssn: u16, syscall_addr: usize) -> Result<()> {
let mut code = Vec::with_capacity(64);
code.extend_from_slice(&[0x48, 0x83, 0xEC, 0x28]);
code.extend_from_slice(&[0x49, 0x89, 0xCA]);
code.push(0xB8);
code.extend_from_slice(&(ssn as u32).to_le_bytes());
code.push(0x49); code.push(0xBB);
code.extend_from_slice(&(syscall_addr as u64).to_le_bytes());
code.extend_from_slice(&[0x41, 0xFF, 0xD3]);
code.extend_from_slice(&[0x48, 0x83, 0xC4, 0x28]);
code.push(0xC3);
unsafe { self.write_code(&code) }
}
pub unsafe fn as_fn_ptr<F>(&self) -> F
where
F: Copy,
{
debug_assert!(
core::mem::size_of::<F>() == core::mem::size_of::<usize>(),
"function pointer must be pointer-sized"
);
unsafe { core::mem::transmute_copy(&self.address) }
}
}
const MEM_COMMIT: u32 = 0x1000;
const MEM_RESERVE: u32 = 0x2000;
const MEM_RELEASE: u32 = 0x8000;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
#[link(name = "kernel32")]
extern "system" {
fn VirtualAlloc(
lpAddress: *mut core::ffi::c_void,
dwSize: usize,
flAllocationType: u32,
flProtect: u32,
) -> *mut core::ffi::c_void;
fn VirtualFree(
lpAddress: *mut core::ffi::c_void,
dwSize: usize,
dwFreeType: u32,
) -> i32;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocator_creation() {
let alloc = TrampolineAllocator::new().expect("should allocate");
assert!(alloc.base() > 0);
assert!(alloc.remaining_slots() > 0);
}
#[test]
fn test_trampoline_allocation() {
let alloc = TrampolineAllocator::new().expect("should allocate");
let tramp = alloc.allocate().expect("should get slot");
assert!(tramp.address() > 0);
assert!(tramp.size() >= 64);
}
#[test]
fn test_code_write() {
let alloc = TrampolineAllocator::new().expect("should allocate");
let tramp = alloc.allocate().expect("should get slot");
let code = [0x90u8; 16]; unsafe {
tramp.write_code(&code).expect("should write");
}
let written = unsafe { std::slice::from_raw_parts(tramp.address() as *const u8, 16) };
assert_eq!(written, &code);
}
}