use crate::memory::{code_cave, info::symbol, rw};
#[cfg(debug_assertions)]
use crate::utils::logger;
use std::arch::asm;
use std::collections::HashMap;
use std::ffi::c_void;
use thiserror::Error;
const CACHE_LINE_SIZE: usize = 64;
#[derive(Error, Debug)]
pub enum LoaderError {
#[error("Code cave allocation failed: {0}")]
AllocationFailed(#[from] code_cave::CodeCaveError),
#[error("Symbol not found: {0}")]
SymbolNotFound(String),
#[error("Write failed: {0}")]
WriteFailed(#[from] rw::RwError),
#[error("Invalid shellcode size: {0}")]
InvalidSize(usize),
#[error("Relocation failed at offset {0}")]
RelocationFailed(usize),
}
pub struct LoadedShellcode {
pub address: usize,
pub size: usize,
auto_free: bool,
}
impl LoadedShellcode {
pub unsafe fn execute(&self) -> usize {
unsafe {
let func: extern "C" fn() -> usize = std::mem::transmute(self.address);
func()
}
}
pub unsafe fn execute_as<F, R>(&self, callback: impl FnOnce(F) -> R) -> R
where
F: Copy,
{
unsafe {
let func_ptr = self.address as *const ();
let func: F = *(&func_ptr as *const *const () as *const F);
callback(func)
}
}
pub unsafe fn as_function<F>(&self) -> F
where
F: Copy,
{
unsafe {
let func_ptr = self.address as *const ();
*(&func_ptr as *const *const () as *const F)
}
}
pub fn free(self) {
if self.address != 0 {
let _ = code_cave::free_cave(self.address);
}
}
}
impl Drop for LoadedShellcode {
fn drop(&mut self) {
if self.auto_free && self.address != 0 {
let _ = code_cave::free_cave(self.address);
}
}
}
#[derive(Debug, Clone)]
pub struct SymbolRelocation {
pub offset: usize,
pub symbol_name: String,
}
pub struct ShellcodeBuilder {
code: Vec<u8>,
relocations: Vec<SymbolRelocation>,
auto_free: bool,
target_address: Option<usize>,
}
impl ShellcodeBuilder {
pub fn new(shellcode: &[u8]) -> Self {
Self {
code: shellcode.to_vec(),
relocations: Vec::new(),
auto_free: true,
target_address: None,
}
}
pub fn from_instructions(instructions: &[u32]) -> Self {
let bytes: Vec<u8> = instructions
.iter()
.flat_map(|&instr| instr.to_le_bytes())
.collect();
Self::new(&bytes)
}
pub fn with_symbol(mut self, offset: usize, symbol_name: &str) -> Self {
self.relocations.push(SymbolRelocation {
offset,
symbol_name: symbol_name.to_string(),
});
self
}
pub fn no_auto_free(mut self) -> Self {
self.auto_free = false;
self
}
pub fn near_address(mut self, target: usize) -> Self {
self.target_address = Some(target);
self
}
pub fn load(self) -> Result<LoadedShellcode, LoaderError> {
if self.code.is_empty() {
return Err(LoaderError::InvalidSize(0));
}
let mut cave = if let Some(target) = self.target_address {
code_cave::allocate_cave_near(target, self.code.len())?
} else {
code_cave::allocate_cave(self.code.len())?
};
let alignment = cave.address % 4;
if alignment != 0 {
let adjust = 4 - alignment;
cave.address += adjust;
cave.size = cave.size.saturating_sub(adjust);
if cave.size < self.code.len() {
return Err(LoaderError::InvalidSize(cave.size));
}
}
let mut code = self.code.clone();
let mut symbol_cache: HashMap<String, usize> = HashMap::new();
for reloc in &self.relocations {
let symbol_addr = if let Some(&cached) = symbol_cache.get(&reloc.symbol_name) {
cached
} else {
let addr = symbol::resolve_symbol(&reloc.symbol_name)
.map_err(|_| LoaderError::SymbolNotFound(reloc.symbol_name.clone()))?;
symbol_cache.insert(reloc.symbol_name.clone(), addr);
addr
};
if reloc.offset + 8 > code.len() {
return Err(LoaderError::RelocationFailed(reloc.offset));
}
let addr_bytes = (symbol_addr as u64).to_le_bytes();
code[reloc.offset..reloc.offset + 8].copy_from_slice(&addr_bytes);
}
unsafe {
rw::write_bytes(cave.address, &code)?;
}
unsafe {
for (i, &expected) in code.iter().enumerate() {
match rw::read::<u8>(cave.address + i) {
Ok(actual) => {
if actual != expected {
return Err(LoaderError::WriteFailed(rw::RwError::NullPointer));
}
}
Err(e) => {
return Err(LoaderError::WriteFailed(e));
}
}
}
}
if !crate::memory::protection::is_executable(cave.address) {
return Err(LoaderError::InvalidSize(0));
}
unsafe {
invalidate_icache(cave.address as *mut c_void, code.len());
}
#[cfg(debug_assertions)]
logger::info(&format!(
"Shellcode loaded at {:#x} ({} bytes)",
cave.address,
code.len()
));
Ok(LoadedShellcode {
address: cave.address,
size: code.len(),
auto_free: self.auto_free,
})
}
}
pub trait ShellcodeSource {
fn into_bytes(self) -> Vec<u8>;
}
impl ShellcodeSource for &[u8] {
fn into_bytes(self) -> Vec<u8> {
self.to_vec()
}
}
impl ShellcodeSource for &[u32] {
fn into_bytes(self) -> Vec<u8> {
self.iter().flat_map(|&instr| instr.to_le_bytes()).collect()
}
}
impl<const N: usize> ShellcodeSource for &[u32; N] {
fn into_bytes(self) -> Vec<u8> {
self.iter().flat_map(|&instr| instr.to_le_bytes()).collect()
}
}
impl<const N: usize> ShellcodeSource for &[u8; N] {
fn into_bytes(self) -> Vec<u8> {
self.to_vec()
}
}
pub fn load(source: impl ShellcodeSource) -> Result<LoadedShellcode, LoaderError> {
ShellcodeBuilder::new(&source.into_bytes()).load()
}
#[inline]
pub unsafe fn invalidate_icache(start: *mut c_void, len: usize) {
unsafe {
let start_addr = start as usize;
let end_addr = start_addr + len;
let mut addr = start_addr & !(CACHE_LINE_SIZE - 1);
while addr < end_addr {
asm!("dc cvau, {x}", x = in(reg) addr, options(nostack, preserves_flags));
addr += CACHE_LINE_SIZE;
}
asm!("dsb ish", options(nostack, preserves_flags));
addr = start_addr & !(CACHE_LINE_SIZE - 1);
while addr < end_addr {
asm!("ic ivau, {x}", x = in(reg) addr, options(nostack, preserves_flags));
addr += CACHE_LINE_SIZE;
}
asm!("dsb ish", options(nostack, preserves_flags));
asm!("isb", options(nostack, preserves_flags));
}
}