SANS Penetration Testing

Go To The Head Of The Class: LD_PRELOAD For The Win

By Jeff McJunkin

funskool-go-to-the-head-of-the-class-original-imadhd8wbpcuhmzg

Imagine a Linux binary compiled from the following source:

#include <stdio.h>
#include <unistd.h>

int main(){
int duration = 15 * 1000 * 1000; /* microseconds are hard */

printf("Starting, please wait...");
usleep(duration);

printf(" Done!\nThe program started up or whatever.\n");
return 0;
}

Now, dear readers, what if we wanted the program to start up immediately? We could wait 15 seconds, but nobody's got time for that. Plus, it could just as easily be a longer wait.

If we have access to the source, we could certainly just remove the usleep() calls and recompile the program. But what if we don't have the source to the program? We'd need an easy way to change runtime behavior, and we'd need to discover which function calls are being made in the first place.

Luckily, today we'll discuss using a cool trick to steal time back. We will make our own usleep function and make the Linux loader call on it first — hence the title of this blog post.

First, if we're curious as to what function calls a binary is making (when we don't have the source), we can use the ltrace utility to see, as shown here:

$ ltrace ./delayed 
__libc_start_main(0x4005b6, 1, 0x7fff764a7958, 0x4005f0
printf("Starting, please wait...") = 24
usleep(15000000) = <void>
puts(" Done!\nThe program started up or"...Starting, please wait... Done!
The program started up or whatever.
) = 43
+++ exited (status 0) +++

The ltrace utility shows regular library calls. It's similar to strace, which shows system calls made by a program, but it's more useful for our purpose here.

Now that we can see those usleep() calls, we can make our own version of that function, then replace the normal version with ours at runtime.

$ cat hacking_time.c
#include <stdio.h>

unsigned int usleep(unsigned int microseconds) {
printf("Hijacked usleep!\n");
return 0;
}
$ gcc hacking_time.c -o hacking_time -shared -fPIC

Using gcc we've specified the normal input file (hacking_time.c) and output file (-o hacking_time),but we've also specified two additional options: -shared to make a library and -fPIC to specify Position Independent Code, which is necessary for making a shared library.

Now that we've built hacking_time as a shared library, here's the basic usage:

$ LD_PRELOAD="$PWD/hacking_time" ./delayed
Starting, please wait...Hijacked usleep!
Done!
The program started up or whatever.

Great! Instead of calling the normal usleep, we've told the Linux library loader to use our own version instead.

There are a few gotchas here. Note that C functions have input variable types and output variable types. We can look up those expected values after finding the functions shown in the ltrace output though by querying the corresponding manual page:

$ man 3 usleep # also visible online at http://man7.org/linux/man-pages/man3/usleep.3.html
[...skipping a few lines at the beginning...]
SYNOPSIS
#include
int usleep(useconds_t usec);

int usleep tells us that usleep() will return an integer. usleep(useconds_t usec) tells us it expects to be sent something of the type useconds_t. Scrolling down a little further in that same man page we see that useconds_t is also an integer type:

NOTES
The type useconds_t is an unsigned integer type capable of holding integers in the range [0,1000000]

When we build hacking_time.c (or any other code meant for hijacking library calls) we'll need to be sure to have the same inputs (parameters) and output types.

Okay, now that we've examined the happy path to hacking time itself, let's look at some of the common issues when hijacking functions:

Common Issues

Compiling without -fPIC:

Running a shared library for use with LD_PRELOAD without -fPIC looks like the following:

$ gcc hacking_time.c -shared -o hacking_time
/usr/bin/ld: /tmp/ccZKQYM8.o: relocation R_X86_64_32 against `.rodata' can not be used when making
a shared object; recompile with -fPIC
/tmp/ccZKQYM8.o: error adding symbols: Bad value
collect2: error: ld returned 1 exit status

Make sure you specify the -fPIC argument when compiling an library for a LD_PRELOAD attack.

Compiling without -shared:

$ gcc hacking_time.c -o hacking_time
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status

Since we don't define main(), gcc doesn't know how to build this as a standalone binary. Make sure you specify the -shared argument when building a library for an LD_PRELOAD attack.

Wrong input type to a hijacked function

$ cat hacking_time_wrong_input.c
#include <tstdio.h>

unsigned int usleep(void) {
printf("Hijacked usleep!\n");
return 0;
}
$ gcc hacking_time_wrong_input.c -o hacking_time -shared -fPIC
$ LD_PRELOAD="$PWD/hacking_time" ./delayed
Starting, please wait...Hijacked usleep!
Done!
The program started up or whatever.

Note that I defined usleep as accepting no parameters / inputs with usleep(void). I think we're clearly into undefined behavior here, but I was surprised to see that this actually worked for me.

Wrong return type from a hijacked function

$ cat hacking_time_wrong_return.c
#include <tstdio.h>

void usleep(unsigned int microseconds) {
printf("Hijacked usleep!\n");
return;
}
$ gcc hacking_time_wrong_return.c -o hacking_time -shared -fPIC
$ LD_PRELOAD="$PWD/hacking_time" ./delayed
Starting, please wait...Hijacked usleep!
Done!
The program started up or whatever.

Here I defined usleep as not returning any data with void usleep. This worked but isn't reliable, since the Linux loader expects the library to comply with the system function declaration.

More resources

If you want to go further into LD_LIBRARY tricks, there are a few resources I'd like to point you to:

  • preeny has a number of pre-built replacement functions available
  • libfaketime can fake calls to see the system time, which is interestingly used to make more deterministic (bit-for-bit identical) software builds in projects like Reproducible Builds by Debian
  • retrace to flexibly track and alter library calls using config files

Thanks for reading! I hope you enjoy the challenge of hacking time — and other libraries — this holiday season.

- Jeff McJunkin
@jeffmcjunkin

SANS Note:

Would you like (4) printed copies of the new SANS Penetration Testing Curriculum poster mailed directly to you?

poster_for_webcast

Then register for the upcoming webcast detailing the new poster, by Ed Skoudis, on January 9th at 1pm EST, and we will mail the printed posters to you after the webcast has aired.

This is for the brand new "Blueprint: Building a Better Pen Tester" poster.

Register now for this free educational webcast and to receive (4) printed copies of the new SANS Pen Test Poster!

Post a Comment






Captcha


* Indicates a required field.