diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | lib/darwin/find-syms.c | 87 | ||||
-rw-r--r-- | lib/darwin/find-syms.h | 14 | ||||
-rw-r--r-- | lib/darwin/inject.c | 184 | ||||
-rw-r--r-- | lib/darwin/substrate-compat.c | 6 | ||||
-rw-r--r-- | lib/substitute-internal.h | 19 | ||||
-rw-r--r-- | lib/substitute.h | 11 | ||||
-rw-r--r-- | test/test-find-syms.c | 7 |
8 files changed, 290 insertions, 39 deletions
@@ -38,6 +38,7 @@ out/transform-dis.o: $(GENERATED) LIB_OBJS := \ out/darwin/find-syms.o \ + out/darwin/inject.o \ out/darwin/interpose.o \ out/darwin/objc-asm.o \ out/darwin/objc.o \ diff --git a/lib/darwin/find-syms.c b/lib/darwin/find-syms.c index 44adf78..f2261f0 100644 --- a/lib/darwin/find-syms.c +++ b/lib/darwin/find-syms.c @@ -6,6 +6,7 @@ #include "substitute.h" #include "substitute-internal.h" +#include "find-syms.h" extern const struct dyld_all_image_infos *_dyld_get_all_image_infos(); @@ -22,11 +23,23 @@ static void *sym_to_ptr(substitute_sym *sym, intptr_t slide) { return (void *) addr; } -static void find_syms_raw(const void *hdr, intptr_t *slide, const char **names, substitute_sym **syms, size_t count) { - memset(syms, 0, sizeof(*syms) * count); +static bool find_slide(const mach_header_x *mh, intptr_t *slidep) { + uint32_t ncmds = mh->ncmds; + struct load_command *lc = (void *) (mh + 1); + for (uint32_t i = 0; i < ncmds; i++) { + if (lc->cmd == LC_SEGMENT_X) { + segment_command_x *sc = (void *) lc; + if (sc->fileoff == 0) { + *slidep = (uintptr_t) mh - sc->vmaddr; + return true; + } + } + lc = (void *) lc + lc->cmdsize; + } + return false; +} - /* note: no verification at all */ - const mach_header_x *mh = hdr; +bool find_symtab_data(const mach_header_x *mh, struct symtab_data *data) { uint32_t ncmds = mh->ncmds; struct load_command *lc = (void *) (mh + 1); struct symtab_command syc; @@ -37,38 +50,45 @@ static void find_syms_raw(const void *hdr, intptr_t *slide, const char **names, } lc = (void *) lc + lc->cmdsize; } - return; /* no symtab, no symbols */ + return false; /* no symtab, no symbols */ ok: ; - substitute_sym *symtab = NULL; - const char *strtab = NULL; lc = (void *) (mh + 1); for (uint32_t i = 0; i < ncmds; i++) { if (lc->cmd == LC_SEGMENT_X) { segment_command_x *sc = (void *) lc; - if (syc.symoff - sc->fileoff < sc->filesize) - symtab = (void *) sc->vmaddr + syc.symoff - sc->fileoff; - if (syc.stroff - sc->fileoff < sc->filesize) - strtab = (void *) sc->vmaddr + syc.stroff - sc->fileoff; - if (*slide == -1 && sc->fileoff == 0) { - // used only for dyld - *slide = (uintptr_t) hdr - sc->vmaddr; + uint32_t seg_symoff = syc.symoff - sc->fileoff; + uint32_t seg_stroff = syc.stroff - sc->fileoff; + if (seg_symoff < sc->filesize && + syc.nsyms <= (sc->filesize - seg_symoff) / sizeof(substitute_sym) && + seg_stroff < sc->filesize && + syc.strsize <= sc->filesize - seg_stroff) { + data->linkedit_vmaddr = sc->vmaddr; + data->linkedit_symoff = seg_symoff; + data->nsyms = syc.nsyms; + data->linkedit_stroff = seg_stroff; + data->strsize = syc.strsize; + return true; } - if (symtab && strtab) - goto ok2; } lc = (void *) lc + lc->cmdsize; } - return; /* uh... weird */ -ok2: ; - symtab = (void *) symtab + *slide; - strtab = (void *) strtab + *slide; + return false; /* not in any segment? */ +} + +void find_syms_raw(const struct symtab_data *restrict data, void *linkedit, + const char **restrict names, substitute_sym **restrict syms, + size_t count) { + memset(syms, 0, count * sizeof(*syms)); + substitute_sym *symtab = linkedit + data->linkedit_symoff; + const char *strtab = linkedit + data->linkedit_stroff; /* This could be optimized for efficiency with a large number of names... */ - for (uint32_t i = 0; i < syc.nsyms; i++) { + for (uint32_t i = 0; i < data->nsyms; i++) { substitute_sym *sym = &symtab[i]; uint32_t strx = sym->n_un.n_strx; - const char *name = strx == 0 ? "" : strtab + strx; + const char *name = (strx == 0 || strx >= data->strsize) ? "" : strtab + strx; + size_t maxlen = data->strsize - strx; for (size_t j = 0; j < count; j++) { - if (!strcmp(name, names[j])) { + if (!strncmp(name, names[j], maxlen)) { syms[j] = sym; break; } @@ -93,14 +113,18 @@ static void inspect_dyld() { const char *names[2] = { "__ZNK16ImageLoaderMachO8getSlideEv", "__ZNK16ImageLoaderMachO10machHeaderEv" }; substitute_sym *syms[2]; intptr_t dyld_slide = -1; - find_syms_raw(dyld_hdr, &dyld_slide, names, syms, 2); + struct symtab_data symtab_data; + if (!find_slide(dyld_hdr, &dyld_slide) || + !find_symtab_data(dyld_hdr, &symtab_data)) + substitute_panic("couldn't find ImageLoader methods\n"); + void *linkedit = (void *) (symtab_data.linkedit_vmaddr + dyld_slide); + find_syms_raw(&symtab_data, linkedit, names, syms, 2); if (!syms[0] || !syms[1]) substitute_panic("couldn't find ImageLoader methods\n"); ImageLoaderMachO_getSlide = sym_to_ptr(syms[0], dyld_slide); ImageLoaderMachO_machHeader = sym_to_ptr(syms[1], dyld_slide); } -/* 'dlopen_header' keeps the image alive */ EXPORT struct substitute_image *substitute_open_image(const char *filename) { pthread_once(&dyld_inspect_once, inspect_dyld); @@ -129,8 +153,17 @@ void substitute_close_image(struct substitute_image *im) { EXPORT int substitute_find_private_syms(struct substitute_image *im, const char **names, - substitute_sym **syms, size_t nsyms) { - find_syms_raw(im->image_header, &im->slide, names, syms, nsyms); + void **syms, size_t nsyms) { + struct symtab_data symtab_data; + if (!find_symtab_data(im->image_header, &symtab_data)) + return SUBSTITUTE_OK; + void *linkedit = (void *) (symtab_data.linkedit_vmaddr + im->slide); + substitute_sym **ssyms = (void *) syms; + find_syms_raw(&symtab_data, linkedit, names, ssyms, nsyms); + for (size_t i = 0; i < nsyms; i++) { + if (ssyms[i]) + syms[i] = sym_to_ptr(ssyms[i], im->slide); + } return SUBSTITUTE_OK; } diff --git a/lib/darwin/find-syms.h b/lib/darwin/find-syms.h new file mode 100644 index 0000000..c017d2a --- /dev/null +++ b/lib/darwin/find-syms.h @@ -0,0 +1,14 @@ +#pragma once +#include "substitute.h" +#include "substitute-internal.h" + +struct symtab_data { + uint64_t linkedit_vmaddr; + uint32_t linkedit_symoff, nsyms; + uint32_t linkedit_stroff, strsize; +}; + +bool find_symtab_data(const mach_header_x *mh, struct symtab_data *data); +void find_syms_raw(const struct symtab_data *restrict data, void *linkedit, + const char **restrict names, substitute_sym **restrict syms, + size_t count); diff --git a/lib/darwin/inject.c b/lib/darwin/inject.c new file mode 100644 index 0000000..a73c9aa --- /dev/null +++ b/lib/darwin/inject.c @@ -0,0 +1,184 @@ +#ifdef __APPLE__ +#include "substitute.h" +#include "substitute-internal.h" +#include <mach/mach.h> +#include <sys/param.h> +#include <stdint.h> +#include <stdio.h> +#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 *); + +#define DEFINE_STRUCTS + +#define dyld_image_infos_fields(ptr) \ + uint32_t version; \ + uint32_t infoArrayCount; \ + ptr infoArray; \ + ptr notification; \ + bool processDetachedFromSharedRegion; \ + bool libSystemInitialized; \ + ptr dyldImageLoadAddress; \ + ptr jitInfo; \ + ptr dyldVersion; \ + ptr errorMessage; \ + ptr terminationFlags; \ + ptr coreSymbolicationShmPage; \ + ptr systemOrderFlag; \ + ptr uuidArrayCount; \ + ptr uuidArray; \ + ptr dyldAllImageInfosAddress; \ + ptr initialImageCount; \ + ptr errorKind; \ + ptr errorClientOfDylibPath; \ + ptr errorTargetDylibPath; \ + ptr errorSymbol; \ + ptr sharedCacheSlide; \ + uint8_t sharedCacheUUID[16]; \ + ptr reserved[16]; + +struct dyld_all_image_infos_32 { + dyld_image_infos_fields(uint32_t) +}; +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) { + struct task_dyld_info tdi; + mach_msg_type_number_t cnt = TASK_DYLD_INFO_COUNT; + + kern_return_t kr = task_info(task, TASK_DYLD_INFO, (void *) &tdi, &cnt); + if (kr || cnt != TASK_DYLD_INFO_COUNT) { + asprintf(error, "task_info(TASK_DYLD_INFO): kr=%d", kr); + return SUBSTITUTE_ERR_MISC; + } + + if (!tdi.all_image_info_addr || !tdi.all_image_info_size || + tdi.all_image_info_size > 1024 || + tdi.all_image_info_format > TASK_DYLD_ALL_IMAGE_INFO_64) { + asprintf(error, "TASK_DYLD_INFO obviously malformed"); + return SUBSTITUTE_ERR_MISC; + } + char all_image_infos_buf[1024]; + + cnt = tdi.all_image_info_size; + mach_vm_size_t size = tdi.all_image_info_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) { + asprintf(error, "mach_vm_read_overwrite(all_image_info): kr=%d", kr); + return SUBSTITUTE_ERR_MISC; + } + + bool is64 = tdi.all_image_info_format = TASK_DYLD_ALL_IMAGE_INFO_64; + const struct dyld_all_image_infos_32 *aii32 = (void *) all_image_infos_buf; + const struct dyld_all_image_infos_64 *aii64 = (void *) all_image_infos_buf; + + #define FIELD(f) (is64 ? aii64->f : aii32->f) + + if (FIELD(version) < 2) { + /* apparently we're on Leopard or something */ + asprintf(error, "dyld_all_image_infos version too low"); + 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; + + #undef FIELD + + if (info_array_count > 2000) { + asprintf(error, "unreasonable number of loaded libraries: %u", info_array_count); + return SUBSTITUTE_ERR_MISC; + } + + size_t info_array_size = info_array_count * info_array_elm_size; + void *info_array = malloc(info_array_count * info_array_elm_size); + if (!info_array) + return SUBSTITUTE_ERR_OOM; + + kr = mach_vm_read_overwrite(task, info_array_addr, info_array_size, + (mach_vm_address_t) info_array, &size); + if (kr || size != info_array_size) { + asprintf(error, "mach_vm_read_overwrite(info_array): kr=%d", kr); + return SUBSTITUTE_ERR_MISC; + } + + /* yay, slow file path reads! */ + + void *info_array_ptr = info_array; + for (uint32_t i = 0; i < info_array_count; i++) { + uint64_t load_address; + uint64_t file_path; + if (is64) { + uint64_t *e = info_array_ptr; + load_address = e[0]; + file_path = e[1]; + } else { + uint32_t *e = info_array_ptr; + load_address = e[0]; + file_path = e[1]; + } + + /* mach_vm_read_overwrite won't do partial copies, so... */ + + 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); + if (kr) { + 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); + if (kr) { + continue; + } + path_buf[MAXPATHLEN] = '\0'; + } + + if (!strcmp(path_buf, "/usr/lib/libpthread.dylib")) { + *libpthread_p = load_address; + return 0; + } + + info_array_ptr += info_array_elm_size; + } + + asprintf(error, "couldn't find libpthread"); + return SUBSTITUTE_ERR_MISC; +} + +EXPORT +int substitute_dlopen_in_pid(int pid, const char *filename, int options, char **error) { + mach_port_t task; + *error = NULL; + kern_return_t kr = task_for_pid(mach_task_self(), pid, &task); + int ret; + if (kr) { + asprintf(error, "task_for_pid: kr=%d", kr); + return SUBSTITUTE_ERR_TASK_FOR_PID; + } + + uint64_t dyld_addr, libpthread_addr; + if ((ret = find_libs_in_task(task, &dyld_addr, &libpthread_addr, error))) + goto fail; + (void) filename; + (void) options; + printf("%p %p\n", (void *) dyld_addr, (void *) libpthread_addr); + + ret = 0; +fail: + mach_port_deallocate(mach_task_self(), task); + return ret; +} +#endif /* __APPLE__ */ diff --git a/lib/darwin/substrate-compat.c b/lib/darwin/substrate-compat.c index 3658ac7..cf44b19 100644 --- a/lib/darwin/substrate-compat.c +++ b/lib/darwin/substrate-compat.c @@ -32,10 +32,10 @@ void *SubFindSymbol(void *image, const char *name) { return NULL; } - substitute_sym *sym; - if (substitute_find_private_syms(image, &name, &sym, 1) || !sym) + void *ptr; + if (substitute_find_private_syms(image, &name, &ptr, 1)) return NULL; - return substitute_sym_to_ptr(image, sym); + return ptr; } /* diff --git a/lib/substitute-internal.h b/lib/substitute-internal.h index 0f3e0ad..b21a190 100644 --- a/lib/substitute-internal.h +++ b/lib/substitute-internal.h @@ -53,3 +53,22 @@ typedef struct section section_x; #elif defined(TARGET_arm64) #include "arm64/misc.h" #endif + +#ifdef __APPLE__ +/* This could graduate to a public API but is not yet. */ +enum { + SUBSTITUTE_DIP_INJECT_MAIN_THREAD, /* not yet */ +}; + +enum { + /* substitute_dlopen_in_pid: task_for_pid failed; on OS X the reasons this + * can happen are really complicated and dumb, but generally one solution + * is to be root */ + SUBSTITUTE_ERR_TASK_FOR_PID = 1000, + + /* substitute_dlopen_in_pid: something didn't work */ + SUBSTITUTE_ERR_MISC, +}; + +int substitute_dlopen_in_pid(int pid, const char *filename, int options, char **error); +#endif diff --git a/lib/substitute.h b/lib/substitute.h index 398b7fd..00cc1b8 100644 --- a/lib/substitute.h +++ b/lib/substitute.h @@ -103,16 +103,17 @@ void substitute_close_image(struct substitute_image *handle); * * @handle handle opened with substitute_open_image * @names an array of symbol names to search for - * @syms an array of substitute_sym *, one per name; on return, each entry - * will be a pointer into the symbol table for that image, or NULL if - * the symbol wasn't found + * @syms an array of void *, one per name; on return, each entry will be + * filled in with the corresponding symbol address, or NULL if the + * symbol wasn't found + * (on ARM, this will be | 1 for Thumb functions) * @nsyms number of names * * @return SUBSTITUTE_OK (maybe errors in the future) */ int substitute_find_private_syms(struct substitute_image *handle, - const char **names, - substitute_sym **syms, + const char **__restrict names, + void **__restrict syms, size_t nsyms); /* Get a pointer corresponding to a loaded symbol table entry. diff --git a/test/test-find-syms.c b/test/test-find-syms.c index acd31ba..5dc1514 100644 --- a/test/test-find-syms.c +++ b/test/test-find-syms.c @@ -9,11 +9,10 @@ int main() { struct substitute_image *im = substitute_open_image(foundation); assert(im); const char *names[] = { "_absolute_from_gregorian" }; - substitute_sym *syms[1]; - assert(!substitute_find_private_syms(im, names, syms, 1)); - assert(syms[0]); + int (*f)(int); + assert(!substitute_find_private_syms(im, names, (void **) &f, 1)); + assert(f); - int (*f)(int) = (int (*)(int)) substitute_sym_to_ptr(im, syms[0]); assert(f(12345) < 0); substitute_close_image(im); |