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-28 00:37:03 +02:00
|
|
|
#include "detours_internal.h"
|
|
|
|
|
#include "arm64/detours_arm64.h"
|
|
|
|
|
|
|
|
|
|
#include <stdatomic.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
|
|
#include <mach/mach_init.h>
|
|
|
|
|
#include <mach/mach_vm.h>
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Trampoline Memory Management
|
|
|
|
|
|
|
|
|
|
typedef struct detour_region
|
|
|
|
|
{
|
|
|
|
|
uint32_t signature;
|
|
|
|
|
struct detour_region* next; // Next region in list of regions.
|
|
|
|
|
detour_trampoline* free_list_head; // List of free trampolines in this region.
|
|
|
|
|
} detour_region;
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false, VM_PROT_READ | VM_PROT_WRITE);
|
|
|
|
|
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) {
|
|
|
|
|
const mach_error_t error = mach_vm_protect(port, (mach_vm_address_t)pRegion, DETOUR_REGION_SIZE, false, VM_PROT_READ | VM_PROT_EXECUTE);
|
|
|
|
|
if (error != err_none) {
|
|
|
|
|
DETOUR_BREAK();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// Transactions
|
|
|
|
|
|
|
|
|
|
typedef enum detour_operation_kind {
|
|
|
|
|
attach,
|
|
|
|
|
detach,
|
|
|
|
|
} detour_operation_kind;
|
|
|
|
|
|
|
|
|
|
typedef struct detour_operation
|
|
|
|
|
{
|
|
|
|
|
struct detour_operation* next;
|
|
|
|
|
detour_operation_kind kind;
|
|
|
|
|
uint8_t** pointer;
|
|
|
|
|
uint8_t* target;
|
|
|
|
|
struct detour_trampoline* trampoline;
|
|
|
|
|
vm_prot_t perm;
|
|
|
|
|
} detour_operation;
|
2025-09-27 23:12:16 +02:00
|
|
|
|
2025-09-28 00:37:03 +02:00
|
|
|
typedef struct detour_pending_thread
|
2025-09-27 23:12:16 +02:00
|
|
|
{
|
2025-09-28 00:37:03 +02:00
|
|
|
struct detour_pending_thread* next;
|
|
|
|
|
thread_t thread;
|
|
|
|
|
} detour_pending_thread;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
static void** s_pending_error_pointer = nullptr;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|