Dynamically Loading Mach-O Executables
On Apple platforms, you can add dynamic libraries to executables at run time with
DYLD_INSERT_LIBRARIES
. This can be done for debugging purposes, or for things like iOS “tweaks” where you’d like to alter functionality of a closed-source application.Very simply, it works something like this:
// main.c #import <stdio.h> int main(int argc, char **argv) { printf("Hello, world!"); return 0; } // lib.c #import <stdio.h> __attribute__((constructor)) void runlib() { printf("LIB INJECTED\n"); }
Compile and run the executable to see the “Hello” message…
$ clang -o ex main.c $ ./ex > Hello, world!
Now compile the library, and run them together…
$ clang -shared -o lib.dylib lib.c $ DYLD_INSERT_LIBRARIES=lib.dylib ./ex > LIB INJECTED > Hello, world!
You can run arbitrary code, overwrite functions, and generally do whatever you’d like. But that’s not what this is about (I’ve over-buried the lede 😔). You can also load other executables with this method, and treat them kind of like a library.
This is slightly more complicated, but still pretty simple:
// main.c #import <stdio.h> #import <dlfcn.h> int main(int argc, char **argv) { typedef void (*fun)(); void *handle = dlopen("pseudolib", RTLD_NOW); if (handle) { fun f = dlsym(handle, "run"); if (f) { f(); } } else { printf("%s\n", dlerror()); } return 0; } // lib.c #import <stdio.h> int main() { printf("Don't run this like an executable."); } void run() { printf("LIB INJECTED"); }
Now compiling and running looks like…
$ clang -o ex main.c && clang -o pseudolib lib.c $ ./pseudolib > Don't run this like an executable. $ ./ex > LIB INJECTED
The only hang-up (maybe not only) with doing this, pops up on iOS. Executables have a large
__PAGEZERO
, which doesn’t actually take up memory, but will still prevent you from loading one iOS application into another iOS application as a dynamic library due to memory errors.Fortunately, this issue can be bypassed by zeroing out (or minimizing) the
__PAGEZERO
segment. The process for that is basically, update thevmsize
of the__PAGEZERO
segment to something small, then loop through all of the other segments and load commands and update their addresses accordingly. Once you’ve done that, you can load the application just like any other library.