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) +++</void>
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</stdio.h>
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