use crate::error::{Result, WraithError};
const MEM_COMMIT: u32 = 0x1000;
const MEM_RESERVE: u32 = 0x2000;
const MEM_RELEASE: u32 = 0x8000;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
pub struct ExecutableMemory {
base: *mut u8,
size: usize,
used: usize,
}
impl ExecutableMemory {
pub fn allocate_near(target: usize, size: usize) -> Result<Self> {
let size = (size + 0xFFF) & !0xFFF;
#[cfg(target_arch = "x86_64")]
{
if let Some(mem) = try_allocate_near_x64(target, size) {
return Ok(mem);
}
}
Self::allocate(size)
}
pub fn allocate(size: usize) -> Result<Self> {
let size = (size + 0xFFF) & !0xFFF;
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,
});
}
unsafe {
core::ptr::write_bytes(base, 0xCC, size); }
Ok(Self {
base: base as *mut u8,
size,
used: 0,
})
}
pub fn base(&self) -> usize {
self.base as usize
}
pub fn size(&self) -> usize {
self.size
}
pub fn used(&self) -> usize {
self.used
}
pub fn available(&self) -> usize {
self.size - self.used
}
pub fn write(&mut self, code: &[u8]) -> Result<usize> {
if code.len() > self.available() {
return Err(WraithError::WriteFailed {
address: self.base as u64,
size: code.len(),
});
}
let write_addr = self.base as usize + self.used;
unsafe {
core::ptr::copy_nonoverlapping(code.as_ptr(), write_addr as *mut u8, code.len());
}
self.used += code.len();
Ok(write_addr)
}
pub fn ptr_at(&self, offset: usize) -> *mut u8 {
unsafe { self.base.add(offset) }
}
pub fn read_at(&self, offset: usize, len: usize) -> Result<&[u8]> {
if offset + len > self.size {
return Err(WraithError::ReadFailed {
address: (self.base as usize + offset) as u64,
size: len,
});
}
Ok(unsafe { core::slice::from_raw_parts(self.base.add(offset), len) })
}
pub fn contains(&self, addr: usize) -> bool {
addr >= self.base as usize && addr < (self.base as usize + self.size)
}
pub fn is_near(&self, target: usize) -> bool {
let base = self.base as usize;
let distance = if base > target {
base - target
} else {
target - base
};
distance <= i32::MAX as usize
}
pub fn flush_icache(&self) -> Result<()> {
let result = unsafe {
FlushInstructionCache(
GetCurrentProcess(),
self.base as *const _,
self.size,
)
};
if result == 0 {
Err(WraithError::from_last_error("FlushInstructionCache"))
} else {
Ok(())
}
}
pub fn leak(self) -> *mut u8 {
let ptr = self.base;
core::mem::forget(self);
ptr
}
}
impl Drop for ExecutableMemory {
fn drop(&mut self) {
unsafe {
VirtualFree(self.base as *mut _, 0, MEM_RELEASE);
}
}
}
unsafe impl Send for ExecutableMemory {}
unsafe impl Sync for ExecutableMemory {}
#[cfg(target_arch = "x86_64")]
fn try_allocate_near_x64(target: usize, size: usize) -> Option<ExecutableMemory> {
const GRANULARITY: usize = 0x10000;
const SEARCH_RANGE: i64 = 0x7FFF0000;
let target_i64 = target as i64;
let mut addr = (target_i64 - SEARCH_RANGE).max(0x10000) as usize;
addr = addr & !(GRANULARITY - 1);
while (addr as i64) < target_i64 {
let ptr = unsafe {
VirtualAlloc(
addr as *mut _,
size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
)
};
if !ptr.is_null() {
let distance = (target as i64 - ptr as i64).abs();
if distance <= i32::MAX as i64 {
unsafe {
core::ptr::write_bytes(ptr, 0xCC, size);
}
return Some(ExecutableMemory {
base: ptr as *mut u8,
size,
used: 0,
});
}
unsafe {
VirtualFree(ptr, 0, MEM_RELEASE);
}
}
addr += GRANULARITY;
}
addr = ((target_i64 + 0x10000) & !(GRANULARITY as i64 - 1)) as usize;
let max_addr = (target_i64 + SEARCH_RANGE) as usize;
while addr < max_addr {
let ptr = unsafe {
VirtualAlloc(
addr as *mut _,
size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE,
)
};
if !ptr.is_null() {
let distance = (target as i64 - ptr as i64).abs();
if distance <= i32::MAX as i64 {
unsafe {
core::ptr::write_bytes(ptr, 0xCC, size);
}
return Some(ExecutableMemory {
base: ptr as *mut u8,
size,
used: 0,
});
}
unsafe {
VirtualFree(ptr, 0, MEM_RELEASE);
}
}
addr += GRANULARITY;
}
None
}
#[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;
fn FlushInstructionCache(
hProcess: *mut core::ffi::c_void,
lpBaseAddress: *const core::ffi::c_void,
dwSize: usize,
) -> i32;
fn GetCurrentProcess() -> *mut core::ffi::c_void;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocate() {
let mem = ExecutableMemory::allocate(0x1000).unwrap();
assert!(mem.base() != 0);
assert!(mem.size() >= 0x1000);
assert_eq!(mem.used(), 0);
}
#[test]
fn test_write() {
let mut mem = ExecutableMemory::allocate(0x1000).unwrap();
let code = [0x90, 0x90, 0x90, 0xC3];
let addr = mem.write(&code).unwrap();
assert_eq!(addr, mem.base());
assert_eq!(mem.used(), 4);
let read = mem.read_at(0, 4).unwrap();
assert_eq!(read, &code);
}
#[test]
fn test_contains() {
let mem = ExecutableMemory::allocate(0x1000).unwrap();
assert!(mem.contains(mem.base()));
assert!(mem.contains(mem.base() + 0x500));
assert!(!mem.contains(mem.base() + 0x2000));
}
}