From e3453ae1272d6bf3d5dce1563b4da5a993256f83 Mon Sep 17 00:00:00 2001 From: nganhkhoa Date: Wed, 31 May 2023 16:17:45 +0700 Subject: [PATCH] add research folder --- research/custom_loader/.gitignore | 1 + research/custom_loader/a.c | 4 + research/custom_loader/b.c | 361 ++++++++++++++++++++++++ research/custom_loader/build.sh | 8 + research/scripts/bind.c | 24 ++ research/scripts/dyld_export_trie.bin | Bin 0 -> 4576 bytes research/scripts/go.mod | 7 + research/scripts/go.sum | 4 + research/scripts/libc_export_trie.bin | Bin 0 -> 22192 bytes research/scripts/parse_export_trie.go | 16 ++ research/scripts/system_exoprt_trie.bin | Bin 0 -> 520 bytes research/scripts/system_export_trie.bin | Bin 0 -> 520 bytes 12 files changed, 425 insertions(+) create mode 100644 research/custom_loader/.gitignore create mode 100644 research/custom_loader/a.c create mode 100644 research/custom_loader/b.c create mode 100755 research/custom_loader/build.sh create mode 100644 research/scripts/bind.c create mode 100644 research/scripts/dyld_export_trie.bin create mode 100644 research/scripts/go.mod create mode 100644 research/scripts/go.sum create mode 100644 research/scripts/libc_export_trie.bin create mode 100644 research/scripts/parse_export_trie.go create mode 100644 research/scripts/system_exoprt_trie.bin create mode 100644 research/scripts/system_export_trie.bin diff --git a/research/custom_loader/.gitignore b/research/custom_loader/.gitignore new file mode 100644 index 0000000..89f9ac0 --- /dev/null +++ b/research/custom_loader/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/research/custom_loader/a.c b/research/custom_loader/a.c new file mode 100644 index 0000000..c2685a1 --- /dev/null +++ b/research/custom_loader/a.c @@ -0,0 +1,4 @@ +#include +int main() { + printf("Hello World\n"); +} diff --git a/research/custom_loader/b.c b/research/custom_loader/b.c new file mode 100644 index 0000000..60f51ca --- /dev/null +++ b/research/custom_loader/b.c @@ -0,0 +1,361 @@ +#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); +} diff --git a/research/custom_loader/build.sh b/research/custom_loader/build.sh new file mode 100755 index 0000000..e6a6bbe --- /dev/null +++ b/research/custom_loader/build.sh @@ -0,0 +1,8 @@ +set -ex + +VERSION=14 +OUT=./out + +mkdir -p $OUT +clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared b.c +clang++ -mmacosx-version-min=$VERSION -o $OUT/a a.c -L"./out" -lb diff --git a/research/scripts/bind.c b/research/scripts/bind.c new file mode 100644 index 0000000..aa3a0bb --- /dev/null +++ b/research/scripts/bind.c @@ -0,0 +1,24 @@ +#include +#include + +struct dyld_chained_ptr_64_bind +{ + uint64_t ordinal : 24, + addend : 8, // 0 thru 255 + reserved : 19, // all zeros + next : 12, // 4-byte stride + bind : 1; // == 1 +}; + +int main() { + // 0 = 0x8010000000000000 = 9227875636482146000 + uint64_t x = 0x8000000000000010; + uint8_t y[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80}; + struct dyld_chained_ptr_64_bind* bind = y; + // printf("x? %llx\n", x); + // printf("y? %llx\n", *(uint64_t*)y); + printf("bind? %d\n", bind->bind); + printf("next? %d\n", bind->next); + printf("ordinal? %d\n", bind->ordinal); + printf("addend? %d\n", bind->addend); +} diff --git a/research/scripts/dyld_export_trie.bin b/research/scripts/dyld_export_trie.bin new file mode 100644 index 0000000000000000000000000000000000000000..ab007ff6d3a5a2890c0fd0da588152d40a5caf1b GIT binary patch literal 4576 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