Recently I had a chance to play with LD_PRELOAD for a bit, due to our recent
Huge Refactor (tm) at Nodejitsu. LD_PRELOAD environment variable is a way of
loading a library before any other libraries are loaded.
For example, let’s write this short program in C:
#include <stdio.h>
int main(int argc, char** argv) {
puts("Hello, world!");
return 0;
}Its output is obvious:
$ gcc puts.c -o puts
$ ./puts
Hello, world!
Now, as I mentioned before, with LD_PRELOAD we can force our library to be
loaded before other libraries. This also affects libc. That means that we can
override the puts() function
Let’s compile this little library and try to override puts from libc:
#include <dlfcn.h>
#include <stdlib.h>
int puts(const char* s) {
int (*original_puts) (const char*) = dlsym(RTLD_NEXT, "puts");
return original_puts("Hello, puts!");
}Thanks to dlsym(RTLD_NEXT, "puts"), we’re able to extract the original puts
function and call it with different arguments. RTLD_NEXT is a pseudo-handle
which makes linker search for occurences of symbol in libraries loaded after
current library.
First problem we’re going to run into is the difference between how Mac OS X and other UNIX operating systems work.
To compile this library on Mac OS X execute:
gcc -dynamiclib -o puts-override.dylib puts-override.cTo compile it on other operating systems, use:
gcc -D_GNU_SOURCE -fPIC -shared -o puts-override.so -ldl puts-override.cNow that we compiled the library, let’s try overriding our puts call.
On Mac OS X:
$ export DYLD_FORCE_FLAT_NAMESPACE=1
$ export DYLD_INSERT_LIBRARIES=./puts-override.dylib
$ ./puts
On other operating systems:
$ export LD_PRELOAD=./puts-override.so
$ ./puts
Hello, puts!
Now, the difference between compilation and usage come from the fact that Mac OS X uses a different linker than other systems. You can read more about OS X’s linker on its manual page.
That’s it for this blog post. If you have problems getting those instructions to work, shoot me a line!