It's Bugs All the Way Down

Security Research by Dan Rosenberg

Controlling uninitialized memory with LD_PRELOAD

In this post, I’ll be describing a technique that allows a local Linux user to exercise a degree of control over uninitialized memory on the stack when executing a program.

I did not discover this technique myself – as far as I know, many people already know about this behavior, but I’ve never seen it documented. Situations in which this is security relevant are unlikely – first of all, since the user needs control over environment variables to perform this trick, the only potentially relevant cases are with setuid binaries. The trick may provide a means of placing shellcode on the stack, but increasing adoption of NX support makes this mostly useless, and unless environment variables and arguments were deliberately sanitized by the program, an attacker already has that ability anyway. The other situation in which this may prove useful is by controlling uninitialized variables, but if uninitialized memory is being accessed by a program, you’ve already got problems. Taking all this into account, this trick seems mostly useless in real life, but might be useful in contrived situations such as wargames (obligatory plug for www.smashthestack.org).

The source of this technique is in the way the Linux linker/loader, ld.so, handles the LD_PRELOAD environment variable. This variable allows users to specify libraries to be preloaded, effectively allowing users to override functions used in a particular binary. Of course, the LD_PRELOAD variable is ignored with setuid binaries, since otherwise an attacker could trivially override arbitrary functions in setuid binaries and easily take control of a system.

However, regardless of whether or not libraries specified via LD_PRELOAD are actually loaded at runtime, ld.so copies the name of each library onto the stack prior to executing the program, and doesn’t clean up after itself. By specifying a very long LD_PRELOAD variable and executing a binary, a portion of the stack will be overwritten with part of the LD_PRELOAD variable during linking, and it will stay that way once execution of the program begins, even on setuid binaries, where the library itself is not loaded.

Let’s try an example. The following program is just useful for verifying the issue:

int main(int argc, char * argv[], char * envp[])
{

	char ** arg;
	char * c;

	for(arg = argv; *arg; arg++)
		for(c = *arg; *c; c++)
			*c = '';

	for(arg = envp; *arg; arg++)
		for(c = *arg; *c; c++)
			*c = '';

	sleep(10);

}

This program simply erases all of the arguments and environment variables, and then goes to sleep. Let’s see what happens if we run this in gdb with an extra long LD_PRELOAD variable:

$ export LD_PRELOAD=`perl -e 'print "A"x10000'`
$ gdb ./test
(complaint about being unable to preload a library)

gdb> run
(complaint about being unable to preload a library)

^Z
(program interrupted)

gdb> x/100x $esp

0xbff85a90: 0x0070ef23 0xbff85c44 0xbff85c44 0x00000000
0xbff85aa0: 0x0d696910 0x0000000a 0xbff85ab8 0xbff85b44
0xbff85ab0: 0xbff85c44 0xbff85bc4 0x00000000 0x00000000
0xbff85ac0: 0x00000000 0x41414141 0x41414141 0x41414141
0xbff85ad0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff85ae0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff85af0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff85b00: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff85b10: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff85b20: 0x41414141 0x41414141 0x41414141 0x41414141
0xbff85b30: 0x41414141 0x41414141 0x41414141 0x00000000
0xbff85b40: 0xfffff800 0x00000000 0x00000000 0x002c8828
0xbff85b50: 0xbff85c04 0x002b5009 0xbff85bf4 0x08048204
0xbff85b60: 0xbff85be8 0x002c87cc 0x00000000 0xb78cda18
0xbff85b70: 0x00000001 0x00000000 0x00000001 0x08048234
0xbff85b80: 0x002c8954 0x002c7ff4 0x0067ab98 0x00000001
0xbff85b90: 0xbff85c00 0x002b4da5 0x00000000 0x002c8670
0xbff85ba0: 0x00000000 0xbff85c30 0xbff85be8 0xbff85bf4
0xbff85bb0: 0x00000000 0xbff85bcc 0x0804824d 0x07b1ea71
0xbff85bc0: 0xb78cd9ec 0x00010000 0x00000000 0x00000000

Where did all of those A’s (0x41) come from? They’re clearly not from the environment or arguments, since those were deliberately cleared. The answer is, they’re left over from when ld.so copied the name of a supposed library onto the stack prior to executing the program. Running the program again after clearing the LD_PRELOAD variable confirms that this memory is uninitialized.

So, there you have it. A neat little trick for controlling uninitialized memory by exploiting how ld.so processes preloaded libraries.

This entry was posted on Thursday, April 8th, 2010 at 1:17 pm and is filed under Exploitation, Linux. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.