2025-09-28 00:37:03 +02:00
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
|
// Copyright (c) Lysann Tranvouez. All rights reserved.
|
|
|
|
|
|
2025-09-27 23:12:16 +02:00
|
|
|
#include "mach_detours.h"
|
|
|
|
|
|
2025-09-30 00:09:10 +02:00
|
|
|
#define DETOUR_DEBUG 0
|
2025-09-29 23:06:15 +02:00
|
|
|
|
|
|
|
|
#ifdef __arm64__
|
|
|
|
|
#define DETOURS_ARM64
|
|
|
|
|
#define DETOURS_64BIT
|
|
|
|
|
#else
|
2025-09-29 23:53:05 +02:00
|
|
|
#error Unsupported architecture (supported: arm64)
|
2025-09-29 23:06:15 +02:00
|
|
|
#endif
|
|
|
|
|
|
2025-09-28 00:37:03 +02:00
|
|
|
#include "detours_internal.h"
|
2025-09-29 23:36:09 +02:00
|
|
|
#include "detours_disasm.h"
|
2025-09-29 23:06:15 +02:00
|
|
|
|
2025-09-28 00:37:03 +02:00
|
|
|
#include "arm64/detours_arm64.h"
|
|
|
|
|
|
2025-09-29 01:07:56 +02:00
|
|
|
#include <inttypes.h>
|
2025-09-28 00:37:03 +02:00
|
|
|
#include <stdatomic.h>
|
|
|
|
|
#include <stdint.h>
|
2025-09-29 00:08:13 +02:00
|
|
|
#include <stdlib.h>
|
2025-09-28 00:37:03 +02:00
|
|
|
|
2025-09-29 00:08:13 +02:00
|
|
|
#include <mach/mach.h>
|
2025-09-28 00:37:03 +02:00
|
|
|
#include <mach/mach_vm.h>
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Trampoline Memory Management
|
|
|
|
|
|
|
|
|
|
typedef struct detour_region
|
|
|
|
|
{
|
|
|
|
|
uint32_t signature;
|
2025-09-29 01:07:56 +02:00
|
|
|
struct detour_region* next; // Next region in list of regions.
|
|
|
|
|
detour_trampoline* free_list_head; // List of free trampolines in this region.
|
2025-09-28 00:37:03 +02:00
|
|
|
} detour_region;
|
|
|
|
|
|
2025-09-29 01:19:23 +02:00
|
|
|
// ReSharper disable once CppMultiCharacterLiteral
|
2025-09-28 00:37:03 +02:00
|
|
|
static const uint32_t DETOUR_REGION_SIGNATURE = 'Rrtd';
|
|
|
|
|
static const uint32_t DETOUR_REGION_SIZE = 0x10000;
|
|
|
|
|
static const uint32_t DETOUR_TRAMPOLINES_PER_REGION = (DETOUR_REGION_SIZE / sizeof(detour_trampoline)) - 1;
|
|
|
|
|
static detour_region* s_regions_head = nullptr;
|
|
|
|
|
static detour_region* s_default_region = nullptr;
|
|
|
|
|
|
|
|
|
|
static mach_error_t internal_detour_writable_trampoline_regions()
|
|
|
|
|
{
|
|
|
|
|
// Mark all the regions as writable.
|
|
|
|
|
const mach_port_t port = mach_task_self();
|
|
|
|
|
for (detour_region* pRegion = s_regions_head; pRegion != NULL; pRegion = pRegion->next) {
|
2025-09-29 01:07:56 +02:00
|
|
|
const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false,
|
|
|
|
|
VM_PROT_READ | VM_PROT_WRITE);
|
2025-09-28 00:37:03 +02:00
|
|
|
if (error != err_none) {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return err_none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void internal_detour_runnable_trampoline_regions()
|
|
|
|
|
{
|
|
|
|
|
// Mark all the regions as executable.
|
|
|
|
|
const mach_port_t port = mach_task_self();
|
|
|
|
|
for (detour_region* pRegion = s_regions_head; pRegion != NULL; pRegion = pRegion->next) {
|
2025-09-29 01:07:56 +02:00
|
|
|
const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false,
|
|
|
|
|
VM_PROT_READ | VM_PROT_EXECUTE);
|
2025-09-28 00:37:03 +02:00
|
|
|
if (error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 23:06:15 +02:00
|
|
|
static uintptr_t internal_detour_round_down_to_page(const uintptr_t address)
|
|
|
|
|
{
|
|
|
|
|
return address & ~(PAGE_SIZE - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const uint8_t* internal_detour_alloc_round_up_to_region(const uint8_t* address)
|
|
|
|
|
{
|
|
|
|
|
// WinXP64 returns free areas that aren't REGION aligned to 32-bit applications.
|
|
|
|
|
const uintptr_t extra = ((uintptr_t)address) & (DETOUR_REGION_SIZE - 1);
|
|
|
|
|
if (extra != 0) {
|
|
|
|
|
const uintptr_t adjust = DETOUR_REGION_SIZE - extra;
|
|
|
|
|
address += adjust;
|
|
|
|
|
}
|
|
|
|
|
return address;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Starting at lo, try to allocate a memory region, continue until hi.
|
|
|
|
|
|
|
|
|
|
static void* internal_detour_alloc_region_from_lo(const uint8_t* lo, const uint8_t* hi)
|
|
|
|
|
{
|
|
|
|
|
const uint8_t* try_addr = internal_detour_alloc_round_up_to_region(lo);
|
|
|
|
|
|
|
|
|
|
DETOUR_TRACE((" Looking for free region in %p..%p from %p:\n", lo, hi, try_addr));
|
|
|
|
|
|
|
|
|
|
const vm_map_t task_self = mach_task_self();
|
|
|
|
|
|
|
|
|
|
for (vm_address_t page = (vm_address_t)try_addr; page < (vm_address_t)hi; page += PAGE_SIZE) {
|
|
|
|
|
DETOUR_TRACE((" Try %p\n", (void*)page));
|
|
|
|
|
|
|
|
|
|
const mach_error_t err = vm_allocate(task_self, &page, DETOUR_REGION_SIZE, 0);
|
|
|
|
|
if (err == err_none) {
|
|
|
|
|
return (void*)page;
|
|
|
|
|
}
|
|
|
|
|
if (err != KERN_NO_SPACE && err != KERN_INVALID_ADDRESS) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Starting at hi, try to allocate a memory region, continue until lo.
|
|
|
|
|
|
|
|
|
|
static void* internal_detour_alloc_region_from_hi(const uint8_t* lo, const uint8_t* hi)
|
|
|
|
|
{
|
|
|
|
|
uintptr_t try_addr = internal_detour_round_down_to_page((uintptr_t)(hi - DETOUR_REGION_SIZE));
|
|
|
|
|
|
|
|
|
|
DETOUR_TRACE((" Looking for free region in %p..%p from %p:\n", lo, hi, (void*)try_addr));
|
|
|
|
|
|
|
|
|
|
const vm_map_t task_self = mach_task_self();
|
|
|
|
|
|
|
|
|
|
for (vm_address_t page = try_addr; page > (vm_address_t)lo; page -= PAGE_SIZE) {
|
|
|
|
|
DETOUR_TRACE((" Try %p\n", (void*)page));
|
|
|
|
|
if ((void*)page >= s_system_region_lower_bound && (void*)page <= s_system_region_upper_bound) {
|
|
|
|
|
// Skip region reserved for system DLLs, but preserve address space entropy.
|
|
|
|
|
try_addr -= 0x08000000;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mach_error_t err = vm_allocate(task_self, &page, DETOUR_REGION_SIZE, 0);
|
|
|
|
|
if (err == err_none) {
|
|
|
|
|
return (void*)page;
|
|
|
|
|
}
|
|
|
|
|
if (err != KERN_NO_SPACE && err != KERN_INVALID_ADDRESS) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void* internal_detour_alloc_trampoline_allocate_new(const uint8_t* target,
|
2025-09-30 00:09:10 +02:00
|
|
|
detour_trampoline* lo,
|
|
|
|
|
detour_trampoline* hi)
|
2025-09-29 23:06:15 +02:00
|
|
|
{
|
|
|
|
|
void* try_addr = nullptr;
|
|
|
|
|
|
|
|
|
|
// NB: We must always also start the search at an offset from pbTarget
|
|
|
|
|
// in order to maintain ASLR entropy.
|
|
|
|
|
|
|
|
|
|
#if defined(DETOURS_64BIT)
|
|
|
|
|
// Try looking 1GB below or lower.
|
|
|
|
|
if (!try_addr && target > (uint8_t*)0x40000000) {
|
|
|
|
|
try_addr = internal_detour_alloc_region_from_hi((uint8_t*)lo, target - 0x40000000);
|
|
|
|
|
}
|
|
|
|
|
// Try looking 1GB above or higher.
|
|
|
|
|
if (!try_addr && target < (uint8_t*)0xffffffff40000000) {
|
|
|
|
|
try_addr = internal_detour_alloc_region_from_lo(target + 0x40000000, (uint8_t*)hi);
|
|
|
|
|
}
|
|
|
|
|
// Try looking 1GB below or higher.
|
|
|
|
|
if (!try_addr && target > (uint8_t*)0x40000000) {
|
|
|
|
|
try_addr = internal_detour_alloc_region_from_lo(target - 0x40000000, target);
|
|
|
|
|
}
|
|
|
|
|
// Try looking 1GB above or lower.
|
|
|
|
|
if (!try_addr && target < (uint8_t*)0xffffffff40000000) {
|
|
|
|
|
try_addr = internal_detour_alloc_region_from_hi(target, target + 0x40000000);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// Try anything below.
|
|
|
|
|
if (!try_addr) {
|
|
|
|
|
try_addr = internal_detour_alloc_region_from_hi((uint8_t*)lo, target);
|
|
|
|
|
}
|
|
|
|
|
// try anything above.
|
|
|
|
|
if (!try_addr) {
|
|
|
|
|
try_addr = internal_detour_alloc_region_from_lo(target, (uint8_t*)hi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return try_addr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static detour_trampoline* internal_detour_alloc_trampoline(uint8_t* target)
|
|
|
|
|
{
|
|
|
|
|
// We have to place trampolines within +/- 2GB of target.
|
|
|
|
|
|
|
|
|
|
detour_trampoline* lo;
|
|
|
|
|
detour_trampoline* hi;
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
detour_platform_find_jmp_bounds(target, &lo, &hi);
|
2025-09-29 23:06:15 +02:00
|
|
|
|
|
|
|
|
// ReSharper disable once CppDFAUnusedValue
|
|
|
|
|
detour_trampoline* trampoline = nullptr;
|
|
|
|
|
|
|
|
|
|
// Ensure that there is a default region.
|
|
|
|
|
if (!s_default_region && s_regions_head) {
|
|
|
|
|
s_default_region = s_regions_head;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First check the default region for a valid free block.
|
|
|
|
|
if (s_default_region && s_default_region->free_list_head &&
|
|
|
|
|
s_default_region->free_list_head >= lo && s_default_region->free_list_head <= hi) {
|
2025-09-30 00:09:10 +02:00
|
|
|
found_region:
|
2025-09-29 23:06:15 +02:00
|
|
|
trampoline = s_default_region->free_list_head;
|
|
|
|
|
// do a last sanity check on region.
|
|
|
|
|
if (trampoline < lo || trampoline > hi) {
|
2025-09-30 00:09:10 +02:00
|
|
|
DETOUR_BREAK();
|
2025-09-29 23:06:15 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
s_default_region->free_list_head = (detour_trampoline*)trampoline->ptr_remain;
|
|
|
|
|
memset(trampoline, 0xcc, sizeof(*trampoline));
|
|
|
|
|
return trampoline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then check the existing regions for a valid free block.
|
|
|
|
|
for (s_default_region = s_regions_head; s_default_region != nullptr; s_default_region = s_default_region->next) {
|
|
|
|
|
// ReSharper disable once CppDFANullDereference // false positive
|
|
|
|
|
if (s_default_region->free_list_head &&
|
|
|
|
|
s_default_region->free_list_head >= lo && s_default_region->free_list_head <= hi) {
|
|
|
|
|
goto found_region;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We need to allocate a new region.
|
|
|
|
|
|
|
|
|
|
// Round pbTarget down to 64KB block.
|
|
|
|
|
// /RTCc RuntimeChecks breaks PtrToUlong.
|
|
|
|
|
target = target - (uint32_t)((uintptr_t)target & 0xffff);
|
|
|
|
|
|
|
|
|
|
void* newly_allocated = internal_detour_alloc_trampoline_allocate_new(target, lo, hi);
|
|
|
|
|
if (newly_allocated) {
|
|
|
|
|
s_default_region = (detour_region*)newly_allocated;
|
|
|
|
|
s_default_region->signature = DETOUR_REGION_SIGNATURE;
|
|
|
|
|
s_default_region->free_list_head = nullptr;
|
|
|
|
|
s_default_region->next = s_regions_head;
|
|
|
|
|
s_regions_head = s_default_region;
|
|
|
|
|
DETOUR_TRACE((" Allocated region %p..%p\n\n",
|
2025-09-30 00:09:10 +02:00
|
|
|
s_default_region, ((uint8_t*)s_default_region) + DETOUR_REGION_SIZE - 1));
|
2025-09-29 23:06:15 +02:00
|
|
|
|
|
|
|
|
// Put everything but the first trampoline on the free list.
|
|
|
|
|
uint8_t* free = nullptr;
|
|
|
|
|
trampoline = ((detour_trampoline*)s_default_region) + 1;
|
|
|
|
|
for (uint32_t i = DETOUR_TRAMPOLINES_PER_REGION - 1; i > 1; i--) {
|
|
|
|
|
trampoline[i].ptr_remain = free;
|
|
|
|
|
free = (uint8_t*)&trampoline[i];
|
|
|
|
|
}
|
|
|
|
|
s_default_region->free_list_head = (detour_trampoline*)free;
|
|
|
|
|
goto found_region;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DETOUR_TRACE(("Couldn't find available memory region!\n"));
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 00:08:13 +02:00
|
|
|
static void internal_detour_free_trampoline(detour_trampoline* trampoline)
|
|
|
|
|
{
|
|
|
|
|
detour_region* region = (detour_region*)((uintptr_t)trampoline & ~(uintptr_t)0xffff);
|
|
|
|
|
|
|
|
|
|
memset(trampoline, 0, sizeof(*trampoline));
|
|
|
|
|
trampoline->ptr_remain = (uint8_t*)region->free_list_head;
|
|
|
|
|
region->free_list_head = trampoline;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 01:07:56 +02:00
|
|
|
static bool internal_detour_is_region_empty(detour_region* region)
|
|
|
|
|
{
|
|
|
|
|
// Stop if the region isn't a region (this would be bad).
|
|
|
|
|
if (region->signature != DETOUR_REGION_SIGNATURE) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t* region_begin = (uint8_t*)region;
|
|
|
|
|
uint8_t* region_limit = region_begin + DETOUR_REGION_SIZE;
|
|
|
|
|
|
|
|
|
|
// Stop if any of the trampolines aren't free.
|
|
|
|
|
detour_trampoline* trampoline = ((detour_trampoline*)region) + 1;
|
|
|
|
|
for (int i = 0; i < DETOUR_TRAMPOLINES_PER_REGION; i++) {
|
|
|
|
|
if (trampoline[i].ptr_remain != NULL &&
|
|
|
|
|
(trampoline[i].ptr_remain < region_begin || trampoline[i].ptr_remain >= region_limit)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OK, the region is empty.
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void internal_detour_free_unused_trampoline_regions()
|
|
|
|
|
{
|
|
|
|
|
detour_region** ptr_region_base = &s_regions_head;
|
|
|
|
|
detour_region* curr_region = s_regions_head;
|
|
|
|
|
|
|
|
|
|
const mach_port_t port = mach_task_self();
|
|
|
|
|
|
|
|
|
|
while (curr_region) {
|
|
|
|
|
if (internal_detour_is_region_empty(curr_region)) {
|
|
|
|
|
*ptr_region_base = curr_region->next;
|
|
|
|
|
|
|
|
|
|
vm_deallocate(port, (vm_address_t)curr_region, DETOUR_REGION_SIZE);
|
|
|
|
|
s_default_region = nullptr;
|
|
|
|
|
} else {
|
|
|
|
|
ptr_region_base = &curr_region->next;
|
|
|
|
|
}
|
|
|
|
|
curr_region = *ptr_region_base;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 00:37:03 +02:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Transactions
|
|
|
|
|
|
2025-09-29 01:07:56 +02:00
|
|
|
static bool s_ignore_too_small = false;
|
|
|
|
|
static bool s_retain_regions = false;
|
|
|
|
|
|
2025-09-28 00:37:03 +02:00
|
|
|
static _Atomic(thread_t) s_transaction_thread = THREAD_NULL;
|
|
|
|
|
static detour_operation* s_pending_operations_head = nullptr;
|
|
|
|
|
static detour_pending_thread* s_pending_threads_head = nullptr;
|
|
|
|
|
|
|
|
|
|
static mach_error_t s_pending_error = err_none;
|
2025-09-29 23:06:15 +02:00
|
|
|
static detour_func_t* s_pending_error_pointer = nullptr;
|
2025-09-28 00:37:03 +02:00
|
|
|
|
|
|
|
|
mach_error_t detour_transaction_begin()
|
|
|
|
|
{
|
|
|
|
|
// Make sure only one thread can start a transaction.
|
|
|
|
|
thread_t expected = THREAD_NULL;
|
|
|
|
|
// ReSharper disable once CppIncompatiblePointerConversion
|
|
|
|
|
if (!atomic_compare_exchange_strong(&s_transaction_thread, &expected, mach_thread_self())) {
|
|
|
|
|
return detour_err_in_progress;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s_pending_operations_head = nullptr;
|
|
|
|
|
s_pending_threads_head = nullptr;
|
|
|
|
|
s_pending_error_pointer = nullptr;
|
|
|
|
|
|
|
|
|
|
// Make sure the trampoline pages are writable.
|
|
|
|
|
s_pending_error = internal_detour_writable_trampoline_regions();
|
|
|
|
|
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
2025-09-29 00:08:13 +02:00
|
|
|
|
|
|
|
|
mach_error_t detour_transaction_abort()
|
|
|
|
|
{
|
|
|
|
|
if (s_transaction_thread != mach_thread_self()) {
|
|
|
|
|
return detour_err_wrong_thread;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore all the page permissions.
|
|
|
|
|
const mach_port_t port = mach_task_self();
|
|
|
|
|
for (detour_operation* operation = s_pending_operations_head; operation != nullptr;) {
|
2025-09-29 01:07:56 +02:00
|
|
|
DETOUR_CHECK(
|
|
|
|
|
mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
|
|
|
|
|
operation->perm));
|
2025-09-29 00:08:13 +02:00
|
|
|
|
|
|
|
|
if (operation->kind == detour_operation_kind_attach) {
|
|
|
|
|
if (operation->trampoline) {
|
|
|
|
|
internal_detour_free_trampoline(operation->trampoline);
|
|
|
|
|
operation->trampoline = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detour_operation* next = operation->next;
|
|
|
|
|
free(operation);
|
|
|
|
|
operation = next;
|
|
|
|
|
}
|
|
|
|
|
s_pending_operations_head = nullptr;
|
|
|
|
|
|
|
|
|
|
// Make sure the trampoline pages are no longer writable.
|
|
|
|
|
internal_detour_runnable_trampoline_regions();
|
|
|
|
|
|
|
|
|
|
// Resume any suspended threads.
|
|
|
|
|
for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr;) {
|
|
|
|
|
// There is nothing we can do if this fails.
|
|
|
|
|
DETOUR_CHECK(thread_resume(thread->thread));
|
|
|
|
|
|
|
|
|
|
detour_pending_thread* next = thread->next;
|
|
|
|
|
free(thread);
|
|
|
|
|
thread = next;
|
|
|
|
|
}
|
|
|
|
|
s_pending_threads_head = nullptr;
|
|
|
|
|
s_transaction_thread = THREAD_NULL;
|
|
|
|
|
|
|
|
|
|
return err_none;
|
|
|
|
|
}
|
2025-09-29 01:07:56 +02:00
|
|
|
|
|
|
|
|
mach_error_t detour_transaction_commit()
|
|
|
|
|
{
|
|
|
|
|
return detour_transaction_commit_ex(nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mach_error_t detour_transaction_commit_ex(detour_func_t** out_failed_target)
|
|
|
|
|
{
|
|
|
|
|
if (out_failed_target != NULL) {
|
|
|
|
|
*out_failed_target = s_pending_error_pointer;
|
|
|
|
|
}
|
|
|
|
|
if (s_transaction_thread != mach_thread_self()) {
|
|
|
|
|
return detour_err_wrong_thread;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If any of the pending operations failed, then we abort the whole transaction.
|
|
|
|
|
if (s_pending_error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
detour_transaction_abort();
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Insert or remove each of the detours.
|
|
|
|
|
for (detour_operation* operation = s_pending_operations_head; operation != nullptr; operation = operation->next) {
|
2025-10-01 22:43:05 +02:00
|
|
|
switch (operation->kind) {
|
|
|
|
|
case detour_operation_kind_attach: {
|
|
|
|
|
DETOUR_TRACE(("detours: trampoline=%p, ptr_remain=%p, ptr_detour=%p, restore_code_size=%u\n",
|
|
|
|
|
operation->trampoline,
|
|
|
|
|
operation->trampoline->ptr_remain,
|
|
|
|
|
operation->trampoline->ptr_detour,
|
|
|
|
|
operation->trampoline->restore_code_size));
|
|
|
|
|
|
|
|
|
|
DETOUR_TRACE(("detours: target=%p: "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x [before]\n",
|
|
|
|
|
operation->target,
|
|
|
|
|
operation->target[0], operation->target[1], operation->target[2], operation->target[3],
|
|
|
|
|
operation->target[4], operation->target[5], operation->target[6], operation->target[7],
|
|
|
|
|
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
detour_platform_operation_commit_detour(operation);
|
|
|
|
|
*operation->pointer = detour_platform_operation_get_trampoline_ptr(operation)
|
2025-10-01 22:43:05 +02:00
|
|
|
|
|
|
|
|
DETOUR_TRACE(("detours: target=%p: "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x [after]\n",
|
|
|
|
|
operation->target,
|
|
|
|
|
operation->target[0], operation->target[1], operation->target[2], operation->target[3],
|
|
|
|
|
operation->target[4], operation->target[5], operation->target[6], operation->target[7],
|
|
|
|
|
operation->target[8], operation->target[9], operation->target[10], operation->target[11]));
|
|
|
|
|
|
|
|
|
|
DETOUR_TRACE(("detours: trampoline=%p: "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x\n",
|
|
|
|
|
operation->trampoline,
|
|
|
|
|
operation->trampoline->code[0], operation->trampoline->code[1],
|
|
|
|
|
operation->trampoline->code[2], operation->trampoline->code[3],
|
|
|
|
|
operation->trampoline->code[4], operation->trampoline->code[5],
|
|
|
|
|
operation->trampoline->code[6], operation->trampoline->code[7],
|
|
|
|
|
operation->trampoline->code[8], operation->trampoline->code[9],
|
|
|
|
|
operation->trampoline->code[10], operation->trampoline->code[11]));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case detour_operation_kind_detach: {
|
|
|
|
|
memcpy(operation->target, operation->trampoline->restore_code,
|
|
|
|
|
operation->trampoline->restore_code_size);
|
2025-10-01 22:55:31 +02:00
|
|
|
*operation->pointer = detour_platform_operation_get_target_ptr(operation);
|
2025-10-01 22:43:05 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2025-09-29 01:07:56 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update any suspended threads.
|
|
|
|
|
for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr; thread = thread->next) {
|
2025-10-01 22:55:31 +02:00
|
|
|
detour_platform_update_thread_on_commit(thread, s_pending_operations_head);
|
2025-09-29 01:07:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore all the page permissions
|
|
|
|
|
bool freed_trampoline = false;
|
|
|
|
|
const mach_port_t port = mach_task_self();
|
|
|
|
|
for (detour_operation* operation = s_pending_operations_head; operation != nullptr;) {
|
|
|
|
|
DETOUR_CHECK(
|
|
|
|
|
mach_vm_protect(port, (mach_vm_address_t)operation->target, operation->trampoline->restore_code_size, false,
|
|
|
|
|
operation->perm));
|
|
|
|
|
|
|
|
|
|
if (operation->kind == detour_operation_kind_detach && operation->trampoline) {
|
|
|
|
|
internal_detour_free_trampoline(operation->trampoline);
|
|
|
|
|
operation->trampoline = nullptr;
|
|
|
|
|
freed_trampoline = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detour_operation* next = operation->next;
|
|
|
|
|
free(operation);
|
|
|
|
|
operation = next;
|
|
|
|
|
}
|
|
|
|
|
s_pending_operations_head = nullptr;
|
|
|
|
|
|
|
|
|
|
// Free any trampoline regions that are now unused.
|
|
|
|
|
if (freed_trampoline && !s_retain_regions) {
|
|
|
|
|
internal_detour_free_unused_trampoline_regions();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the trampoline pages are no longer writable.
|
|
|
|
|
internal_detour_runnable_trampoline_regions();
|
|
|
|
|
|
|
|
|
|
// Resume any suspended threads.
|
|
|
|
|
for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr;) {
|
|
|
|
|
// There is nothing we can do if this fails.
|
|
|
|
|
DETOUR_CHECK(thread_resume(thread->thread));
|
|
|
|
|
|
|
|
|
|
detour_pending_thread* next = thread->next;
|
|
|
|
|
free(thread);
|
|
|
|
|
thread = next;
|
|
|
|
|
}
|
|
|
|
|
s_pending_threads_head = nullptr;
|
|
|
|
|
s_transaction_thread = THREAD_NULL;
|
|
|
|
|
|
|
|
|
|
if (out_failed_target) {
|
|
|
|
|
*out_failed_target = s_pending_error_pointer;
|
|
|
|
|
}
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
2025-09-29 01:28:30 +02:00
|
|
|
|
|
|
|
|
mach_error_t detour_manage_thread(thread_t thread)
|
|
|
|
|
{
|
|
|
|
|
mach_error_t error;
|
|
|
|
|
|
|
|
|
|
// If any of the pending operations failed, then we don't need to do this.
|
|
|
|
|
if (s_pending_error != err_none) {
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// must be the transaction thread
|
|
|
|
|
if (s_transaction_thread != mach_thread_self()) {
|
|
|
|
|
return detour_err_wrong_thread;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Silently (and safely) drop any attempt to suspend our own thread.
|
|
|
|
|
if (thread == mach_thread_self()) {
|
|
|
|
|
return err_none;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 23:06:15 +02:00
|
|
|
detour_pending_thread* new_pending_thread = calloc(1, sizeof(detour_pending_thread));
|
2025-09-29 01:28:30 +02:00
|
|
|
if (!new_pending_thread) {
|
|
|
|
|
error = KERN_RESOURCE_SHORTAGE;
|
|
|
|
|
fail:
|
|
|
|
|
free(new_pending_thread);
|
2025-09-29 23:06:15 +02:00
|
|
|
// ReSharper disable once CppDFAUnusedValue
|
|
|
|
|
new_pending_thread = nullptr;
|
2025-09-29 01:28:30 +02:00
|
|
|
s_pending_error = error;
|
|
|
|
|
s_pending_error_pointer = nullptr;
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
error = thread_suspend(thread);
|
|
|
|
|
if (error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_pending_thread->thread = thread;
|
|
|
|
|
new_pending_thread->next = s_pending_threads_head;
|
|
|
|
|
s_pending_threads_head = new_pending_thread;
|
|
|
|
|
|
|
|
|
|
return err_none;
|
|
|
|
|
}
|
2025-09-29 23:06:15 +02:00
|
|
|
|
2025-09-29 23:53:05 +02:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Attach/Detach
|
|
|
|
|
|
2025-09-29 23:06:15 +02:00
|
|
|
mach_error_t detour_attach(detour_func_t* inout_pointer, detour_func_t detour)
|
|
|
|
|
{
|
|
|
|
|
return detour_attach_ex(inout_pointer, detour, nullptr, nullptr, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-30 00:09:10 +02:00
|
|
|
mach_error_t detour_attach_ex(detour_func_t* inout_pointer, detour_func_t detour, detour_func_t* out_real_trampoline,
|
|
|
|
|
detour_func_t* out_real_target, detour_func_t* out_real_detour)
|
2025-09-29 23:06:15 +02:00
|
|
|
{
|
|
|
|
|
if (out_real_trampoline) {
|
|
|
|
|
*out_real_trampoline = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (out_real_target) {
|
|
|
|
|
*out_real_target = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (out_real_detour) {
|
|
|
|
|
*out_real_detour = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (!detour) {
|
|
|
|
|
DETOUR_TRACE(("empty detour\n"));
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!inout_pointer) {
|
|
|
|
|
DETOUR_TRACE(("inout_pointer is null\n"));
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
if (!(*inout_pointer)) {
|
|
|
|
|
s_pending_error = KERN_INVALID_ARGUMENT;
|
|
|
|
|
s_pending_error_pointer = inout_pointer;
|
|
|
|
|
DETOUR_TRACE(("*inout_pointer is null (inout_pointer=%p)\n", inout_pointer));
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const thread_t active_thread = s_transaction_thread;
|
|
|
|
|
if (active_thread != mach_thread_self()) {
|
|
|
|
|
DETOUR_TRACE(("transaction conflict with thread id=%u\n", active_thread));
|
|
|
|
|
return detour_err_wrong_thread;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If any of the pending operations failed, then we don't need to do this.
|
|
|
|
|
if (s_pending_error != err_none) {
|
|
|
|
|
DETOUR_TRACE(("pending transaction error=%d\n", s_pending_error));
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mach_error_t error = err_none;
|
|
|
|
|
detour_trampoline* trampoline = nullptr;
|
|
|
|
|
detour_operation* op = nullptr;
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
uint8_t* target = detour_platform_skip_jmp(*inout_pointer);
|
|
|
|
|
detour = detour_platform_skip_jmp(detour);
|
2025-09-29 23:06:15 +02:00
|
|
|
|
|
|
|
|
// Don't follow a jump if its destination is the target function.
|
|
|
|
|
// This happens when the detour does nothing other than call the target.
|
|
|
|
|
if (detour == (detour_func_t)target) {
|
|
|
|
|
if (s_ignore_too_small) {
|
|
|
|
|
goto stop;
|
|
|
|
|
} else {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
error = detour_err_too_small;
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (out_real_target) {
|
|
|
|
|
*out_real_target = target;
|
|
|
|
|
}
|
|
|
|
|
if (out_real_detour) {
|
|
|
|
|
*out_real_detour = detour;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
op = calloc(1, sizeof(detour_operation));
|
|
|
|
|
if (!op) {
|
|
|
|
|
error = KERN_RESOURCE_SHORTAGE;
|
2025-09-30 00:09:10 +02:00
|
|
|
fail:
|
2025-09-29 23:06:15 +02:00
|
|
|
s_pending_error = error;
|
|
|
|
|
DETOUR_BREAK();
|
2025-09-30 00:09:10 +02:00
|
|
|
stop:
|
2025-09-29 23:06:15 +02:00
|
|
|
if (trampoline) {
|
|
|
|
|
internal_detour_free_trampoline(trampoline);
|
|
|
|
|
// ReSharper disable once CppDFAUnusedValue
|
|
|
|
|
trampoline = nullptr;
|
|
|
|
|
if (out_real_trampoline) {
|
|
|
|
|
*out_real_trampoline = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(op);
|
|
|
|
|
// ReSharper disable once CppDFAUnusedValue
|
|
|
|
|
op = nullptr;
|
|
|
|
|
|
|
|
|
|
if (out_real_detour) {
|
|
|
|
|
*out_real_detour = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (out_real_target) {
|
|
|
|
|
*out_real_target = nullptr;
|
|
|
|
|
}
|
|
|
|
|
s_pending_error_pointer = inout_pointer;
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trampoline = internal_detour_alloc_trampoline(target);
|
|
|
|
|
if (!trampoline) {
|
|
|
|
|
error = KERN_RESOURCE_SHORTAGE;
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
if (out_real_trampoline) {
|
|
|
|
|
*out_real_trampoline = trampoline;
|
|
|
|
|
}
|
|
|
|
|
memset(trampoline->align, 0, sizeof(trampoline->align));
|
|
|
|
|
|
|
|
|
|
DETOUR_TRACE(("detours: trampoline=%p, detour=%p\n", trampoline, detour));
|
|
|
|
|
|
|
|
|
|
// Determine the number of movable target instructions.
|
2025-10-01 22:08:01 +02:00
|
|
|
const uint8_t* src = target;
|
2025-09-29 23:06:15 +02:00
|
|
|
uint8_t* trampoline_code = trampoline->code;
|
|
|
|
|
uint8_t* trampoline_code_limit = trampoline_code + sizeof(trampoline->code);
|
|
|
|
|
uint32_t offset_target = 0;
|
|
|
|
|
uint32_t align_idx = 0;
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
while (offset_target < DETOUR_PLATFORM_SIZE_OF_JMP) {
|
2025-09-29 23:06:15 +02:00
|
|
|
const uint8_t* curr_op = src;
|
2025-10-01 00:21:04 +02:00
|
|
|
uint32_t extra_len = 0;
|
2025-09-29 23:06:15 +02:00
|
|
|
|
|
|
|
|
DETOUR_TRACE((" copy instruction src=%p, dest=%p\n", src, trampoline_code));
|
2025-10-01 00:21:04 +02:00
|
|
|
src = internal_detour_copy_instruction(trampoline_code, src, &extra_len);
|
2025-09-29 23:06:15 +02:00
|
|
|
DETOUR_TRACE((" after: src=%p (copied %d bytes)\n", src, (int)(src - curr_op)));
|
|
|
|
|
trampoline_code += (src - curr_op) + extra_len;
|
|
|
|
|
offset_target = (int32_t)(src - target);
|
|
|
|
|
trampoline->align[align_idx].offset_target = offset_target;
|
|
|
|
|
trampoline->align[align_idx].offset_trampoline = trampoline_code - trampoline->code;
|
|
|
|
|
align_idx++;
|
|
|
|
|
|
|
|
|
|
if (align_idx >= ARRAYSIZE(trampoline->align)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
if (detour_platform_does_code_end_function(curr_op)) {
|
2025-09-29 23:06:15 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Consume, but don't duplicate padding if it is needed and available.
|
2025-10-01 22:55:31 +02:00
|
|
|
while (offset_target < DETOUR_PLATFORM_SIZE_OF_JMP) {
|
|
|
|
|
const uint32_t len_filler = detour_platform_is_code_filler(src);
|
2025-09-29 23:06:15 +02:00
|
|
|
if (len_filler == 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
src += len_filler;
|
|
|
|
|
offset_target = (int32_t)(src - target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if DETOUR_DEBUG
|
|
|
|
|
{
|
|
|
|
|
DETOUR_TRACE((" detours: align ["));
|
|
|
|
|
int32_t n = 0;
|
|
|
|
|
for (n = 0; n < ARRAYSIZE(trampoline->align); n++) {
|
|
|
|
|
if (trampoline->align[n].offset_target == 0 && trampoline->align[n].offset_trampoline == 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
DETOUR_TRACE((" %u/%u", trampoline->align[n].offset_target, trampoline->align[n].offset_trampoline));
|
|
|
|
|
}
|
|
|
|
|
DETOUR_TRACE((" ]\n"));
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
if (offset_target < DETOUR_PLATFORM_SIZE_OF_JMP || align_idx > ARRAYSIZE(trampoline->align)) {
|
2025-09-29 23:06:15 +02:00
|
|
|
// Too few instructions.
|
|
|
|
|
error = detour_err_too_small;
|
|
|
|
|
if (s_ignore_too_small) {
|
|
|
|
|
goto stop;
|
|
|
|
|
} else {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (trampoline_code > trampoline_code_limit) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trampoline->code_size = (uint8_t)(trampoline_code - trampoline->code);
|
|
|
|
|
trampoline->restore_code_size = (uint8_t)offset_target;
|
|
|
|
|
memcpy(trampoline->restore_code, target, offset_target);
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
if (offset_target > sizeof(trampoline->code) - DETOUR_PLATFORM_SIZE_OF_JMP) {
|
2025-09-29 23:06:15 +02:00
|
|
|
// Too many instructions.
|
|
|
|
|
error = detour_err_too_large;
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trampoline->ptr_remain = target + offset_target;
|
|
|
|
|
trampoline->ptr_detour = (uint8_t*)detour;
|
|
|
|
|
|
|
|
|
|
trampoline_code = trampoline->code + trampoline->code_size;
|
|
|
|
|
|
2025-09-30 00:09:10 +02:00
|
|
|
trampoline_code =
|
2025-10-01 22:55:31 +02:00
|
|
|
detour_platform_gen_jmp_immediate(trampoline_code, &trampoline_code_limit, trampoline->ptr_remain);
|
|
|
|
|
trampoline_code = detour_platform_gen_brk(trampoline_code, trampoline_code_limit);
|
2025-09-29 23:06:15 +02:00
|
|
|
UNUSED_VARIABLE(trampoline_code);
|
|
|
|
|
|
|
|
|
|
const mach_port_t port = mach_task_self();
|
|
|
|
|
const mach_vm_address_t page_addr = internal_detour_round_down_to_page((uintptr_t)target);
|
|
|
|
|
|
|
|
|
|
vm_region_submap_short_info_data_64_t region_info;
|
|
|
|
|
{
|
|
|
|
|
mach_vm_address_t region_addr = (mach_vm_address_t)target;
|
|
|
|
|
mach_vm_size_t region_size = 0;
|
|
|
|
|
natural_t nesting_depth = 99999;
|
|
|
|
|
mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
|
2025-09-30 00:09:10 +02:00
|
|
|
error = mach_vm_region_recurse(port, ®ion_addr, ®ion_size, &nesting_depth,
|
|
|
|
|
(vm_region_recurse_info_t)®ion_info, &count);
|
2025-09-29 23:06:15 +02:00
|
|
|
if (error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const vm_prot_t old_perm = region_info.protection;
|
|
|
|
|
|
|
|
|
|
error = mach_vm_protect(port, page_addr, PAGE_SIZE, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
|
|
|
|
|
if (error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DETOUR_TRACE(("detours: target=%p: "
|
2025-09-30 00:09:10 +02:00
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x\n",
|
|
|
|
|
target,
|
|
|
|
|
target[0], target[1], target[2], target[3],
|
|
|
|
|
target[4], target[5], target[6], target[7],
|
|
|
|
|
target[8], target[9], target[10], target[11]));
|
2025-09-29 23:06:15 +02:00
|
|
|
DETOUR_TRACE(("detours: trampoline =%p: "
|
2025-09-30 00:09:10 +02:00
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x "
|
|
|
|
|
"%02x %02x %02x %02x\n",
|
|
|
|
|
trampoline,
|
|
|
|
|
trampoline->code[0], trampoline->code[1],
|
|
|
|
|
trampoline->code[2], trampoline->code[3],
|
|
|
|
|
trampoline->code[4], trampoline->code[5],
|
|
|
|
|
trampoline->code[6], trampoline->code[7],
|
|
|
|
|
trampoline->code[8], trampoline->code[9],
|
|
|
|
|
trampoline->code[10], trampoline->code[11]));
|
2025-09-29 23:06:15 +02:00
|
|
|
|
|
|
|
|
op->kind = detour_operation_kind_attach;
|
|
|
|
|
op->pointer = (uint8_t**)inout_pointer;
|
|
|
|
|
op->trampoline = trampoline;
|
|
|
|
|
op->target = target;
|
|
|
|
|
op->perm = old_perm;
|
|
|
|
|
op->next = s_pending_operations_head;
|
|
|
|
|
s_pending_operations_head = op;
|
|
|
|
|
|
|
|
|
|
return err_none;
|
|
|
|
|
}
|
2025-09-29 23:53:05 +02:00
|
|
|
|
|
|
|
|
mach_error_t detour_detach(detour_func_t* inout_pointer, detour_func_t detour)
|
|
|
|
|
{
|
|
|
|
|
if (s_transaction_thread != mach_thread_self()) {
|
|
|
|
|
return detour_err_wrong_thread;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If any of the pending operations failed, then we don't need to do this.
|
|
|
|
|
if (s_pending_error != err_none) {
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!detour) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
if (!inout_pointer) {
|
|
|
|
|
return KERN_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
if (!(*inout_pointer)) {
|
|
|
|
|
s_pending_error = KERN_INVALID_ARGUMENT;
|
|
|
|
|
s_pending_error_pointer = inout_pointer;
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
return s_pending_error;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mach_error_t error = err_none;
|
|
|
|
|
|
|
|
|
|
detour_operation* op = calloc(1, sizeof(detour_operation));
|
|
|
|
|
if (!op) {
|
|
|
|
|
error = KERN_RESOURCE_SHORTAGE;
|
2025-09-30 00:09:10 +02:00
|
|
|
fail:
|
2025-09-29 23:53:05 +02:00
|
|
|
s_pending_error = error;
|
|
|
|
|
DETOUR_BREAK();
|
2025-09-30 00:09:10 +02:00
|
|
|
stop:
|
2025-09-29 23:53:05 +02:00
|
|
|
free(op);
|
|
|
|
|
// ReSharper disable once CppDFAUnusedValue
|
|
|
|
|
op = nullptr;
|
|
|
|
|
s_pending_error_pointer = inout_pointer;
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-01 22:55:31 +02:00
|
|
|
detour_trampoline* trampoline = (detour_trampoline*)detour_platform_skip_jmp(*inout_pointer);
|
|
|
|
|
detour = detour_platform_skip_jmp(detour);
|
2025-09-29 23:53:05 +02:00
|
|
|
|
|
|
|
|
// Verify that Trampoline is in place.
|
|
|
|
|
const int32_t restore_code_size = trampoline->restore_code_size;
|
|
|
|
|
uint8_t* target = trampoline->ptr_remain - restore_code_size;
|
|
|
|
|
if (restore_code_size == 0 || restore_code_size > sizeof(trampoline->code)) {
|
|
|
|
|
error = KERN_FAILURE;
|
|
|
|
|
if (s_ignore_too_small) {
|
|
|
|
|
goto stop;
|
|
|
|
|
} else {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (trampoline->ptr_detour != detour) {
|
|
|
|
|
error = KERN_FAILURE;
|
|
|
|
|
if (s_ignore_too_small) {
|
|
|
|
|
goto stop;
|
|
|
|
|
} else {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mach_port_t port = mach_task_self();
|
|
|
|
|
|
|
|
|
|
vm_region_submap_short_info_data_64_t region_info;
|
|
|
|
|
{
|
|
|
|
|
mach_vm_address_t region_addr = (mach_vm_address_t)target;
|
|
|
|
|
mach_vm_size_t region_size = 0;
|
|
|
|
|
natural_t nesting_depth = 99999;
|
|
|
|
|
mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
|
2025-09-30 00:09:10 +02:00
|
|
|
error = mach_vm_region_recurse(port, ®ion_addr, ®ion_size, &nesting_depth,
|
|
|
|
|
(vm_region_recurse_info_t)®ion_info, &count);
|
2025-09-29 23:53:05 +02:00
|
|
|
if (error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const vm_prot_t old_perm = region_info.protection;
|
|
|
|
|
|
2025-09-30 00:09:10 +02:00
|
|
|
error = mach_vm_protect(port, (mach_vm_address_t)target, PAGE_SIZE, false,
|
|
|
|
|
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
|
2025-09-29 23:53:05 +02:00
|
|
|
if (error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
op->kind = detour_operation_kind_detach;
|
|
|
|
|
op->pointer = (uint8_t**)inout_pointer;
|
|
|
|
|
op->trampoline = trampoline;
|
|
|
|
|
op->target = target;
|
|
|
|
|
op->perm = old_perm;
|
|
|
|
|
op->next = s_pending_operations_head;
|
|
|
|
|
s_pending_operations_head = op;
|
|
|
|
|
|
|
|
|
|
return ERR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// API Setters
|
|
|
|
|
|
|
|
|
|
bool detour_set_ignore_too_small(const bool value)
|
|
|
|
|
{
|
|
|
|
|
const bool previous = s_ignore_too_small;
|
|
|
|
|
s_ignore_too_small = value;
|
|
|
|
|
return previous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool detour_set_retain_regions(const bool value)
|
|
|
|
|
{
|
|
|
|
|
const bool previous = s_retain_regions;
|
|
|
|
|
s_retain_regions = value;
|
|
|
|
|
return previous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void* detour_set_system_region_lower_bound(void* value)
|
|
|
|
|
{
|
|
|
|
|
void* previous = s_system_region_lower_bound;
|
|
|
|
|
s_system_region_lower_bound = value;
|
|
|
|
|
return previous;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void* detour_set_system_region_upper_bound(void* value)
|
|
|
|
|
{
|
|
|
|
|
void* previous = s_system_region_upper_bound;
|
|
|
|
|
s_system_region_upper_bound = value;
|
|
|
|
|
return previous;
|
|
|
|
|
}
|