use windows::Win32::Foundation::HANDLE;
use super::TrampolineHook;
use crate::memory::MemoryError;
use crate::memory_hook::{utils::SendableHandle, Architecture};
#[derive(Debug, Clone)]
pub struct TrampolineHookBuilder {
handle: Option<HANDLE>,
target_address: Option<usize>,
detour_code: Option<Vec<u8>>,
trampoline_address: Option<usize>,
original_bytes: Option<Vec<u8>>,
bytes_to_overwrite: Option<usize>,
architecture: Option<Architecture>,
skip_trampoline: bool, }
impl TrampolineHookBuilder {
pub fn new() -> Self {
Self {
handle: None,
target_address: None,
detour_code: None,
trampoline_address: None,
original_bytes: None,
bytes_to_overwrite: None,
architecture: None,
skip_trampoline: false,
}
}
pub fn handle(mut self, handle: HANDLE) -> Self {
self.handle = Some(handle);
self
}
pub fn target_address(mut self, addr: usize) -> Self {
self.target_address = Some(addr);
self
}
pub fn detour_code(mut self, code: Vec<u8>) -> Self {
self.detour_code = Some(code);
self
}
pub fn trampoline_address(mut self, addr: usize) -> Self {
self.trampoline_address = Some(addr);
self
}
pub fn original_bytes(mut self, bytes: Vec<u8>) -> Self {
self.original_bytes = Some(bytes);
self
}
pub fn bytes_to_overwrite(mut self, bytes: usize) -> Self {
self.bytes_to_overwrite = Some(bytes);
self
}
pub fn architecture(mut self, arch: Architecture) -> Self {
self.architecture = Some(arch);
self
}
pub fn x86(self) -> Self {
self.architecture(Architecture::X86)
}
pub fn x64(self) -> Self {
self.architecture(Architecture::X64)
}
pub fn skip_trampoline(mut self, skip: bool) -> Self {
self.skip_trampoline = skip;
self
}
pub fn build(self) -> Result<TrampolineHook, MemoryError> {
let handle = self.handle.ok_or_else(|| {
MemoryError::WriteFailed(
"handle must be set. Call .handle(handle) before build().".to_string(),
)
})?;
let target_address = self.target_address.ok_or_else(|| {
MemoryError::WriteFailed(
"target_address must be set. Call .target_address(addr) before build(), \
or use .handle(handle).target_address(addr).build() chain."
.to_string(),
)
})?;
let detour_code = self.detour_code.ok_or_else(|| {
MemoryError::WriteFailed(
"detour_code must be set. TrampolineHook only supports auto-allocated detour. \
Call .detour_code(shellcode) before build()."
.to_string(),
)
})?;
let bytes_to_overwrite = self.bytes_to_overwrite.unwrap_or_else(|| {
match self.architecture.unwrap_or(Architecture::X64) {
Architecture::X86 => 5,
Architecture::X64 => 14,
}
});
Ok(TrampolineHook {
handle: SendableHandle(handle),
target_address,
detour_address: 0, detour_code: Some(detour_code),
trampoline_address: self.trampoline_address,
original_bytes: self.original_bytes.unwrap_or_default(),
bytes_to_overwrite,
is_installed: false,
architecture: self.architecture.unwrap_or(Architecture::X64),
skip_trampoline: self.skip_trampoline,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use windows::Win32::Foundation::HANDLE;
#[test]
fn test_builder_x86_quick() {
let shellcode = vec![0x01, 0xD2];
let result = TrampolineHookBuilder::new()
.handle(HANDLE::default())
.target_address(0x1000)
.detour_code(shellcode)
.x86()
.build();
assert!(result.is_ok());
}
#[test]
fn test_builder_fluent_api() {
let shellcode = vec![0x01, 0xD2];
let result = TrampolineHookBuilder::new()
.handle(HANDLE::default())
.target_address(0x1000)
.detour_code(shellcode)
.x86()
.bytes_to_overwrite(6)
.build();
assert!(result.is_ok());
}
#[test]
fn test_builder_clone_support() {
let builder = TrampolineHookBuilder::new()
.detour_code(vec![0x01, 0xD2])
.x86();
let hook1 = builder
.clone()
.handle(HANDLE::default())
.target_address(0x1000)
.build();
let hook2 = builder
.clone()
.handle(HANDLE::default())
.target_address(0x2000)
.build();
assert!(hook1.is_ok());
assert!(hook2.is_ok());
}
#[test]
fn test_builder_validation_missing_handle() {
let result = TrampolineHookBuilder::new()
.target_address(0x1000)
.detour_code(vec![0x01, 0xD2])
.x86()
.build();
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("handle must be set"));
}
}
#[test]
fn test_builder_validation_missing_target_address() {
let result = TrampolineHookBuilder::new()
.handle(HANDLE::default())
.detour_code(vec![0x01, 0xD2])
.x86()
.build();
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("target_address must be set"));
}
}
#[test]
fn test_builder_validation_missing_detour_code() {
let result = TrampolineHookBuilder::new()
.handle(HANDLE::default())
.target_address(0x1000)
.x86()
.build();
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("detour_code must be set"));
}
}
}