// Copyright (c) Microsoft Corporation. All rights reserved. // Copyright (c) Lysann Tranvouez. All rights reserved. #include "mach_detours.h" #define DETOUR_DEBUG 0 #ifdef __arm64__ #define DETOURS_ARM64 #define DETOURS_64BIT #else #error Unsupported architecture (supported: arm64) #endif #include "detours_internal.h" #include "detours_disasm.h" #include "arm64/detours_arm64.h" #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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; // ReSharper disable once CppMultiCharacterLiteral 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(); } } } 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, detour_trampoline* lo, detour_trampoline* hi) { 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; internal_detour_find_jmp_bounds(target, &lo, &hi); // 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) { found_region: trampoline = s_default_region->free_list_head; // do a last sanity check on region. if (trampoline < lo || trampoline > hi) { DETOUR_BREAK(); 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", s_default_region, ((uint8_t*)s_default_region) + DETOUR_REGION_SIZE - 1)); // 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; } 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; } 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; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Transactions static bool s_ignore_too_small = false; static bool s_retain_regions = false; 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 detour_func_t* 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; } 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;) { 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_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; } 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) { 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])); internal_detour_operation_commit_detour(operation); *operation->pointer = internal_detour_operation_get_trampoline_ptr(operation) 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); *operation->pointer = internal_detour_operation_get_target_ptr(operation); break; } } } // Update any suspended threads. for (detour_pending_thread* thread = s_pending_threads_head; thread != nullptr; thread = thread->next) { internal_detour_update_thread_on_commit(thread, s_pending_operations_head); } // 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; } 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; } detour_pending_thread* new_pending_thread = calloc(1, sizeof(detour_pending_thread)); if (!new_pending_thread) { error = KERN_RESOURCE_SHORTAGE; fail: free(new_pending_thread); // ReSharper disable once CppDFAUnusedValue new_pending_thread = nullptr; 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; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Attach/Detach mach_error_t detour_attach(detour_func_t* inout_pointer, detour_func_t detour) { return detour_attach_ex(inout_pointer, detour, nullptr, nullptr, nullptr); } 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) { 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; uint8_t* target = internal_detour_skip_jmp(*inout_pointer); detour = internal_detour_skip_jmp(detour); // 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; fail: s_pending_error = error; DETOUR_BREAK(); stop: 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. const uint8_t* src = target; 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; while (offset_target < DETOUR_SIZE_OF_JMP) { const uint8_t* curr_op = src; uint32_t extra_len = 0; DETOUR_TRACE((" copy instruction src=%p, dest=%p\n", src, trampoline_code)); src = internal_detour_copy_instruction(trampoline_code, src, &extra_len); 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; } if (internal_detour_does_code_end_function(curr_op)) { break; } } // Consume, but don't duplicate padding if it is needed and available. while (offset_target < DETOUR_SIZE_OF_JMP) { const uint32_t len_filler = internal_detour_is_code_filler(src); 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 if (offset_target < DETOUR_SIZE_OF_JMP || align_idx > ARRAYSIZE(trampoline->align)) { // 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); if (offset_target > sizeof(trampoline->code) - DETOUR_SIZE_OF_JMP) { // 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; trampoline_code = internal_detour_gen_jmp_immediate(trampoline_code, &trampoline_code_limit, trampoline->ptr_remain); trampoline_code = internal_detour_gen_brk(trampoline_code, trampoline_code_limit); 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; error = mach_vm_region_recurse(port, ®ion_addr, ®ion_size, &nesting_depth, (vm_region_recurse_info_t)®ion_info, &count); 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: " "%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])); DETOUR_TRACE(("detours: trampoline =%p: " "%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])); 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; } 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; fail: s_pending_error = error; DETOUR_BREAK(); stop: free(op); // ReSharper disable once CppDFAUnusedValue op = nullptr; s_pending_error_pointer = inout_pointer; return error; } detour_trampoline* trampoline = (detour_trampoline*)internal_detour_skip_jmp(*inout_pointer); detour = internal_detour_skip_jmp(detour); // 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; error = mach_vm_region_recurse(port, ®ion_addr, ®ion_size, &nesting_depth, (vm_region_recurse_info_t)®ion_info, &count); if (error != err_none) { DETOUR_BREAK(); goto fail; } } const vm_prot_t old_perm = region_info.protection; error = mach_vm_protect(port, (mach_vm_address_t)target, PAGE_SIZE, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); 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; }