Friday, February 14, 2014

Detecting Userland Preload Rootkits


We recently released a new userland rootkit on BHL named Azazel. It's similar to previous versions of Jynx/Jynx2, but is more advanced and focused on anti-debugging and anti-detection methods. This leads us into a current major problem with rootkit detection mechanisms such as rkhunter. Put simply, if you run these tools in a potentially compromised environment, you cannot trust their output. Rkhunter relies primarily on signature detection which is fine for known threats that use default values and file names, but here's a simple detection method that compares the address of syscalls loaded directly from libc, and the "next" address to that system call. By comparing the two values, you can more accurately detect preload based userland rootkits.

#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>

#define LIBC "/lib/x86_64-linux-gnu/"

int main(int argc, char *argv[]) {
 void *libc = dlopen(LIBC, RTLD_LAZY); // Open up libc directly
 char *syscalls[] = {"open", "readdir", "fopen", "accept", "access", "unlink"};
 int i;
 void *(*libc_func)();
 void *(*next_func)();

 for (i = 0; i < 6; ++i) {
  printf("[+] Checking %s syscall.\n", syscalls[i]);
  libc_func = dlsym(libc, syscalls[i]);
  next_func = dlsym(RTLD_NEXT, syscalls[i]);
  if (libc_func != next_func) {
   printf("[!] Preload hooks dectected!\n");
   printf("Libc address: %p\n", libc_func);
   printf("Next address: %p\n", next_func);

 return 0;
$ gcc preloadcheck.c -o preloadcheck -ldl
$ ./preloadcheck
[+] Checking open syscall.
[+] Checking readdir syscall.
[+] Checking fopen syscall.
[+] Checking accept syscall.
[+] Checking access syscall.
[+] Checking unlink syscall.
Now here's an example run against Azazel.
$ LD_PRELOAD=/lib/ ./preloadcheck
[+] Checking open syscall.
[!] Preload hooks dectected!
Libc address: 0x7fe1bf65a890
Next address: 0x7fe1bfb1d932
[+] Checking readdir syscall.
[!] Preload hooks dectected!
Libc address: 0x7fe1bf633c50
Next address: 0x7fe1bfb1dc56
[+] Checking fopen syscall.
[!] Preload hooks dectected!
Libc address: 0x7fe1bf5f46c0
Next address: 0x7fe1bfb1d6c6
[+] Checking accept syscall.
[!] Preload hooks dectected!
Libc address: 0x7fe1bf6676a0
Next address: 0x7fe1bfb1eb4a
[+] Checking access syscall.
[!] Preload hooks dectected!
Libc address: 0x7fe1bf65ab40
Next address: 0x7fe1bfb1d670
[+] Checking unlink syscall.
[!] Preload hooks dectected!
Libc address: 0x7fe1bf65bd50
Next address: 0x7fe1bfb1db58

Note: This is not an end all to preload kits. As we have seen with Azazel, kits are still able to selectively filter results or unhook specific programs to avoid detection. It is also important to note that by hooking's functions themselves, one could even evade this detection mechanism.


  1. Hi,

    I have tested this rootkit on VM and I think I have found a bug. WTMP_FILE_X should be /var/run/utmp and not /var/log/utmp on Keylogger would also be a good feature for this nice rootkit.

  2. Good catch, I pushed a quick update. Thanks!

  3. Hi,

    I have a question about possible false-positives. I performed this check and got:
    [+] Checking fopen syscall.
    [!] Preload hooks dectected!

    Made me very suspicious, but checking both pointers with 'dladdr', I noticed that these symbols seem to be defined in ''. Executing "objdump -tT /lib/i386-linux-gnu/ | grep fopen", I got:
    00066670 g DF .text 00000030 GLIBC_2.1 fopen
    0012e2d0 g DF .text 00000088 (GLIBC_2.0) fopen

    Does this mean that the code you published can give false-positives in this case? And can this check be easily avoided by defining two identical symbols in a preloaded library?

  4. Oops. Please disregard the last sentence in my previous comment, I was thinking about something different.

  5. There is a kernel module that hooks sys_execve and scans envp for the LD_PRELOAD or any other token:

    It may be used for the in-kernel envp scanning engine and can even prevent execution of the binary if some token have been found (though, some modifications needed).

    // milabs

  6. Well,

    extern void *_dl_sym(void *, const char *, void *);

    void * dlsym(void *handle, const char *symbol)
    return _dl_sym(handle, symbol, dlsym);

    I call to _dl_sym() in all hooked functions too.

    It seems that this trick prevents this simple detector.

  7. Hi,

    Directories hidden by this rootkit can be detected by a rather simple method.

    root@debian7:/usr# ls -al | grep "^d"
    drwxr-xr-x 11 root root 4096 helmi 23 15:28 .
    drwxr-xr-x 23 root root 4096 marra 26 22:09 ..
    drwxr-xr-x 2 root root 36864 helmi 22 14:22 bin
    drwxr-xr-x 2 root root 4096 kesä  2 2013 games
    drwxr-xr-x 33 root root 12288 helmi 17 16:36 include
    drwxr-xr-x 56 root root 4096 helmi 22 14:22 lib
    drwxrwsr-x 10 root staff 4096 marra 26 22:08 local
    drwxr-xr-x 2 root root 4096 helmi 22 14:21 sbin
    drwxr-xr-x 107 root root 4096 helmi 22 14:21 share
    drwxr-xr-x 5 root root 4096 marra 26 22:14 src

    The link count should be 10 and not 11. There is a hidden subdirectory. This detection method applies to many rootkits not just Azazel.

  8. inb4 hooks to spoof link count

  9. I've seem to install Azazel correctly...
    sudo apt-get install libpcap0.8-dev libpam0g-dev libssl-dev
    git clone
    cd azazel/
    make && sudo make install
    I don't encounter any errors or indication the injection did not incorrectly install at this point. LD_PRELOAD=/lib/ bash -l
    I also have crypthook made correctly, but when I go to use the backdoors, from my understading I need to restart ssh and LD_PRELOAD=./ ncat localhost 22 -p 61051 , but I get a connection refused. even after stopping sshd. do I need to LD_PRELOAD=/lib/ ssh(d)? is that the proper syntax, or could you nudge me in the right direction? Thank you

  10. Any plans to address post-reboot persistence? - seems that everything other than Debian hangs after reboot with azazel installed.