17 Jul 2013

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.c

To compile it on other operating systems, use:

gcc -D_GNU_SOURCE -fPIC -shared -o -ldl puts-override.c

Now that we compiled the library, let's try overriding our puts call.

On Mac OS X:

$ export DYLD_INSERT_LIBRARIES=./puts-override.dylib
$ ./puts

On other operating systems:

$ export LD_PRELOAD=./
$ ./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!