The Magic of LD - PRELOAD For Userland Rootkits
The Magic of LD - PRELOAD For Userland Rootkits
The Magic of LD - PRELOAD For Userland Rootkits
How much can you trust binaries you are running, even if you had analyzed them before compilation? With less privileges than kernel
rootkits (explained in Ring 0f Fire), userland rootkits still represent a big threat for users. To see it, we will talk about an interesting
technique to hook functions that are commonly used by programs on shared libraries.
First and foremost, we will introduce quickly the use of shared libraries to explain in the second time, the need of LD_PRELOADs trick.
After that, we will see how to apply it for rootkit, its limits and the case of its detection, that is not surprising with some anti-rootkits.
Prerequisites:
Basics in Linux and ELF (read the analysis part of my last article),
a Linux,
a survival skill in C programming language,
your evil mind switched on (or just be cool!),
another default song: Ez3kiel Via continium.
Here is the contents:
Shared libraries,
LD_PRELOAD in the wild,
Make and use your own library,
dlsym: Yo Hook Hook And A Bottle Of Rum!,
Limitations,
Userland rootkit,
Jynx-Kit,
Detection,
http://fluxius.handgrep.se/2011/10/31/the-magic-of-ld_preload-for-userland-rootkits/
1/8
22/10/2016
Shared libraries
As we should know, when a program starts, it loads shared libraries and links it to the process. The linking process is done by ld-linuxx86-64.so.X (or ld-linux.so.X for 32-bits) (Remember The Art Of ELF?), as follows:
0x000000000000001c 0x000000000000001c R
#include <stdio.h>
main()
{
printf("huhu la charrue");
}
Compile it now in dynamic and in static:
2/8
22/10/2016
update libraries and still support programs that want to use older, non-backward-compatible versions of those
libraries,
override specific libraries or even specific functions in a library when executing a particular program,
do all this while programs are running using existing libraries.
Shared libraries have a special convention, which is the soname. soname have a prefix lib, followed by the name of the library, then
.so and a period + a version number whenever the interface has changed (has you can see on previous listings).
#define _GNU_SOURCE
#include <stdio.h>
int printf(const char *format, ...)
{
exit(153);
}
Now we have to compile[2] this code into a shared library as follows:
http://fluxius.handgrep.se/2011/10/31/the-magic-of-ld_preload-for-userland-rootkits/
3/8
22/10/2016
[...]
typeof(printf) *old_printf;
[...]
/*
DO HERE SOMETHING VERY EVIL
*/
old_printf = dlsym(RTLD_NEXT, "printf");
[...]
After that, we need to format the string passed in argument and call the original function with this formatted string (huhu la charrue), to
be shown as expected:
int
{
va_list list;
char *parg;
typeof(printf) *old_printf;
// format variable arguments
va_start(list, format);
vasprintf(&parg, format, list);
va_end(list);
We compile it:
4/8
22/10/2016
fluxiux@handgrep:~$ ./toto-dyn
huhu la charrue
Wonderful! A user cannot expect that something evil is going on, when executing his own program now. But there are some limitations
using the LD_PRELOAD trick.
Limitations
This trick is very good but limited. Indeed, if you try with the static version of toto (toto-stat), the kernel will just load each segment to
the specified virtual address, then jump to the entry-point. It means that there is no linking process done by the program interpreter.
Moreover, if the SUID or SGID bit is set to 1, the LD_PRELOAd will not work for some security reasons (Too bad!).
For more informations about LD_PRELOAD, I suggest you to read the article of Etienne Dubl[3] (in French), that inspirited me a lot to
make this post.
Userland rootkit
Jynx-Kit
About 2 weeks ago, a new userland rootkit[4] have been introduced. This rootkit came with an automated bash script to install it easily
and is undetected by rkhunter and chkrootkit. To know more about that, we will analyze it.
[...]
old_fxstat = dlsym(RTLD_NEXT, "__fxstat");
old_fxstat64 = dlsym(RTLD_NEXT, "__fxstat64");
old_lxstat = dlsym(RTLD_NEXT, "__lxstat");
old_lxstat64 = dlsym(RTLD_NEXT, "__lxstat64");
old_open = dlsym(RTLD_NEXT,"open");
old_rmdir = dlsym(RTLD_NEXT,"rmdir");
old_unlink = dlsym(RTLD_NEXT,"unlink");
old_unlinkat = dlsym(RTLD_NEXT,"unlinkat");
old_xstat = dlsym(RTLD_NEXT, "__xstat");
old_xstat64 = dlsym(RTLD_NEXT, "__xstat64");
old_fdopendir = dlsym(RTLD_NEXT, "fdopendir")
old_opendir = dlsym(RTLD_NEXT, "opendir");
old_readdir = dlsym(RTLD_NEXT, "readdir");
old_readdir64 = dlsym(RTLD_NEXT, "readdir64")
[...]
http://fluxius.handgrep.se/2011/10/31/the-magic-of-ld_preload-for-userland-rootkits/
5/8
22/10/2016
Randomly, have look to the open function. As you can see a __xstat is performed to get file informations:
[...]
struct stat s_fstat;
[...]
old_xstat(_STAT_VER, pathname, &s_fstat);
[...]
After that, a comparison informations like Group ID, path, and ld.so.preload that we want to hide. If these informations match, the
function doesnt return any result:
[...]
Detection
Surprising (or not), but this rootkit is undetected by rkhunter and chkrootkit. The reason is that these two anti-rootkit check for signs, and
as we should know, this is not the best to do.
Indeed, for example, just clean the LD_PRELOAD variable and generate a sha1sum of toto, as follows:
Exactly! We didnt modified anything in the ELF file, so the checksum should be the same, and it is. If anti-rootkit like rkhunter work like
that, the detection must fail. Other techniques are based on suspicious files, signs and port binding detection like in chkrootkit, but they
failed too, because this type of rootkit is very flexible, and in Jynx we have a sort of port knocking to open the remote shell for our host.
To avoid these rootkits, you could check for any suspicious library specified in LD_PRELOAD or /etc/ld.so.preload. We know also that
dlsym can be used to call the original function while altering it:
http://fluxius.handgrep.se/2011/10/31/the-magic-of-ld_preload-for-userland-rootkits/
6/8
22/10/2016
$ strace ./bin/ls
[...]
open("/home/fluxiux/blabla/Jynx-Kit/ld_poison.so", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\n\0\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=17641, ...}) = 0
mmap(NULL, 2109656, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5e1a586000
mprotect(0x7f5e1a589000, 2093056, PROT_NONE) = 0
To finish, there is also some interesting forensic tools that compare results with many techniques (/bin/ps output against /proc,
procfs walking and syscall). Indeed, Security by default has provided a special analysis on Jynx-kit[5] that made me discover
Unhide[6], that checks if there are no hidden processes and opened ports (brute-forcing all available TCP/UDP ports).
http://fluxius.handgrep.se/2011/10/31/the-magic-of-ld_preload-for-userland-rootkits/
7/8
22/10/2016
This entry was posted in elf, Reversing, Rootkit and tagged elf, reversing, rootkit, userland. Bookmark the permalink.
The way using Unhide is great because there are six methods to check for processes and thats better than checking for checksum or
known infections like chkrootkit do. Great tool! =) But also if the attacker wants to, he can also hook for their six methods.
Reply
Pingback:
Pingback:
Pingback:
http://fluxius.handgrep.se/2011/10/31/the-magic-of-ld_preload-for-userland-rootkits/
8/8