#include #include #include #include const uint32_t magic64 = 0xfeedfacf; const uint32_t magic32 = 0xfeedface; struct ProgramVars { void* mh; // mach_header or mach_header64 int* NXArgcPtr; const char*** NXArgvPtr; const char*** environPtr; const char** __prognamePtr; }; extern "C" uint32_t dyld_get_sdk_version(const mach_header* mh); void decode_uleb128(char*& addr, uint32_t* ret) { uint32_t result = 0; int shift = 0; while (1) { unsigned char byte = *(unsigned char*)(addr); addr++; result |= (byte & 0x7f) << shift; shift += 7; if (!(byte & 0x80)) break; } *ret = result; } void* find_header(void* _func) { // Approach 1: (not stable) // we assume that text section is small enough to fit on 1 page // so the header should stay at the top of the page due to allocation logic // the slice/slide is random but always align 0x1000 so we test a few values // to see if the magic value is found // // Guaranteed to stop, but search range is small // const uint64_t page_size = 0x4000; // uint64_t func = (uint64_t)_func; // uint64_t potential_head = func + (0x4000 - (func % page_size)); // void* head = 0; // for (uint64_t i = 0x1000; i < 0xf000; i+=0x1000) { // uint32_t* x = (uint32_t*)(potential_head - i); // if (*x == magic64 || *x == magic32) { // head = (void*)x; // break; // } // } // return head; // Approach 2: (more stable) // We know that the header is 0x1000 aligned, // just loop until the magic value is found // Using while loop so ¯\_(ツ)_/¯ const uint64_t page_size = 0x1000; uint64_t func = (uint64_t)_func; uint64_t potential_head = func + (0x1000 - (func % page_size)); void* head = 0; uint32_t* x = (uint32_t*)(potential_head); while (*x != magic64 && *x != magic32) { x -= 0x1000/4; } return (void*)x; } void print_macho_summary(const void* header) { const uint32_t magic = *(uint32_t*)header; char* ptr = (char*)header; if (magic == magic64) { ptr += 0x20; } else { ptr += 0x20 - 0x4; } const uint32_t ncmds = *((uint32_t*)header + 4); printf("parsing macho at %p\n", header); printf("ncmds %x\n", ncmds); for (int i = 0; i < ncmds; i++) { const uint32_t cmd = *((uint32_t*)ptr + 0); const uint32_t cmdsize = *((uint32_t*)ptr + 1); printf(" cmd %x %x\n", cmd, cmdsize); if (cmd == LC_DYLD_EXPORTS_TRIE) { const uint32_t offset = *((uint32_t*)ptr + 2); const uint32_t size = *((uint32_t*)ptr + 3); printf(" export trie: offset=0x%x size=0x%x\n", offset, size); } if (cmd == LC_SEGMENT_64) { char* name = (char*)((uint64_t*)ptr + 1); uint64_t vmaddr = *((uint64_t*)ptr + 3); uint64_t vmsize = *((uint64_t*)ptr + 4); uint64_t fileoffset = *((uint64_t*)ptr + 5); uint64_t filesize = *((uint64_t*)ptr + 6); if (strcmp(name, "__TEXT") == 0) { uint64_t slide = (uint64_t)header - vmaddr; printf(" --- slide=0x%llx ---\n", slide); } printf(" Segment %s\n", name); printf(" vmaddr=0x%llx fileoffset=0x%llx\n", vmaddr, fileoffset); printf(" vmsize=0x%llx filesize=0x%llx\n", vmsize, filesize); } ptr += cmdsize; } } void* get_export_trie(const void* header, uint32_t& size) { const uint32_t magic = *(uint32_t*)header; char* ptr = (char*)header; if (magic == magic64) { ptr += 0x20; } else { ptr += 0x20 - 0x4; } uint64_t slice = 0; uint64_t linkedit_vmaddr = 0; uint64_t linkedit_fileoffset = 0; const uint32_t ncmds = *((uint32_t*)header + 4); for (int i = 0; i < ncmds; i++) { const uint32_t cmd = *((uint32_t*)ptr + 0); const uint32_t cmdsize = *((uint32_t*)ptr + 1); if (cmd == LC_DYLD_EXPORTS_TRIE) { const uint32_t offset = *((uint32_t*)ptr + 2); size = *((uint32_t*)ptr + 3); uint64_t offset_in_linkedit = (uint64_t)offset - linkedit_fileoffset; return (void*)(linkedit_vmaddr + slice + offset_in_linkedit); } if (cmd == LC_SEGMENT_64) { char* name = (char*)((uint64_t*)ptr + 1); uint64_t vmaddr = *((uint64_t*)ptr + 3); uint64_t fileoffset = *((uint64_t*)ptr + 5); if (strcmp(name, "__TEXT") == 0) { slice = (uint64_t)header - vmaddr; } else if (strcmp(name, "__LINKEDIT") == 0) { linkedit_vmaddr = vmaddr; linkedit_fileoffset = fileoffset; } } ptr += cmdsize; } return 0; } uint32_t should_follow_symbol(char*& buffer, char*& _find) { // printf("follow check %s has prefix: %s\n", _find, buffer); char* find = _find; char is_prefix = true; while (1) { int find_end = *find == 0; int buffer_end = *buffer == 0; int check = *buffer == *find; // printf("check is %x == %x\n", *buffer, *find); if (buffer_end) { // we must always run to the end of buffer, marked 0x00 buffer++; break; } if (find_end) { // symbol to find is shorter than current buffer string // but we still need to run to the end of buffer // so just set not prefix is_prefix = false; } if (!check) { is_prefix = false; } buffer++; find++; } // only move forward if is_prefix if (is_prefix) { _find = find; // printf("prefix is found\n"); } return is_prefix; } void* find_in_export_trie(const void* header, void* trie, char* symbol) { uint32_t func = 0; char* ptr = (char*)trie; char* find = symbol; while (1) { // terminal node will have data uint32_t data_count = 0; decode_uleb128(ptr, &data_count); if (data_count != 0) { // printf("reached terminal node\n"); break; } char num_child = ptr[0]; ptr++; // printf("num child %d\n", num_child); int still_following = 0; for (char i = 0; i < num_child; i++) { still_following = should_follow_symbol(ptr, find); uint32_t follow_offset; decode_uleb128(ptr, &follow_offset); if (still_following) { ptr = (char*)trie + follow_offset; break; } } if (!still_following) { // symbol not found return 0; } } char count = *(ptr - 1); ptr++; // flags // uleb128 offset decode_uleb128(ptr, &func); return (void*)((char*)header + func); } int hook_printf (const char * format, ... ) { va_list args; va_start(args, format); printf("HOOKED BEGIN LOL\n"); int status = printf(format, args); printf("HOOKED END LOL\n"); va_end(args); return status; } __attribute__((constructor)) static void bruh(int argc, const char* const argv[], const char* const envp[], const char* const apple[], const struct ProgramVars* vars) { // ProgramVars contains pointer to main executable (mapped) file const void* main = (int*)(vars->mh); // Find our lib (mapped) file const void* thislib = find_header((void*)bruh); // Find dyld lib (mapped) file using a no-sus function const void* libdyld = find_header((void*)dyld_get_sdk_version); const void* libc = find_header((void*)printf); // From libdyld header, we can list exports table // to find all function we want to use // // This way there is no leakage of functions we use to do our trick // mostly to hide // - _dyld_image_count // - _dyld_get_image_name // - _dyld_get_image_header // - _dyld_get_image_vmaddr_slide // The above functions are crucial to find all libraries loaded // From which we will traverse the exports table to replace // _got and _la_symbol_pointer data // Our lib can hide more details too // We can resolve all functions we use // before resolving the main executable imports // // This will make our lib use only dyld_get_sdk_version // For the main executable, imports are empty due to manual resolve printf("executable header at %p\n", main); printf("lib header at %p\n", thislib); printf("libdyld header at %p\n", libdyld); for (int i = 0; i < _dyld_image_count(); i++) { void* header = (void*)_dyld_get_image_header(i); char* name = (char*)_dyld_get_image_name(i); int offset = _dyld_get_image_vmaddr_slide(i); printf("%p 0x%x name=%s\n", header, offset, name); } uint32_t trie_size; void* thislib_export_trie = get_export_trie(thislib, trie_size); void* libdyld_export_trie = get_export_trie(libdyld, trie_size); void* libc_export_trie = get_export_trie(libc, trie_size); // printf("export this lib address %p\n", thislib_export_trie); // for (int i = 0; i < 136; i++) { // if (i % 0x10 == 0) printf("\n"); // printf("%02x ", *((unsigned char*)thislib_export_trie + i)); // } // printf("\n"); // printf("export dyld lib address %llx\n", (uint64_t)libdyld_export_trie); // for (int i = 0; i < 0x11e0; i++) { // if (i % 0x10 == 0) printf("\n"); // printf("%02x ", *((unsigned char*)libdyld_export_trie + i)); // } // printf("\n"); // printf("export system lib address %llx\n", (uint64_t)system_export_trie); // for (int i = 0; i < 0x10f30; i++) { // if (i % 0x10 == 0) printf("\n"); // printf("%02x ", *((unsigned char*)system_export_trie + i)); // } // printf("\n"); // FILE *write_ptr = fopen("../tmp/libc_export_trie.bin","wb"); // fwrite(system_export_trie, trie_size, 1, write_ptr); struct test_find_export { const char* name; const void* lib; void* trie; void* original; }; struct test_find_export find_export_testcases[] = { {"__Z11find_headerPv", thislib, thislib_export_trie, (void*)find_header}, {"__dyld_get_image_name", libdyld, libdyld_export_trie, (void*)_dyld_get_image_name}, {"__dyld_image_count", libdyld, libdyld_export_trie, (void*)_dyld_image_count}, {"_printf",libc, libc_export_trie, (void*)printf}, }; for (int i = 0; i < 4; i++) { struct test_find_export test = find_export_testcases[i]; void* found = find_in_export_trie(test.lib, test.trie, (char*)test.name); printf("%s: Found=%p | Expect=%p\n", test.name, found, test.original); } // legacy symbol resolve // fix got and la_symbol_ptr // modern symbol resolve // fix got uint64_t* got = (uint64_t*)((char*)main + 0x4000); printf("BEFORE symbol bind code is %llx\n", *got); vm_protect(mach_task_self(), (uint64_t)got, 0x4000, 0, VM_PROT_READ | VM_PROT_WRITE); // fix got table // *got = (uint64_t)find_in_export_trie(libc, libc_export_trie, "_printf"); *got = (uint64_t)hook_printf; // unsigned char* opcodes = (unsigned char*)got + 0x20; // unsigned char original[] = { // 0x73, 0x00, 0x13, 0x40, 0x5f, 0x70, 0x72, 0x69, // 0x6e, 0x74, 0x66, 0x00, 0x90, 0x00, 0x00, 0x00 // }; // for (int i = 0; i < 0x10; i++) { // printf("CHANGE AT %p %x => %x\n", opcodes+i, opcodes[i], original[i]); // // opcodes[i] = original[i]; // } vm_protect(mach_task_self(), (uint64_t)got, 0x4000, 0, VM_PROT_READ); printf("AFTER symbol bind code is %llx\n", *got); printf("symbol should bind to %p\n", printf); }