Saturday, April 7, 2012

DYLD_INSERT_LIBRARIES

The so-called Flashback trojan is in the news the last few days. There is a breathless piece at PCWorld which compares it to Conficker, which is pretty funny (and completely stupid). FWIW, I don't have Flash Player installed on my machine, or Java either. I use a Safari Extension called YouTube5 on the very rare occasions I want to watch something requiring this.

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();
}
The if block above grabs the "real" f. We carry out our nefarious plan (printing a warning) and then call the original function.

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
And the output:
> ./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)