Introduction
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/libc.so.6" 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/libselinux.so ./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 libdl.so's functions themselves, one could even evade this detection mechanism.
Hi,
ReplyDeleteI 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 config.py. Keylogger would also be a good feature for this nice rootkit.
Good catch, I pushed a quick update. Thanks!
ReplyDeleteHi,
ReplyDeleteI 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 'libc.so.6'. Executing "objdump -tT /lib/i386-linux-gnu/libc.so.6 | 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?
Oops. Please disregard the last sentence in my previous comment, I was thinking about something different.
ReplyDeleteThere is a kernel module that hooks sys_execve and scans envp for the LD_PRELOAD or any other token:
ReplyDeletehttps://github.com/milabs/kmod_hooking/tree/hook-execve
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
Well,
ReplyDeleteextern 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.
Hi,
ReplyDeleteDirectories 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.
inb4 hooks to spoof link count
ReplyDeleteI've seem to install Azazel correctly...
ReplyDeletesudo apt-get install libpcap0.8-dev libpam0g-dev libssl-dev
git clone https://github.com/chokepoint/azazel.git
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/libselinux.so 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=./crypthook.so ncat localhost 22 -p 61051 , but I get a connection refused. even after stopping sshd. do I need to LD_PRELOAD=/lib/libselinux.so ssh(d)? is that the proper syntax, or could you nudge me in the right direction? Thank you
Any plans to address post-reboot persistence? - seems that everything other than Debian hangs after reboot with azazel installed.
ReplyDelete