#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{format, string::String, vec::Vec};
#[cfg(feature = "std")]
use std::{format, string::String, vec::Vec};
use crate::error::{Result, WraithError};
use super::allocator::ExecutableMemory;
use crate::manipulation::inline_hook::arch::Architecture;
pub struct TrampolineBuilder<A: Architecture> {
target: usize,
prologue_size: usize,
prologue_bytes: Vec<u8>,
memory: Option<ExecutableMemory>,
_arch: core::marker::PhantomData<A>,
}
impl<A: Architecture> TrampolineBuilder<A> {
pub fn new(target: usize) -> Self {
Self {
target,
prologue_size: 0,
prologue_bytes: Vec::new(),
memory: None,
_arch: core::marker::PhantomData,
}
}
pub fn analyze(&mut self, min_size: usize) -> Result<&mut Self> {
let max_read = 64; let target_bytes = unsafe {
core::slice::from_raw_parts(self.target as *const u8, max_read)
};
let boundary = A::find_instruction_boundary(target_bytes, min_size)
.ok_or_else(|| WraithError::HookDetectionFailed {
function: format!("{:#x}", self.target),
reason: "failed to find instruction boundary".into(),
})?;
self.prologue_size = boundary;
self.prologue_bytes = target_bytes[..boundary].to_vec();
Ok(self)
}
pub fn with_prologue_size(&mut self, size: usize) -> Result<&mut Self> {
let target_bytes = unsafe {
core::slice::from_raw_parts(self.target as *const u8, size)
};
self.prologue_size = size;
self.prologue_bytes = target_bytes.to_vec();
Ok(self)
}
pub fn allocate(&mut self) -> Result<&mut Self> {
let trampoline_size = self.prologue_size + A::JMP_ABS_SIZE + 16;
let memory = ExecutableMemory::allocate_near(self.target, trampoline_size)?;
self.memory = Some(memory);
Ok(self)
}
pub fn build(&mut self) -> Result<usize> {
let memory = self.memory.as_mut().ok_or_else(|| WraithError::NullPointer {
context: "trampoline memory not allocated",
})?;
let trampoline_base = memory.base();
let mut trampoline_code = Vec::with_capacity(self.prologue_size + A::JMP_ABS_SIZE);
let mut src_offset = 0;
let mut dst_offset = 0;
while src_offset < self.prologue_size {
let remaining = &self.prologue_bytes[src_offset..];
if remaining.is_empty() {
break;
}
let insn_len = A::find_instruction_boundary(remaining, 1)
.ok_or_else(|| WraithError::HookDetectionFailed {
function: format!("{:#x}", self.target + src_offset),
reason: "failed to decode instruction".into(),
})?;
let instruction = &self.prologue_bytes[src_offset..src_offset + insn_len];
if A::needs_relocation(instruction) {
let old_addr = self.target + src_offset;
let new_addr = trampoline_base + dst_offset;
let relocated = A::relocate_instruction(instruction, old_addr, new_addr)
.ok_or_else(|| WraithError::RelocationFailed {
rva: src_offset as u32,
reason: "instruction cannot be relocated".into(),
})?;
trampoline_code.extend_from_slice(&relocated);
dst_offset += relocated.len();
} else {
trampoline_code.extend_from_slice(instruction);
dst_offset += insn_len;
}
src_offset += insn_len;
}
let continuation = self.target + self.prologue_size;
let jmp_location = trampoline_base + dst_offset;
if let Some(jmp_bytes) = A::encode_jmp_rel(jmp_location, continuation) {
trampoline_code.extend_from_slice(&jmp_bytes);
} else {
let jmp_bytes = A::encode_jmp_abs(continuation);
trampoline_code.extend_from_slice(&jmp_bytes);
}
memory.write(&trampoline_code)?;
memory.flush_icache()?;
Ok(trampoline_base)
}
pub fn prologue_size(&self) -> usize {
self.prologue_size
}
pub fn prologue_bytes(&self) -> &[u8] {
&self.prologue_bytes
}
pub fn take_memory(&mut self) -> Option<ExecutableMemory> {
self.memory.take()
}
pub fn target(&self) -> usize {
self.target
}
}
pub fn build_trampoline<A: Architecture>(
target: usize,
min_hook_size: usize,
) -> Result<(ExecutableMemory, Vec<u8>, usize)> {
let mut builder = TrampolineBuilder::<A>::new(target);
builder.analyze(min_hook_size)?;
builder.allocate()?;
let trampoline_addr = builder.build()?;
let prologue_bytes = builder.prologue_bytes().to_vec();
let prologue_size = builder.prologue_size();
let memory = builder.take_memory().unwrap();
Ok((memory, prologue_bytes, prologue_size))
}
pub struct TrampolineResult {
pub memory: ExecutableMemory,
pub original_bytes: Vec<u8>,
pub prologue_size: usize,
pub entry: usize,
}
impl TrampolineResult {
pub fn entry(&self) -> usize {
self.entry
}
pub fn original_bytes(&self) -> &[u8] {
&self.original_bytes
}
pub fn prologue_size(&self) -> usize {
self.prologue_size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_creation() {
use crate::manipulation::inline_hook::arch::NativeArch;
let builder = TrampolineBuilder::<NativeArch>::new(0x12345678);
assert_eq!(builder.target(), 0x12345678);
assert_eq!(builder.prologue_size(), 0);
}
}