Griffin Byatt

I am a security engineer, currently focused on internet security and RE.

23 January 2019

Dynamically Loading Mach-O Executables

by Griffin Byatt

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 the vmsize 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.

tags: macos - ios