diff --git a/include/mach_detours.h b/include/mach_detours.h index 382a022..1cf57fa 100644 --- a/include/mach_detours.h +++ b/include/mach_detours.h @@ -12,6 +12,8 @@ typedef void* detour_func_t; #define detour_err_in_progress (err_local | 1) #define detour_err_wrong_thread (err_local | 2) +#define detour_err_too_small (err_local | 3) +#define detour_err_too_large (err_local | 4) mach_error_t detour_transaction_begin(); mach_error_t detour_transaction_abort(); diff --git a/src/arm64/detours_arm64.h b/src/arm64/detours_arm64.h index 323739d..cfea12f 100644 --- a/src/arm64/detours_arm64.h +++ b/src/arm64/detours_arm64.h @@ -7,12 +7,6 @@ #include -typedef struct detour_align -{ - uint8_t offset_target; - uint8_t offset_trampoline; -} detour_align; - typedef struct detour_trampoline { // An ARM64 instruction is 4 bytes long. @@ -54,21 +48,25 @@ typedef struct detour_trampoline static_assert(sizeof(detour_trampoline) == 192); -typedef uint32_t detours_arm64_opcode_t; +enum { + DETOUR_SIZE_OF_JMP = 12 +}; -static inline detours_arm64_opcode_t fetch_opcode(const uint8_t* code) +typedef uint32_t detour_arm64_opcode_t; + +static inline detour_arm64_opcode_t fetch_opcode(const uint8_t* code) { - return *(detours_arm64_opcode_t*)code; + return *(detour_arm64_opcode_t*)code; } -static inline void write_opcode(uint8_t** int_out_code, const detours_arm64_opcode_t opcode) +static inline void write_opcode(uint8_t** int_out_code, const detour_arm64_opcode_t opcode) { uint8_t* code = *int_out_code; - *(detours_arm64_opcode_t*)code = opcode; - *int_out_code += sizeof(detours_arm64_opcode_t); + *(detour_arm64_opcode_t*)code = opcode; + *int_out_code += sizeof(detour_arm64_opcode_t); } -struct detours_arm64_indirect_jmp { +struct detour_arm64_indirect_jmp { struct { uint32_t Rd : 5; uint32_t immhi : 19; @@ -91,7 +89,7 @@ struct detours_arm64_indirect_jmp { uint32_t br; }; -union detours_arm64_indirect_imm { +union detour_arm64_indirect_imm { struct { uint64_t pad : 12; uint64_t adrp_immlo : 2; @@ -107,12 +105,12 @@ static inline uint8_t* internal_detour_gen_jmp_indirect(uint8_t* code, const uin // ldr x17, [x17, jmpval] // br x17 - union detours_arm64_indirect_imm jmp_ind_addr; + union detour_arm64_indirect_imm jmp_ind_addr; jmp_ind_addr.value = (((uint64_t)jump_val) & 0xFFFFFFFFFFFFF000) - (((uint64_t)code) & 0xFFFFFFFFFFFFF000); - struct detours_arm64_indirect_jmp* ind_jmp = (struct detours_arm64_indirect_jmp*)code; + struct detour_arm64_indirect_jmp* ind_jmp = (struct detour_arm64_indirect_jmp*)code; code = (uint8_t*)(ind_jmp + 1); ind_jmp->ardp.Rd = 17; @@ -135,6 +133,20 @@ static inline uint8_t* internal_detour_gen_jmp_indirect(uint8_t* code, const uin return code; } +static inline uint8_t* internal_detour_gen_jmp_immediate(uint8_t* code, uint8_t** inout_code_limit, uint8_t* jmp_val) +{ + *inout_code_limit = *inout_code_limit - 8; + uint8_t* literal = *inout_code_limit; + + *(uint8_t**)literal = jmp_val; + const int32_t delta = (int32_t)(literal - code); + + write_opcode(&code, 0x58000011 | ((delta / 4) << 5)); // LDR X17,[PC+n] + write_opcode(&code, 0xd61f0000 | (17 << 5)); // BR X17 + + return code; +} + static inline uint8_t* internal_detour_gen_brk(uint8_t* code, const uint8_t* limit) { while (code < limit) { @@ -143,4 +155,81 @@ static inline uint8_t* internal_detour_gen_brk(uint8_t* code, const uint8_t* lim return code; } +static inline void internal_detour_find_jmp_bounds(uint8_t* code, detour_trampoline** out_lower, detour_trampoline** out_upper) +{ + // The encoding used by detour_gen_jmp_indirect actually enables a + // displacement of +/- 4GiB. In the future, this could be changed to + // reflect that. For now, just reuse the x86 logic which is plenty. + + const uintptr_t lo = internal_detour_2gb_below((uintptr_t)code); + const uintptr_t hi = internal_detour_2gb_above((uintptr_t)code); + DETOUR_TRACE(("[%p..%p..%p]\n", (void*)lo, (void*)code, (void*)hi)); + + *out_lower = (detour_trampoline*)lo; + *out_upper = (detour_trampoline*)hi; +} + +static inline bool internal_detour_is_code_os_patched(const uint8_t* code) +{ + // Identify whether the provided code pointer is a OS patch jump. + // We can do this by checking if a branch (b ) is present, and if so, + // it must be jumping to an HPAT page containing ldr [PC+PAGE_SIZE-4], br . + const uint32_t opcode = fetch_opcode(code); + + if ((opcode & 0xfc000000) != 0x14000000) { + return false; + } + // The branch must be jumping forward if it's going into the HPAT. + // Check that the sign bit is cleared. + if ((opcode & 0x2000000) != 0) { + return false; + } + const uint32_t delta = (uint32_t)((opcode & 0x1FFFFFF) * 4); + const uint8_t* branch_target = code + delta; + + // Now inspect the opcodes of the code we jumped to in order to determine if it's HPAT. + const uint32_t hpat_opcode1 = fetch_opcode(branch_target); + const uint32_t hpat_opcode2 = fetch_opcode(branch_target + 4); + + if (hpat_opcode1 != 0x58008010) { // ldr [PC+PAGE_SIZE] + return false; + } + if (hpat_opcode2 != 0xd61f0200) { // br + return false; + } + return true; +} + +static inline bool internal_detour_does_code_end_function(const uint8_t* code) +{ + const uint32_t opcode = fetch_opcode(code); + // When the OS has patched a function entry point, it will incorrectly + // appear as though the function is just a single branch instruction. + if (internal_detour_is_code_os_patched(code)) { + return false; + } + if ((opcode & 0xffbffc1f) == 0xd61f0000 || // ret/br + (opcode & 0xfc000000) == 0x14000000) { // b + return true; + } + return false; +} + +static inline uint32_t internal_detour_is_code_filler(const uint8_t* code) +{ + if (*(uint32_t *)code == 0xd503201f) { // nop. + return 4; + } + if (*(uint32_t *)code == 0x00000000) { // zero-filled padding. + return 4; + } + return 0; +} + +static inline uint8_t* internal_detour_skip_jmp(uint8_t* code) +{ + // nothing special implemented + return code; +} + #endif //MACH_DETOURS_ARM64_H \ No newline at end of file diff --git a/src/detours_internal.h b/src/detours_internal.h index 999e048..cfbb971 100644 --- a/src/detours_internal.h +++ b/src/detours_internal.h @@ -5,8 +5,11 @@ #ifndef MACH_DETOURS_INTERNAL_H #define MACH_DETOURS_INTERNAL_H -#ifndef DETOUR_TRACE #if DETOUR_DEBUG + +#include +#include + #define DETOUR_TRACE(x) printf x #define DETOUR_BREAK() raise(SIGTRAP) #define DETOUR_CHECK(x) \ @@ -16,14 +19,15 @@ DETOUR_BREAK(); \ } \ } + #else -#include -#include + #define DETOUR_TRACE(x) #define DETOUR_BREAK() #define DETOUR_CHECK(x) (x) + #endif -#endif + #ifndef ARRAYSIZE #define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0])) @@ -33,4 +37,29 @@ #define UNUSED_VARIABLE(x) (void)(x) #endif + +typedef struct detour_align +{ + uint8_t offset_target; + uint8_t offset_trampoline; +} detour_align; + +// Region reserved for system DLLs, which cannot be used for trampolines. +static void* s_system_region_lower_bound = (void*)(uintptr_t)0x70000000; +static void* s_system_region_upper_bound = (void*)(uintptr_t)0x80000000; + +static inline uintptr_t internal_detour_2gb_below(const uintptr_t address) +{ + return (address > 0x7ff80000) ? address - 0x7ff80000 : 0x80000; +} + +static inline uintptr_t internal_detour_2gb_above(uintptr_t address) +{ +#if defined(DETOURS_64BIT) + return (address < 0xffffffff80000000) ? address + 0x7ff80000 : 0xfffffffffff80000; +#else + return (address < 0x80000000) ? address + 0x7ff80000 : 0xfff80000; +#endif +} + #endif //MACH_DETOURS_INTERNAL_H \ No newline at end of file diff --git a/src/mach_detours.c b/src/mach_detours.c index cf848e9..0ca53aa 100644 --- a/src/mach_detours.c +++ b/src/mach_detours.c @@ -3,7 +3,17 @@ #include "mach_detours.h" +#define DETOUR_DEBUG 1 + +#ifdef __arm64__ +#define DETOURS_ARM64 +#define DETOURS_64BIT +#else +#error Unsupported architecture (arm64) +#endif + #include "detours_internal.h" + #include "arm64/detours_arm64.h" #include @@ -14,12 +24,6 @@ #include #include -#ifdef __arm64__ -#define DETOURS_ARM64 -#else -#error Unsupported architecture (arm64) -#endif - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Trampoline Memory Management @@ -64,6 +68,190 @@ static void internal_detour_runnable_trampoline_regions() } } +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) { + raise(SIGTRAP); + 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); @@ -174,7 +362,7 @@ 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; +static detour_func_t* s_pending_error_pointer = nullptr; mach_error_t detour_transaction_begin() { @@ -425,11 +613,13 @@ mach_error_t detour_manage_thread(thread_t thread) return err_none; } - detour_pending_thread* new_pending_thread = malloc(sizeof(detour_pending_thread)); + 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(); @@ -448,3 +638,262 @@ mach_error_t detour_manage_thread(thread_t thread) return err_none; } + +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. + 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; + int32_t extra_len = 0; + + DETOUR_TRACE((" copy instruction src=%p, dest=%p\n", src, trampoline_code)); + src = (uint8_t*)internal_detour_copy_instruction(trampoline_code, (void**)&trampoline_code_limit, src, nullptr, &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; +}