I got interested in how it works, and a google search led me to two pages with example code using DYLD_FORCE_FLAT_NAMESPACE and DYLD_INSERT_LIBRARIES.
I shamelessly copied the code from here (also see here), with slight changes. Very similar code is in a YoLinux tutorial here.
We make a "shared library" containing a function f. We can use this function in our program (main.c below) by simply telling gcc where to find it with:
gcc -o call_f mysharedlib.dylib main.c
Here are the listings:
mysharedlib.c
#include <stdio.h> #include "mysharedlib.h" void f() { printf("hello\n"); } |
mysharedlib.h
void f(); |
main.c
int main() { f(); return 0; } |
A shell script to compile and test what we've got so far:
before.sh
#!/bin/bash gcc -dynamiclib -o mysharedlib.dylib mysharedlib.c gcc -o call_f mysharedlib.dylib main.c echo "before:" ./call_f |
Output:
> ./before.sh before: hello |
Now, here is where it gets more interesting. We have an additional file which also contains a function called f, which will be discovered first and called rather than the function we think we're calling (it "shadows" the first one). That file is
openhook.c
#include <stdio.h> #include <dlfcn.h> #include <unistd.h> #include "mysharedlib.h" typedef void (*fType)(); static void (*real_f)() = NULL; void f() { if ( ! real_f) { void* handle = dlopen("mysharedlib.dylib", RTLD_NOW); real_f = (fType)dlsym(handle, "f"); if ( ! real_f) { printf("NG\n"); return; } } printf("--------hijacked!--------\n"); real_f(); } |
A shell script to run the second part is:
after.sh
#!/bin/bash gcc -flat_namespace -dynamiclib -o openhook.dylib openhook.c export DYLD_FORCE_FLAT_NAMESPACE= export DYLD_INSERT_LIBRARIES=openhook.dylib echo "after export:" ./call_f echo "env:" $DYLD_INSERT_LIBRARIES unset DYLD_FORCE_FLAT_NAMESPACE unset DYLD_INSERT_LIBRARIES echo "after unset:" ./call_f echo "env:" $DYLD_INSERT_LIBRARIES |
> ./after.sh after export: --------hijacked!-------- hello env: openhook.dylib after unset: hello env: |
We've explored dlopen and dlsym a bit in a previous post.
There is nothing very complicated happening in our code. It's just that the DYLD_FORCE_FLAT_NAMESPACE= and DYLD_INSERT_LIBRARIES=openhook.dylib load open hook.dylib before mysharedlib.dylib. This allows us to intercept the function call and execute our special code before doing what the original caller asked for and expects. If we're careful, they may not notice.
There is a bit more detail in the man page for dyld.
For extra credit, you might want to use DYLD_PRINT_LIBRARIES to snoop on the loader, as we did before. Or use otool:
> otool -L call_f dyld: loaded: /usr/bin/otool dyld: loaded: /Users/telliott_admin/Desktop/openhook.dylib dyld: loaded: /usr/lib/libstdc++.6.dylib dyld: loaded: /usr/lib/libSystem.B.dylib dyld: loaded: /usr/lib/libc++abi.dylib dyld: loaded: /usr/lib/system/libcache.dylib dyld: loaded: /usr/lib/system/libcommonCrypto.dylib dyld: loaded: /usr/lib/system/libcompiler_rt.dylib dyld: loaded: /usr/lib/system/libcopyfile.dylib dyld: loaded: /usr/lib/system/libdispatch.dylib dyld: loaded: /usr/lib/system/libdnsinfo.dylib dyld: loaded: /usr/lib/system/libdyld.dylib dyld: loaded: /usr/lib/system/libkeymgr.dylib dyld: loaded: /usr/lib/system/liblaunch.dylib dyld: loaded: /usr/lib/system/libmacho.dylib dyld: loaded: /usr/lib/system/libmathCommon.A.dylib dyld: loaded: /usr/lib/system/libquarantine.dylib dyld: loaded: /usr/lib/system/libremovefile.dylib dyld: loaded: /usr/lib/system/libsystem_blocks.dylib dyld: loaded: /usr/lib/system/libsystem_c.dylib dyld: loaded: /usr/lib/system/libsystem_dnssd.dylib dyld: loaded: /usr/lib/system/libsystem_info.dylib dyld: loaded: /usr/lib/system/libsystem_kernel.dylib dyld: loaded: /usr/lib/system/libsystem_network.dylib dyld: loaded: /usr/lib/system/libsystem_notify.dylib dyld: loaded: /usr/lib/system/libsystem_sandbox.dylib dyld: loaded: /usr/lib/system/libunc.dylib dyld: loaded: /usr/lib/system/libunwind.dylib dyld: loaded: /usr/lib/system/libxpc.dylib call_f: mysharedlib.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0) |