TranslateProject/sources/tech/20130402 Dynamic linker tricks Using LD_PRELOAD to cheat inject features and investigate programs.md
2017-12-04 14:25:46 +08:00

13 KiB
Raw Blame History

Dynamic linker tricks: Using LD_PRELOAD to cheat, inject features and investigate programs

This post assumes some basic C skills.

Linux puts you in full control. This is not always seen from everyones perspective, but a power user loves to be in control. Im going to show you a basic trick that lets you heavily influence the behavior of most applications, which is not only fun, but also, at times, useful.

A motivational example

Let us begin with a simple example. Fun first, science later.

random_num.c:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
int main(){
  srand(time(NULL));
  int i = 10;
  while(i--) printf("%d\n",rand()%100);
  return 0;
}

Simple enough, I believe. I compiled it with no special flags, just

gcc random_num.c -o random_num

I hope the resulting output is obvious ten randomly selected numbers 0-99, hopefully different each time you run this program.

Now lets pretend we dont really have the source of this executable. Either delete the source file, or move it somewhere we wont need it. We will significantly modify this programs behavior, yet without touching its source code nor recompiling it.

For this, lets create another simple C file:

unrandom.c:

int rand(){
    return 42; //the most random number in the universe
}

Well compile it into a shared library.

gcc -shared -fPIC unrandom.c -o unrandom.so

So what we have now is an application that outputs some random data, and a custom library, which implements the rand() function as a constant value of 42.  Now… just run  _random_num _ this way, and watch the result:

LD_PRELOAD=$PWD/unrandom.so ./random_nums

If you are lazy and did not do it yourself (and somehow fail to guess what might have happened), Ill let you know the output consists of ten 42s.

This may be even more impressive it you first:

export LD_PRELOAD=$PWD/unrandom.so

and then run the program normally. An unchanged app run in an apparently usual manner seems to be affected by what we did in our tiny library…

Wait, what? What did just happen?

Yup, you are right, our program failed to generate random numbers, because it did not use the “real” rand(), but the one we provided which returns 42 every time.

But we told it to use the real one. We programmed it to use the real one. Besides, at the time we created that program, the fake rand() did not even exist!

This is not entirely true. We did not choose which rand() we want our program to use. We told it just to use rand().

When our program is started, certain libraries (that provide functionality needed by the program) are loaded. We can learn which are these using  ldd :

$ ldd random_nums
linux-vdso.so.1 => (0x00007fff4bdfe000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f48c03ec000)
/lib64/ld-linux-x86-64.so.2 (0x00007f48c07e3000)

What you see as the output is the list of libs that are needed by  random_nums . This list is built into the executable, and is determined compile time. The exact output might slightly differ on your machine, but a libc.so must be there this is the file which provides core C functionality. That includes the “real” rand().

We can have a peek at what functions does libc provide. I used the following to get a full list:

nm -D /lib/libc.so.6

The  nm  command lists symbols found in a binary file. The -D flag tells it to look for dynamic symbols, which makes sense, as libc.so.6 is a dynamic library. The output is very long, but it indeed lists rand() among many other standard functions.

Now what happens when we set up the environmental variable LD_PRELOAD? This variable forces some libraries to be loaded for a program. In our case, it loads  unrandom.so  for  random_num , even though the program itself does not ask for it. The following command may be interesting:

$ LD_PRELOAD=$PWD/unrandom.so ldd random_nums
linux-vdso.so.1 =>  (0x00007fff369dc000)
/some/path/to/unrandom.so (0x00007f262b439000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f262b044000)
/lib64/ld-linux-x86-64.so.2 (0x00007f262b63d000)

Note that it lists our custom library. And indeed this is the reason why its code gets executed:  random_num  calls rand(), but if  unrandom.so  is loaded it is our library that provides implementation for rand(). Neat, isnt it?

Being transparent

This is not enough. Id like to be able to inject some code into an application in a similar manner, but in such way that it will be able to function normally. Its clear if we implemented open() with a simple “ return 0; “, the application we would like to hack should malfunction. The point is to be transparent, and to actually call the original open:

inspect_open.c:

int open(const char *pathname, int flags){
  /* Some evil injected code goes here. */
  return open(pathname,flags); // Here we call the "real" open function, that is provided to us by libc.so
}

Hm. Not really. This wont call the “original” open(…). Obviously, this is an endless recursive call.

How do we access the “real” open function? It is needed to use the programming interface to the dynamic linker. Its simpler than it sounds. Have a look at this complete example, and then Ill explain what happens there:

inspect_open.c:

#define _GNU_SOURCE
#include <dlfcn.h>
 
typedef int (*orig_open_f_type)(const char *pathname, int flags);
 
int open(const char *pathname, int flags, ...)
{
    /* Some evil injected code goes here. */
 
    orig_open_f_type orig_open;
    orig_open = (orig_open_f_type)dlsym(RTLD_NEXT,"open");
    return orig_open(pathname,flags);
}

The  dlfcn.h  is needed for  dlsym  function we use later. That strange  #define  directive instructs the compiler to enable some non-standard stuff, we need it to enable  RTLD_NEXT  in  dlfcn.h . That typedef is just creating an alias to a complicated pointer-to-function type, with arguments just as the original open the alias name is  orig_open_f_type , which well use later.

The body of our custom open(…) consists of some custom code. The last part of it creates a new function pointer  orig_open  which will point to the original open(…) function. In order to get the address of that function, we ask  dlsym  to find for us the next “open” function on dynamic libraries stack. Finally, we call that function (passing the same arguments as were passed to our fake “open”), and return its return value as ours.

As the “evil injected code” I simply used:

inspect_open.c (fragment):

printf("The victim used open(...) to access '%s'!!!\n",pathname); //remember to include stdio.h!

To compile it, I needed to slightly adjust compiler flags:

gcc -shared -fPIC  inspect_open.c -o inspect_open.so -ldl

I had to append  -ldl , so that this shared library is linked to  libdl , which provides the  dlsym  function. (Nah, I am not going to create a fake version of  dlsym , though this might be fun.)

So what do I have in result? A shared library, which implements the open(…) function so that it behaves exactly as the real open(…)… except it has a side effect of  printf ing the file path :-)

If you are not convinced this is a powerful trick, its the time you tried the following:

LD_PRELOAD=$PWD/inspect_open.so gnome-calculator

I encourage you to see the result yourself, but basically it lists every file this application accesses. In real time.

I believe its not that hard to imagine why this might be useful for debugging or investigating unknown applications. Please note, however, that this particular trick is not quite complete, because  open()  is not the only function that opens files… For example, there is also  open64()  in the standard library, and for full investigation you would need to create a fake one too.

Possible uses

If you are still with me and enjoyed the above, let me suggest a bunch of ideas of what can be achieved using this trick. Keep in mind that you can do all the above without to source of the affected app!

  1. Gain root privileges. Not really, dont even bother, you wont bypass any security this way. (A quick explanation for pros: no libraries will be preloaded this way if ruid != euid)

  2. Cheat games: Unrandomize. This is what I did in the first example. For a fully working case you would need also to implement a custom  random()rand_r() , random_r() . Also some apps may be reading from  /dev/urandom  or so, you might redirect them to  /dev/null  by running the original  open()  with a modified file path. Furthermore, some apps may have their own random number generation algorithm, there is little you can do about that (unless: point 10 below). But this looks like an easy exercise for beginners.

  3. Cheat games: **Bullet time. **Implement all standard time-related functions pretend the time flows two times slower. Or ten times slower. If you correctly calculate new values for time measurement, timed  sleep functions, and others, the affected application will believe the time runs slower (or faster, if you wish), and you can experience awesome bullet-time action. Or go even one step further and let your shared library also be a DBus client, so that you can communicate with it real time. Bind some shortcuts to custom commands, and with some additional calculations in your fake timing functions you will be able to enable&disable the slow-mo or fast-forward anytime you wish.

  4. Investigate apps: List accessed files. Thats what my second example does, but this could be also pushed further, by recording and monitoring all apps file I/O.

  5. Investigate apps: Monitor internet access. You might do this with Wireshark or similar software, but with this trick you could actually gain control of what an app sends over the web, and not just look, but also affect the exchanged data. Lots of possibilities here, from detecting spyware, to cheating in multiplayer games, or analyzing & reverse-engineering protocols of closed-source applications.

  6. Investigate apps: Inspect GTK structures. Why just limit ourselves to standard library? Lets inject code in all GTK calls, so that we can learn what widgets does an app use, and how are they structured. This might be then rendered either to an image or even to a gtkbuilder file! Super useful if you want to learn how does some app manage its interface!

  7. Sandbox unsafe applications. If you dont trust some app and are afraid that it may wish to _ rm -rf / _ or do some other unwanted file activities, you might potentially redirect all its file IO to e.g. /tmp by appropriately modifying the arguments it passes to all file-related functions (not just  open , but also e.g. removing directories etc.). Its more difficult trick that a chroot, but it gives you more control. It would be only as safe as complete your “wrapper” was, and unless you really know what youre doing, dont actually run any malicious software this way.

  8. Implement features. zlibc is an actual library which is run this precise way; it uncompresses files on the go as they are accessed, so that any application can work on compressed data without even realizing it.

  9. **Fix bugs. **Another real-life example: some time ago (I am not sure this is still the case) Skype which is closed-source had problems capturing video from some certain webcams. Because the source could not be modified as Skype is not free software, this was fixed by preloading a library that would correct these problems with video.

  10. Manually access applications own memory. Do note that you can access all app data this way. This may be not impressive if you are familiar with software like CheatEngine/scanmem/GameConqueror, but they all require root privileges to work. LD_PRELOAD does not. In fact, with a number of clever tricks your injected code might access all app memory, because, in fact, it gets executed by that application itself. You might modify everything this application can. You can probably imagine this allows a lot of low-level hacks… but Ill post an article about it another time.

These are only the ideas I came up with. I bet you can find some too, if you do share them by commenting!


via: https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/

作者:Rafał Cieślak 译者:译者ID 校对:校对者ID

本文由 LCTT 原创编译,Linux中国 荣誉推出