Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/injector_core/internal.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use crate::injector_core::common::*;

#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm")))]
use super::patch_trait::PatchTrait;

#[cfg(target_arch = "x86_64")]
use super::patch_amd64::PatchAmd64;
#[cfg(target_arch = "aarch64")]
use super::patch_arm64::PatchArm64;
#[cfg(target_arch = "arm")]
use super::patch_arm::PatchArm;

#[cfg(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm"))]
use super::thread_local_registry;

Expand All @@ -19,10 +25,15 @@ impl WhenCalled {
Self { func_ptr: func }
}

/// Patches the target function so that it branches to a JIT block that uses an absolute jump
/// to call the target function.
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm")))]
/// Patches the target function with a direct JMP to the replacement (0.4.0-style global patching).
/// All threads see the fake because the function's code bytes are overwritten.
/// Used by `when_called_globally()`.
pub(crate) fn will_execute_guard(self, target: FuncPtrInternal) -> PatchGuard {
#[cfg(target_arch = "x86_64")]
{
PatchAmd64::replace_function_with_other_function(self.func_ptr, target)
}

#[cfg(target_arch = "aarch64")]
{
PatchArm64::replace_function_with_other_function(self.func_ptr, target)
Expand Down Expand Up @@ -101,9 +112,14 @@ impl WhenCalled {
)
}

/// Patches the target function so that it branches to a JIT block that returns the specified boolean.
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "arm")))]
/// Patches the target function to return a fixed boolean via direct JMP (0.4.0-style).
/// All threads see the fake. Used by `when_called_globally().will_return_boolean()`.
pub(crate) fn will_return_boolean_guard(self, value: bool) -> PatchGuard {
#[cfg(target_arch = "x86_64")]
{
PatchAmd64::replace_function_return_boolean(self.func_ptr, value)
}

#[cfg(target_arch = "aarch64")]
{
PatchArm64::replace_function_return_boolean(self.func_ptr, value)
Expand Down
26 changes: 22 additions & 4 deletions src/injector_core/patch_amd64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,41 @@ fn generate_branch_to_target_function(ori_func: usize, target_func: usize) -> Ve
}

fn patch_and_guard(src: FuncPtrInternal, jit_memory: *mut u8, jit_size: usize) -> PatchGuard {
let func_addr = src.as_ptr() as usize;
// Resolve IAT thunks (jmp [rip+disp]) to the actual function address.
// This matches what thread_local_registry does, ensuring we patch the real
// function address — not the thunk — so the patch is visible to all call paths.
let func_addr = unsafe { resolve_to_real_function(src.as_ptr() as *mut u8) } as usize;
let jit_addr = jit_memory as usize;

let branch_code = generate_branch_to_target_function(func_addr, jit_addr);
let patch_size = branch_code.len();

let original_bytes = unsafe { read_bytes(src.as_ptr() as *mut u8, patch_size) };
let original_bytes = unsafe { read_bytes(func_addr as *mut u8, patch_size) };

unsafe {
patch_function(src.as_ptr() as *mut u8, &branch_code);
patch_function(func_addr as *mut u8, &branch_code);
}

PatchGuard::new(
src.as_ptr() as *mut u8,
func_addr as *mut u8,
original_bytes,
patch_size,
jit_memory,
jit_size,
)
}

/// Resolve import thunks to the actual function address on Windows x86_64.
/// Extern functions go through an IAT thunk: `jmp [rip+disp32]` (FF 25 xx xx xx xx).
/// This follows the indirection to return the real function address.
unsafe fn resolve_to_real_function(func_addr: *mut u8) -> *mut u8 {
let code = std::slice::from_raw_parts(func_addr, 6);
if code[0] == 0xFF && code[1] == 0x25 {
let disp = i32::from_le_bytes([code[2], code[3], code[4], code[5]]);
let rip_after_insn = func_addr.add(6);
let iat_entry = rip_after_insn.offset(disp as isize) as *const *mut u8;
let real_addr = std::ptr::read(iat_entry);
return resolve_to_real_function(real_addr);
}
func_addr
}
11 changes: 8 additions & 3 deletions src/injector_core/thread_local_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,19 @@ impl Drop for ThreadRegistration {

/// Called by the JIT dispatcher to get the target function pointer for the current thread.
///
/// If the current thread has a registered replacement for `method_key`, returns that.
/// Otherwise, returns `default_target` (the trampoline to the original function).
/// Returns the thread-local replacement if registered, otherwise falls back to
/// the default target (the trampoline to the original function).
///
/// # Safety
/// This function is called from JIT-generated code. It must not panic across the FFI boundary.
pub(crate) extern "C" fn get_thread_target(method_key: usize, default_target: usize) -> usize {
match std::panic::catch_unwind(AssertUnwindSafe(|| {
tls_get(&method_key, default_target)
let tls_result = tls_get(&method_key, 0);
if tls_result != 0 {
return tls_result;
}

default_target
})) {
Ok(target) => target,
Err(_) => default_target,
Expand Down
Loading
Loading