Published on April 23, 2012 by Alexandru Juncu
Tagged: gcc, shared object, dynamic library, LD_LIBRARY_PATH

Based on the previous article, let’s go one step further and study a similar exploit. This time we’ll be dealing with executables and dynamic libraries.

Let’s consider a simple custom library function:

/* random.h */
int xkcd_random(void);

/* random.c */
int xkcd_random()
{
	return 4;
}

We can build it into a shared library:

$ gcc --share -fPIC -o librandom.so random.c

Let’s take a simple program that uses our function:

/* main.c */
#include <stdio.h>
#include "random.h"

int main(void)
{
	printf("8ball says:%d\n", xkcd_random());
	return 0;
}

If we want to use out shared object file in the current directory, we have to do two things. First, compile the program and link the shared library (with the -l flag) using libraries in the current directory (we do that using the -L. flag).

$ gcc -o main -L.  main.c -lrandom

Second, the library will be linked at compile time, but it won’t be loaded at runtime unless the loaded knows where the library is, with the help of the LD_LIBRARY_PATH variable.

$ ./main ./main: error while loading shared libraries:
librandom.so: cannot open shared object file: No such file or directory
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
$ ./main
8ball says:4

To ensure that we can always use the library, we can place it in the system’s library directory. Note that this means that we trust the code of that library and only the administrator can do this

# mv librandom.so /usr/lib

So now, each time the main program runs, the loader will dynamically load the random function from the system. But what if we have another function, from another library that has the same name, but does something else:

/* evil.c */
#include <unistd.h>
int xkcd_random()
{
	return 666;
}

$ gcc --share -fPIC -o librandom.so evil.c

If we overwrite the LD_LIBRARY_PATH variable with the . directory, the loader will use the ./librandom.so instead of /usr/lib/librandom.so and it doesn’t require any modification of the main program (no recompile needed).

$ ./main
8ball says:4
$ export LD_LIBRARY_PATH=.:LD_LIBRARY_PATH
$ ./main
8ball says:666

This is a similar to the PATH variable hack discussed in the previous article, but at a much more lower level. We can add a possible exploit here, like a shell execution:

#include <unistd.h>
int xkcd_random()
{
	execlp("/bin/sh", "/bin/sh", NULL);
	return 666;
}

Like we did before, we used a root-owned executable that had the SETUID bit set, in order to run things as root.

$ ls -la main
-rwsrwsr-x 1 root root 7192 2012-04-18 15:13 main
$ export LD_LIBRARY_PATH=.:LD_LIBRARY_PATH
$ ./main
8ball says:4

The program executed safely.

The Library Loader is smart enough to ignore the LD_LIBRARY_PATH when the executable is setuid-ed, because of exact such attacks. So even though you can exploit programs as a normal user, you can’t affect system. So low level is a little more secure than scripting level.

Here is a related article that explains why LD_LIBRARY_PATH exists but also why it’s evil.


comments powered by Disqus