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 everyone’s perspective, but a power user loves to be in control. I’m 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.

#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 let’s pretend we don’t really have the source of this executable. Either delete the source file, or move it somewhere – we won’t need it. We will significantly modify this programs behavior, yet without touching it’s source code nor recompiling it.

For this, lets create another simple C file:

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

We’ll 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), I’ll let you know – the output consists of ten 42’s.

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 it’s code get’s executed: random_num calls rand(), but if unrandom.so is loaded it is our library that provides implementation for rand(). Neat, isn’t it?

Being transparent

This is not enough. I’d 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. It’s 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:

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 won’t 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. It’s simpler than it sounds. Have a look at this complete example, and then I’ll explain what happens there:

#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 we’ll 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 it’s return value as ours.

As the “evil injected code” I simply used:

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 printfing the file path :-)

If you are not convinced this is a powerful trick, it’s 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 it’s 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, don’t even bother, you won’t 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. That’s what my second example does, but this could be also pushed further, by recording and monitoring all app’s 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? Let’s 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 don’t 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 it’s 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.). It’s 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 you’re doing, don’t 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 application’s 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 I’ll 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!


55 Responses to “Dynamic linker tricks: Using LD_PRELOAD to cheat, inject features and investigate programs”

  1. Jim Says:

    This cannot be used for sandboxing, because applications don’t have to go through dynamically loaded libraries to do anything. They can simply make system calls directly, and you would need to use Linux kernel features to intercept those.

    • Rafał Cieślak Says:

      True. You are indeed right that this way one can never be guaranteed success, which is precisely why I discourage from sandboxing applications this way. However, I consider this as a useful example of what can be done with a number of hooks.

  2. The art of cheating: Making a chess.com chess bot using a unusual approach! | Incolumitas Says:

    […] drawbacks: Hooking is very platform depended. On linux we could hook with kernel modules or the LD_PRELOAD trick. On Windows we'd need other hooking techniques like IAT hooking, using the microsoft hooking […]

  3. herison Says:

    Great article !
    I have a programme which require libc.so.6( GLIBC_2.14 ) so I take new libc.so.6 and tried to preload libc.so.6 but I don’t work. Have you a solution ?

    Thank for this article.

    • Rafał Cieślak Says:

      I am not sure what are you trying to do. Do you wish to force an application to use a different libc.so.6 version? Some background information on what are you trying to achieve and how exactly is it not working the way you expect it to work would be great.

      • herison Says:

        I would compile a C++11 application on the redhat, I got precompiled binary of g++ but when I launch g++ :
        -bash-4.1$ ./g++
        ./g++: /lib64/libc.so.6: version `GLIBC_2.14′ not found (required by ./g++)

        I saw that libc is executable:
        -bash-4.1$ /lib64/libc.so.6 | head -1
        GNU C Library stable release version 2.12, by Roland McGrath et al.

        My libc is too old, I got last precompiled libc.so.6 and when I preload it.
        -bash-4.1$ LD_PRELOAD=$PWD/lib64/libc.so.6 ./usr/bin/g++
        ERROR: ld.so: object ‘/auto/herison/my_env/dl/hermetic/lib64/libc.so.6′ from LD_PRELOAD cannot be preloaded: ignored.
        ./usr/bin/g++: /lib64/libc.so.6: version `GLIBC_2.14’ not found (required by ./usr/bin/g++)

        And when I execute last libc.so.6 :
        -bash-4.1$ $PWD/lib64/libc.so.6
        /auto/herison/my_env/dl/hermetic/lib64/libc.so.6: relocation error: /auto/herison/my_env/dl/hermetic/lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux.so.2 with link time reference

        Thanks for your help

    • Billy Holmes Says:

      You can use LD_LIBRARY_PATH for this.

      Though, it’d probably be easier to just run a docker image of a newer version of linux, like fedora:22 docker image, or even the rhel:7.2 docker image.

      Sounds like you’re on RHEL 6 (mentioned below). You could run your compile on RHEL7 (comes with glibc 2.17), or install docker on a fedora or rhel7 box, and run your compile that way (probably the most stable option).

    • andoryu Says:

      You probably want to specify a newer ld.so to do such:
      env LD_PRELOAD=/path/to/libc.so.6 /path/to/ld/ld-linux.so EXE

  4. Diving Deep into Mayhem | Virus / malware / hacking / security news Says:

    […] is responsible for clean up and for executing the malware. It accomplishes this using the so called ‘LD_PRELOAD’ -technique in which an environment variable, ‘LD_PRELOAD’ is set with the path to the dropped […]

  5. Cataclismo Says:

    I have an application with a class called “A”, for example, and a function in this class called “toast”. I know exactly how is declared the function “toast”. How can I intercept this function using a library? My application is compiled using G++ 4.8 with C++ 11.
    I tried to make a library with function “A::toast()”, but my app it’s calling the good one. Why? I created library using G++ aswell.
    Can you tell me the flags I need in order to work and also what I have to declare to work? I heard something about “extern “C”“ or something.

    • Rafał Cieślak Says:

      Hello! The reason why you are unable to get this trick to work is because you are working with C++. The example I used was pure C, and it is indeed easier in such case. The point is that C++ uses name mangling, so you need to take that into account when naming your functions.
      Furthermore, and more importantly, you will not see any changes if toast() is defined within your application. The trick described in this article can only substitute functions that come from shared libraries. A function that is defined within the app itself is not processed by the dynamic linker, so it cannot be substituted. It is not possible to simply exchange a function within a program. If you are interested in details, I recommend you to learn about static and dynamic linking ;-)

  6. Flick 2 VulnHub Writeup » g0blin Research Says:

    […] refreshing my memory (thanks to this post), I create a little shared library that overrides the rand method. After running […]

  7. LD_PRELOAD | Paradise2 Says:

    […] 其文章內容參考、翻譯自:Dynamic linker tricks: Using LD_PRELOAD to cheat, inject features and investigate programs […]

  8. tower98 Says:

    Already using this idea to “fix” Cisco VPN client which messes up my iptables and routing table while running. Found that hack somewhere online. Thank You for explaining how this works!

  9. Sebastian Parschauer Says:

    You should have a look at https://github.com/ugtrain/ugtrain. ugtrain is a game trainer with dynamic memory support (hooks malloc(), free(), etc. with LD_PRELOAD), runs the game itself and doesn’t require any root privileges – not even for scanmem. Btw.: I also maintain scanmem/GameConqueror since last year at: https://github.com/scanmem/scanmem/. We have it in the scanmem man page now why scanmem requires root privileges (Yama security module).

  10. vulnhub: flickII – to the root – walkthrough part2 | IT-Unsecurity Says:

    […] If youre not familiar with the LD_PRELOAD technique google it quickly or read up on this well written post of it. […]

  11. Brian Says:

    How about I bypass you by issuing the open syscall directly ;)

  12. "This will only hurt for a moment": code injection on Linux and macOS | @datawireio Says:

    […] Cieślak wrote an excellent intro to LD_PRELOAD; I borrowed the dlsym code from […]

  13. LD_PRELOAD | Luca's Blog! Says:

    […] googled “LD_PRELOAD hacks”, clicked on Dynamic linker tricks: Using LD_PRELOAD to cheat, inject features and investigate programs, and we were up and […]

  14. Hi-Angel Says:

    Thanks!

    Note for readers: if you overload `malloc()` to use `printf()` inside it, it gonna cause segfault. Probably `printf()` is calling malloc, causes infinite recursion and crash. What’s really bad: for some reason even gdb doesn’t handle this situation, it just says `During startup program terminated with signal SIGSEGV`, and leaves you in a lone confusion.

    The solution is to use `fprintf()` instead.

  15. LD_PRELOAD hacks – The Meta Bytes Says:

    […] Some of the content for this post was inspired by (lifted from) this post. […]

  16. New top story on Hacker News: Linker tricks: Using LD_PRELOAD to inject features and investigate programs – ÇlusterAssets Inc., Says:

    […] Linker tricks: Using LD_PRELOAD to inject features and investigate programs 6 by striking | 0 comments on Hacker News. […]

  17. Juha Says:

    Notice the third optional argument to open(2), creation mode. It isn’t passed along.

  18. oleandre Says:

    Taking this one step further, one could use LD_PRELOAD with Frida and write the instrumentation logic in JavaScript:
    https://www.frida.re/docs/gadget/
    This also supports monitoring the .js file for changes and reloading the instrumentation logic live, which is great for game tweaks. Just save the file and instantly see the results.

  19. cron.weekly issue #109: PostgreSQL, GIF, VLC, containerd, Docker, NTP & more Says:

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

  20. Lib managment (Linux) | Pearltrees Says:

    […] might try this tommorow and see what happens P.S. Fakechroot(1): gives fake chroot environment. Dynamic linker tricks: Using LD_PRELOAD to cheat, inject features and investigate programs | Rafał …. This post assumes some basic C skills. Linux puts you in full control. This is not always seen […]

  21. LD_PRELOAD and stealing function calls – Matt's Homepage Says:

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

  22. Snotra Skuld Says:

    It works with the rand function but not with the sinf (from math.h) function. Any idea why ? It’s like my sinf custom function is ignored although my .so file is loaded.

  23. Gururaj Says:

    Thank you for the Great article

    Can you also summarize what are the preventive mechanism to avoid LD_PRELOAD affecting your application with customer functions that impersonate with malicious users?

  24. Gururaj Says:

    Any response for above query?
    Basically we have faced with our application where LD_PRE_LOAD loading the vulnerable library which resets the uid/gid to presume as root (0) causing application non-admin user to impersonate the root privileges and can restore root files by non-admin user.So, any inputs are appreciated to prevent this from occurring.

    • Rafał Cieślak Says:

      You can prevent LD_PRELOAD from having any effect at all by not using a dynamic linker in the first place. That is, by compiling your program into a static binary. This will import all external functions into that binary, and thus it will not be possible to modify their behaviour by tricking the dynamic linker.
      Static binaries come with their own inconveniences, so you may consider the alternative of writing your own getuid() function that refers directly to getuid syscall, and then use that function instead of getuid provided by the standard library. This may be a bit tricky to implement, but your program will stay dynamically linked, and even when stdlib’s getuid is replaced with any other code, your application’s behavior will stay unaffected.
      However, if you’re dealing with a malicious user, neither of these approaches will work. Such user can `ptrace` your program and spoof the getuid syscall with a 0, or even modify the binary code of your program to always consider the value that getuid() returns as 0.
      If your application relies on syscall return values or, well, pretty much anything within it’s own memory to prevent users from privilege escalation, then there is nothing you can do to prevent a skilled user from abusing it. You may need to rethink your application’s architecture to make sure that it’s the kernel that prevents from unauthorized the access to files, and not your app. In some scenarios this could be done by ensuring that root is the owner of said restorable files; but obviously the details depend on the context.

  25. c – 库覆盖信号处理程序,但我需要清理CTRL C - 算法网 Says:

    […] 最佳答案 大多数情况下,你运气不好,除非你发现像 using LD_PRELOAD这样的肮脏技巧来覆盖信号或sigaction行为,当你可以合理地推断你在坏库中时. […]

  26. h Says:

    The a remporté le Super Bowl 1997 avec lecteur des unités spéciales Desmond Howard obtenir le joueur le plus utile de la award.

  27. Louis Burda Says:

    Thank you for the article! Very easy to read and follow along :)
    Please keep up the good work.

  28. Mukul Says:

    I tried this preload() technique with read() and fopen() library functions and it works perfectly, but I’m not able to do it for a sample function that I created say func test() in main.c and then basically trying to wrap it in inject.c where another func test() is declared. Is it possible to do so?

  29. [unix] 0이 아닌 바이트를 얻을 수 있도록 / dev / zero에 비트 마스크를 어떻게 넣을 수 있습니까? - 리뷰나라 Says:

    […] 경우에 그래서, 말 그대로 이 달성하고자하는, 당신은 사용할 수 있습니다 LD_PRELOAD 후크를 . 기본 아이디어는 C 라이브러리에서 함수를 다시 작성하여 일반 함수 대신 […]

  30. 0이 아닌 바이트를 얻을 수 있도록 / dev / zero에 비트 마스크를 어떻게 넣을 수 있습니까? - 질문답변 Says:

    […] 경우에 그래서, 말 그대로 이 달성하고자하는, 당신은 사용할 수 있습니다 LD_PRELOAD 후크를 . 기본 아이디어는 C 라이브러리에서 함수를 다시 작성하여 일반 함수 대신 […]

  31. Linux PrivEsc [TryHackMe] – Revx0r – Security Mindset Blog Says:

    […] out this blog post to read further about LD_PRELOAD abuse.   However, this is not a technique that we can used since […]

  32. THM – Linux Privilege Escalation – Part 15 – aghanim blog Says:

    […] is a function that allows any program to use shared libraries. This blog post will give you an idea about the capabilities of LD_PRELOAD. If the “env_keep” […]

  33. ELF的代理共享库(sharedlib、shlib、so)? – 运维实战侠 Says:

    […] 使用 “代理DLL “的方法 LD_PRELOAD 并不真正涉及到修改一些不属于你的东西,而且注入的东西和普通的动态库加载并没有什么不同。ERESI项目中的 “典型的ELF黑客工具 “与以下内容无关 LD_PRELOAD. 你不应该害怕它。一个好的写作入门 LD_PRELOAD-可 “代理 “是 此处. […]

  34. How can I put a bit mask on /dev/zero so that I can get bytes other than zero? Says:

    […] if you literally want to achieve this, you can use a LD_PRELOAD hook. The basic idea is to rewrite a function from the C library and use it instead of the normal […]

  35. Valgrind on MIPS Reports no Heap Usage Says:

    […] that my toolchain doesn’t support this feature. In order to confirm that it does, I followed this example to create a simple test library and load it (I replaced rand() with a function that just returns […]

  36. ELF的代理共享库(sharedlib、shlib、so)? – 实战宝典 Says:

    […] 使用 “代理DLL “的方法 LD_PRELOAD 并不真正涉及到修改一些不属于你的东西,而且注入的东西和普通的动态库加载并没有什么不同。ERESI项目中的 “典型的ELF黑客工具 “与以下内容无关 LD_PRELOAD. 你不应该害怕它。一个好的写作入门 LD_PRELOAD-可 “代理 “是 此处. […]

  37. TryHackMe WriteUp – Linux Privilege Escalation – René und IT-Sec Says:

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


Leave a comment