Created
January 20, 2026 17:46
-
-
Save ChiChou/22d48d5353191a0eab51988d6eece5d9 to your computer and use it in GitHub Desktop.
Load private frameworks without crash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #import <Foundation/Foundation.h> | |
| #import <libkern/OSCacheControl.h> | |
| #import <pthread/pthread.h> | |
| #import <signal.h> | |
| #import <execinfo.h> | |
| #import <mach/exception.h> | |
| #import <mach/exception_types.h> | |
| #import <mach/mach.h> | |
| #import <mach/mach_vm.h> | |
| #import <mach/port.h> | |
| #import <mach/thread_status.h> | |
| #import <mach/vm_prot.h> | |
| static uintptr_t load_images_patch = 0; | |
| static uintptr_t ctor_patch = 0; | |
| @interface Trap : NSObject | |
| + (void)load; | |
| @end | |
| uintptr_t get_caller(void) { | |
| void *addr[3]; | |
| int nframes = backtrace(addr, sizeof(addr) / sizeof(*addr)); | |
| if (nframes <= 2) | |
| return 0; | |
| uint8_t *caller = addr[2]; | |
| #if defined(__arm64__) | |
| uint32_t *blraaz = (uint32_t *)caller - 1; | |
| return (uintptr_t)blraaz; | |
| #elif defined(__x86_64__) | |
| uint8_t *call_r15 = caller - 3; | |
| if (call_r15[0] == 0x41 && call_r15[1] == 0xFF && call_r15[2] == 0xD7) | |
| return (uintptr_t)call_r15; | |
| uint8_t *call_rbx = caller - 2; | |
| if (call_rbx[0] == 0xFF && call_rbx[1] == 0xD3) | |
| return (uintptr_t)call_rbx; | |
| #else | |
| #error "Architecture not supported" | |
| #endif | |
| return 0; | |
| } | |
| __attribute__((constructor)) static void auto_load(void) { | |
| ctor_patch = get_caller(); | |
| } | |
| @implementation Trap | |
| + (void)load { | |
| load_images_patch = get_caller(); | |
| } | |
| @end | |
| kern_return_t bp(uintptr_t address) { | |
| kern_return_t kr; | |
| mach_port_t thread = mach_thread_self(); | |
| #if defined(__arm64__) | |
| arm_debug_state64_t s; | |
| mach_msg_type_number_t count = ARM_DEBUG_STATE64_COUNT; | |
| kr = thread_get_state(thread, ARM_DEBUG_STATE64, (thread_state_t)&s, &count); | |
| if (kr != KERN_SUCCESS) { | |
| mach_port_deallocate(mach_task_self(), thread); | |
| return kr; | |
| } | |
| s.__bvr[0] = (uint64_t)address; | |
| s.__bcr[0] = 0x1e5; | |
| kr = thread_set_state(thread, ARM_DEBUG_STATE64, (thread_state_t)&s, count); | |
| #elif defined(__x86_64__) | |
| x86_debug_state64_t s; | |
| mach_msg_type_number_t count = x86_DEBUG_STATE64_COUNT; | |
| kr = thread_get_state(thread, x86_DEBUG_STATE64, (thread_state_t)&s, &count); | |
| if (kr != KERN_SUCCESS) { | |
| mach_port_deallocate(mach_task_self(), thread); | |
| return kr; | |
| } | |
| s.__dr0 = (uint64_t)address; | |
| s.__dr7 &= ~(0xF0003); | |
| s.__dr7 |= 0x1; | |
| kr = thread_set_state(thread, x86_DEBUG_STATE64, (thread_state_t)&s, count); | |
| #else | |
| #error "Architecture not supported" | |
| #endif | |
| mach_port_deallocate(mach_task_self(), thread); | |
| return kr; | |
| } | |
| kern_return_t patch_code(uint64_t target_addr, const void *code, | |
| size_t length) { | |
| kern_return_t kr; | |
| mach_port_t task = mach_task_self(); | |
| vm_size_t page_size = sysconf(_SC_PAGESIZE); | |
| mach_vm_address_t region_start = | |
| (mach_vm_address_t)target_addr & ~(page_size - 1); | |
| mach_vm_address_t region_end = | |
| (mach_vm_address_t)(target_addr + length + page_size - 1) & | |
| ~(page_size - 1); | |
| vm_size_t region_size = region_end - region_start; | |
| vm_offset_t patch_offset = (vm_offset_t)target_addr - region_start; | |
| mach_vm_address_t temp_buffer = 0; | |
| kr = mach_vm_allocate(task, &temp_buffer, region_size, VM_FLAGS_ANYWHERE); | |
| if (kr != KERN_SUCCESS) | |
| return kr; | |
| memcpy((void *)temp_buffer, (void *)region_start, region_size); | |
| memcpy((void *)(temp_buffer + patch_offset), code, length); | |
| kr = mach_vm_protect(task, temp_buffer, region_size, FALSE, | |
| VM_PROT_READ | VM_PROT_EXECUTE); | |
| if (kr != KERN_SUCCESS) { | |
| mach_vm_deallocate(task, temp_buffer, region_size); | |
| return kr; | |
| } | |
| vm_prot_t cur_prot, max_prot; | |
| kr = mach_vm_remap(task, ®ion_start, region_size, 0, VM_FLAGS_OVERWRITE, | |
| task, temp_buffer, FALSE, &cur_prot, &max_prot, | |
| VM_INHERIT_SHARE); | |
| mach_vm_deallocate(task, temp_buffer, region_size); | |
| sys_icache_invalidate((void *)target_addr, length); | |
| return kr; | |
| } | |
| int main(int argc, const char *argv[]) { | |
| @autoreleasepool { | |
| #if defined(__arm64__) | |
| uint32_t nop = 0xd503201f; | |
| patch_code(load_images_patch, &nop, sizeof(nop)); | |
| patch_code(ctor_patch, &nop, sizeof(nop)); | |
| #elif defined(__x86_64__) | |
| char nops[] = "\x90\x90\x90\x90"; | |
| patch_code(load_images_patch, nops, 3); | |
| patch_code(ctor_patch, nops, 2); | |
| #endif | |
| // todo: load list from dyld_shared_cache | |
| NSString *parent = @"/System/Library/PrivateFrameworks/"; | |
| NSError *err = nil; | |
| NSArray *list = | |
| [[NSFileManager defaultManager] contentsOfDirectoryAtPath:parent | |
| error:&err]; | |
| for (NSString *item in list) { | |
| NSString *full = [parent stringByAppendingPathComponent:item]; | |
| NSBundle *bundle = [NSBundle bundleWithPath:full]; | |
| NSLog(@"load %@", item); | |
| [bundle load]; | |
| } | |
| } | |
| return EXIT_SUCCESS; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment