I recently had to intercept function calls to my C socket library in order to implement a prototype for a Multi-source Multipath network protocol. That is why in this post I want to give a simple introduction on how to use the LD_PRELOAD environment variable to override shared C libraries. Further, I am going to show how to use dlsym to make calls to the original C functions from within our hooks.

Simple socket call interception

Lets create a simple program that opens a socket.

simple_client.c

The only thing this program does is creating a socket without binding it to an address. Compiling and running this program gives us:


$ gcc -Wall simple_client.c -o simple_client
$ ./simple_client
Socket successfully created
$

Now we want to intercept the socket() function to change the behavior on each call. For this, we first have to write a shared library that will override the socket() function.

socket_hook.c

With the help of LD_PRELOAD we can now let our socket_hook.so library get loaded before the standard C libraries, meaning the first occurrence of the socket() function is in our shared library. This results in the following:


$ gcc -Wall -fPIC -shared socket_hook.c -o socket_hook.so
$ LD_PRELOAD=./socket_hook.so ./simple_client 
socket() call intercepted
Error : Could not create socket
$

Please note, that the Error message is due to the fact that we return -1 in our shared library, but our client code expects a non-negative return value. So far, we could override a standard socket() call with our own function from our shared library.

Calling the original socket library from inside our hook

In a next step we also want to call the original socket() function from our shared library. This will enable us to easily add additional functionalities to existing standard C libraries without changing or rewriting the original libraries. Also, when implemented properly, we could achieve total transparency to the calling layer. Lets change our previous socket hook.

socket_hook.c

We use dlsym with RTLD_NEXT from <dlfcn.h> to find the next occurrence of the socket() function and store the location in o_socket. Hence, in our example o_socket can from then on be used to call the original socket() function. _GNU_SOURCE has to be defined in order to be able to use RTLD_NEXT. We now have to compile our shared library differently from before:


$ gcc -Wall -fPIC -shared socket_hook.c -o socket_hook.so -ldl

Using our new shared library gives us the following:


$ LD_PRELOAD=./socket_hook.so ./simple_client 
socket() call intercepted
Socket successfully created
$

That’s it! We have successfully intercepted the socket function and made a call to the original socket library. We could now simply add some additional behavior to the socket() call. We could also easily intercept further functions such as read() or write(), to be able to build a new transparent layer on top of the socket library - ideal for writing quick networking prototypes.