diff options
Diffstat (limited to 'lib/darwin/inject.c')
-rw-r--r-- | lib/darwin/inject.c | 254 |
1 files changed, 238 insertions, 16 deletions
diff --git a/lib/darwin/inject.c b/lib/darwin/inject.c index a73c9aa..b314df6 100644 --- a/lib/darwin/inject.c +++ b/lib/darwin/inject.c @@ -1,6 +1,7 @@ #ifdef __APPLE__ #include "substitute.h" #include "substitute-internal.h" +#include "darwin/read.h" #include <mach/mach.h> #include <sys/param.h> #include <stdint.h> @@ -8,6 +9,7 @@ #include <stdbool.h> kern_return_t mach_vm_read_overwrite(vm_map_t, mach_vm_address_t, mach_vm_size_t, mach_vm_address_t, mach_vm_size_t *); +kern_return_t mach_vm_remap(vm_map_t, mach_vm_address_t *, mach_vm_size_t, mach_vm_offset_t, int, vm_map_t, mach_vm_address_t, boolean_t, vm_prot_t *, vm_prot_t *, vm_inherit_t); #define DEFINE_STRUCTS @@ -44,8 +46,7 @@ struct dyld_all_image_infos_64 { dyld_image_infos_fields(uint64_t) }; -static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p, - uint64_t *libpthread_p, char **error) { +static int find_foreign_images(mach_port_t task, uint64_t *libdyld_p, uint64_t *libpthread_p, char **error) { struct task_dyld_info tdi; mach_msg_type_number_t cnt = TASK_DYLD_INFO_COUNT; @@ -64,7 +65,7 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p, char all_image_infos_buf[1024]; cnt = tdi.all_image_info_size; - mach_vm_size_t size = tdi.all_image_info_size; + mach_vm_size_t size; kr = mach_vm_read_overwrite(task, tdi.all_image_info_addr, tdi.all_image_info_size, (mach_vm_address_t) all_image_infos_buf, &size); if (kr || size != tdi.all_image_info_size) { @@ -84,8 +85,6 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p, return SUBSTITUTE_ERR_MISC; } - *dyld_addr_p = FIELD(dyldImageLoadAddress); - uint64_t info_array_addr = FIELD(infoArray); uint32_t info_array_count = FIELD(infoArrayCount); size_t info_array_elm_size = (is64 ? sizeof(uint64_t) : sizeof(uint32_t)) * 3; @@ -130,26 +129,29 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p, char path_buf[MAXPATHLEN+1]; size_t toread = MIN(MAXPATHLEN, -file_path & 0xfff); path_buf[toread] = '\0'; - kr = mach_vm_read_overwrite(task, (mach_vm_address_t) path_buf, toread, - load_address, &size); + kr = mach_vm_read_overwrite(task, file_path, toread, + (mach_vm_address_t) path_buf, &size); if (kr) { + printf("kr=%d <%p %p>\n", kr, (void *) file_path, path_buf); continue; } if (strlen(path_buf) == toread && toread < MAXPATHLEN) { /* get the rest... */ - kr = mach_vm_read_overwrite(task, (mach_vm_address_t) path_buf + toread, - MAXPATHLEN - toread, load_address + toread, - &size); + kr = mach_vm_read_overwrite(task, file_path + toread, MAXPATHLEN - toread, + (mach_vm_address_t) path_buf + toread, &size); if (kr) { continue; } path_buf[MAXPATHLEN] = '\0'; } - if (!strcmp(path_buf, "/usr/lib/libpthread.dylib")) { + if (!strcmp(path_buf, "/usr/lib/system/libdyld.dylib")) + *libdyld_p = load_address; + else if (!strcmp(path_buf, "/usr/lib/system/libpthread.dylib")) *libpthread_p = load_address; - return 0; - } + + if (*libdyld_p && *libpthread_p) + return SUBSTITUTE_OK; info_array_ptr += info_array_elm_size; } @@ -158,6 +160,196 @@ static int find_libs_in_task(mach_port_t task, uint64_t *dyld_addr_p, return SUBSTITUTE_ERR_MISC; } +static int get_foreign_image_export(mach_port_t task, uint64_t hdr_addr, + void **linkedit_p, size_t *linkedit_size_p, + void **export_p, size_t *export_size_p, + char **error) { + mach_vm_offset_t hdr_buf; + mach_vm_size_t hdr_buf_size; + int ret; + if (hdr_addr & (PAGE_SIZE - 1)) { + asprintf(error, "unaligned mach_header"); + return SUBSTITUTE_ERR_MISC; + } + + vm_prot_t cur, max; + hdr_buf_size = PAGE_SIZE; + kern_return_t kr = mach_vm_remap(mach_task_self(), &hdr_buf, hdr_buf_size, 0, + VM_FLAGS_ANYWHERE, task, hdr_addr, /*copy*/ true, + &cur, &max, VM_INHERIT_NONE); + if (kr) { + asprintf(error, "mach_vm_remap(libdyld header): kr=%d", kr); + return SUBSTITUTE_ERR_MISC; + } + + struct mach_header *mh = (void *) hdr_buf; + if (mh->magic != MH_MAGIC && mh->magic != MH_MAGIC_64) { + asprintf(error, "bad magic in libdyld mach_header"); + ret = SUBSTITUTE_ERR_MISC; + goto fail; + } + + size_t mh_size = mh->magic == MH_MAGIC_64 ? sizeof(struct mach_header_64) + : sizeof(struct mach_header); + if (mh->sizeofcmds < mh_size || mh->sizeofcmds > 128*1024) + goto badmach; + + size_t total_size = mh_size + mh->sizeofcmds; + if (total_size > hdr_buf_size) { + vm_deallocate(mach_task_self(), (vm_offset_t) hdr_buf, (vm_size_t) hdr_buf_size); + hdr_buf_size = total_size; + kr = mach_vm_remap(mach_task_self(), &hdr_buf, hdr_buf_size, 0, + VM_FLAGS_ANYWHERE, task, hdr_addr, /*copy*/ true, + &cur, &max, VM_INHERIT_NONE); + if (kr) { + asprintf(error, "mach_vm_remap(libdyld header) #2: kr=%d", kr); + ret = SUBSTITUTE_ERR_MISC; + goto fail; + } + mh = (void *) hdr_buf; + } + + struct load_command *lc = (void *) mh + mh_size; + uint32_t export_off = 0, export_size = 0; + uint64_t slide = 0; + for (uint32_t i = 0; i < mh->ncmds; i++, lc = (void *) lc + lc->cmdsize) { + size_t remaining = total_size - ((void *) lc - (void *) mh); + if (remaining < sizeof(*lc) || remaining < lc->cmdsize) + goto badmach; + if (lc->cmd == LC_DYLD_INFO || lc->cmd == LC_DYLD_INFO_ONLY) { + struct dyld_info_command *dc = (void *) lc; + if (lc->cmdsize < sizeof(*dc)) + goto badmach; + export_off = dc->export_off; + export_size = dc->export_size; + } else if (lc->cmd == LC_SEGMENT) { + struct segment_command *sc = (void *) lc; + if (lc->cmdsize < sizeof(*sc)) + goto badmach; + if (sc->fileoff == 0) + slide = hdr_addr - sc->vmaddr; + } else if (lc->cmd == LC_SEGMENT_64) { + struct segment_command_64 *sc = (void *) lc; + if (lc->cmdsize < sizeof(*sc)) + goto badmach; + if (sc->fileoff == 0) + slide = hdr_addr - sc->vmaddr; + } + } + + if (export_off == 0) { + asprintf(error, "no LC_DYLD_INFO in libdyld header"); + ret = SUBSTITUTE_ERR_MISC; + goto fail; + } + lc = (void *) mh + mh_size; + + + uint64_t export_segoff, vmaddr, fileoff, filesize; + for (uint32_t i = 0; i < mh->ncmds; i++, lc = (void *) lc + lc->cmdsize) { + if (lc->cmd == LC_SEGMENT) { + struct segment_command *sc = (void *) lc; + vmaddr = sc->vmaddr; + fileoff = sc->fileoff; + filesize = sc->filesize; + } else if (lc->cmd == LC_SEGMENT_64) { + struct segment_command_64 *sc = (void *) lc; + vmaddr = sc->vmaddr; + fileoff = sc->fileoff; + filesize = sc->filesize; + } else { + continue; + } + export_segoff = (uint64_t) export_off - fileoff; + if (export_segoff < filesize) { + if (export_size > filesize - export_segoff) + goto badmach; + break; + } + } + + uint64_t linkedit_addr = vmaddr + slide; + mach_vm_address_t linkedit_buf; + kr = mach_vm_remap(mach_task_self(), &linkedit_buf, filesize, 0, + VM_FLAGS_ANYWHERE, task, linkedit_addr, /*copy*/ true, + &cur, &max, VM_INHERIT_NONE); + if (kr) { + asprintf(error, "mach_vm_remap(libdyld linkedit): kr=%d", kr); + ret = SUBSTITUTE_ERR_MISC; + goto fail; + } + + *linkedit_p = (void *) linkedit_buf; + *linkedit_size_p = (size_t) filesize; + *export_p = (void *) linkedit_buf + export_segoff; + *export_size_p = export_size; + + ret = SUBSTITUTE_OK; + goto fail; + +badmach: + asprintf(error, "bad Mach-O data in libdyld header"); + ret = SUBSTITUTE_ERR_MISC; + goto fail; +fail: + vm_deallocate(mach_task_self(), (vm_offset_t) hdr_buf, (vm_size_t) hdr_buf_size); + return ret; +} + +static bool find_export_symbol(void *export, size_t export_size, const char *name, + uint64_t hdr_addr, uint64_t *sym_addr_p) { + void *end = export + export_size; + void *ptr = export; + while (1) { + /* skip this symbol data */ + uint64_t size; + if (!read_leb128(&ptr, end, false, &size) || + size > (uint64_t) (end - ptr)) + return false; + ptr += size; + if (ptr == end) + return false; + uint8_t i, nedges = *(uint8_t *) ptr; + ptr++; + for (i = 0; i < nedges; i++) { + char *prefix; + if (!read_cstring(&ptr, end, &prefix)) + return false; + size_t prefix_len = (char *) ptr - prefix - 1; + uint64_t next_offset; + if (!read_leb128(&ptr, end, false, &next_offset)) + return false; + if (!strncmp(name, prefix, prefix_len)) { + if (next_offset > export_size) + return false; + ptr = export + next_offset; + name += prefix_len; + if (*name == '\0') + goto got_symbol; + break; + } + } + if (i == nedges) { + /* not found */ + return false; + } + } +got_symbol:; + uint64_t size, flags, hdr_off; + if (!read_leb128(&ptr, end, false, &size)) + return false; + if (!read_leb128(&ptr, end, false, &flags)) + return false; + if (flags & (EXPORT_SYMBOL_FLAGS_REEXPORT | EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER)) { + /* don't bother to support for now */ + return false; + } + if (!read_leb128(&ptr, end, false, &hdr_off)) + return false; + *sym_addr_p = hdr_addr + hdr_off; + return true; +} + EXPORT int substitute_dlopen_in_pid(int pid, const char *filename, int options, char **error) { mach_port_t task; @@ -169,12 +361,42 @@ int substitute_dlopen_in_pid(int pid, const char *filename, int options, char ** return SUBSTITUTE_ERR_TASK_FOR_PID; } - uint64_t dyld_addr, libpthread_addr; - if ((ret = find_libs_in_task(task, &dyld_addr, &libpthread_addr, error))) + uint64_t libdyld_addr, libpthread_addr; + if ((ret = find_foreign_images(task, &libdyld_addr, &libpthread_addr, error))) goto fail; + + struct { + uint64_t addr; + const char *symname; + uint64_t symaddr; + } libs[2] = { + {libdyld_addr, "_dlopen", 0}, + {libpthread_addr, "_pthread_create", 0} + }; + + for (int i = 0; i < 2; i++) { + void *linkedit, *export; + size_t linkedit_size, export_size; + if ((ret = get_foreign_image_export(task, libs[i].addr, + &linkedit, &linkedit_size, + &export, &export_size, + error))) + goto fail; + printf("%p\n", (void *) libdyld_addr); + bool fesr = find_export_symbol(export, export_size, libs[i].symname, + libs[i].addr, &libs[i].symaddr); + vm_deallocate(mach_task_self(), (vm_offset_t) linkedit, (vm_size_t) linkedit_size); + if (!fesr) { + asprintf(error, "couldn't find _dlopen in libdyld"); + ret = SUBSTITUTE_ERR_MISC; + goto fail; + } + } + printf("%p\n", (void *) libs[0].symaddr); + printf("%p\n", (void *) libs[0].symaddr); + (void) filename; (void) options; - printf("%p %p\n", (void *) dyld_addr, (void *) libpthread_addr); ret = 0; fail: |