I always had some strange attraction to rootkits and was thrilled to hear that Crisis had one. This chapter is dedicated to the rootkit implementation, its tricks and how it’s controlled (and its fuckups!).

A small disclosure note about me making fun of Italians on Twitter. I love Italy and have nothing against Italians. We just share some cultural things that I really hate and that’s the reason why I was making fun of Crisis origins and some of its design/features. It’s no coincidence that the South European countries are all in economic trouble 😉.

The rootkit number of features is very small: it can hide processes, files, and itself. Two versions are available for 32 and 64 bit kernels (this post is about the 32 bit version using Snow Leopard). Implementation is very simple and has some flaws that I will describe later.
The main feature that got me interested was hiding itself from kextstat because this needs to be done by modifying the sLoadedKexts array (the old kmod list is not enough anymore since it’s deprecated).
It doesn’t seem an easy job to find the location of this symbol and Crisis kind of cheats in doing it. What happens is that the userland backdoor module will solve the kernel symbols and pass them to the kernel module. Done this way it’s very easy to accomplish, although compatibility with future kernel releases might be in jeopardy if sLoadedKexts is modified.

The main reason for this to be done this way is that __LINKEDIT is removed in Snow Leopard and previous versions so you can’t search the symbol table inside the kernel module. Lion behaves differently – doesn’t remove it but marks this segment as pageable to disk – so it’s possible to solve the symbols inside the kext without any external help. If I’m not mistaken, Mountain Lion maintains this last behavior. The following screenshot shows the rootkit initialization from the userland backdoor:

rootkit initialization

Before continuing with the userland<->kernel interaction let me show you the rootkit startup flow because it helps to understand its design.
The startup function is called mchook_start and does nothing besides creating a character device called /dev/pfCPU. This is the communication channel with the rootkit and contains commands to hide files, transfer solved symbols, stop rootkit activities, etc.
One funny detail is that there’s no identification/authentication/crypto/etc whatsoever, so anyone can send commands to the rootkit using the ioctl() function with the supported requested IDs and parameters. I left a very simple IDC script at github called dump_ioctl_param.idc that will dump all ioctl requests sent by the backdoor module.
You can easily detect if machine is rootkit infected by doing an open() on /dev/pfCPU. If return value is non-negative than the rootkit is most probably installed.

Only three functions from struct cdevsw are implemented, d_open, d_close, d_ioctl. The last one is where all the magic happens – it’s responsible for processing the ioctl requests from the userland module (implemented as cdev_ioctl at address 0xB44).

dev pfcpu and ioctl

Next screenshot is a sample of cdev_ioctl function. The ioctl request 0x207bee7a will hide the rootkit from kextstat and similar tools.

ioctl example

Let’s go back to the userland to demonstrate the first defect in this rootkit. The connection between userland and kernel is initialized by calling the method connectKext. This method sends the request 0x80FF6B26 to the /dev/pfCPU device, and the logon name of the current user as a parameter. Inside the kernel module, the name parameter is truncated to 20 bytes and passed as an argument to function backdoor_init. I did not reversed this function but it seems to use the username as an identifier.

connect to kext

Since there’s no authentication to send ioctl request to the rootkit we can easily test what happens if we send those requests from our own app. This reveals the first big problem – sending again the 0x80FF6B26 request will get the rootkit to show its hidden files and processes. The logon name of the user should be sent as the second parameter, since it’s the “key” to “authentication”. A simple C program to demonstrate this:

#include <sys/ioctl.h>
#include <stdio.h>
#include <fcntl.h>

int main(void)
{
   int fd = open("/dev/pfCPU", O_RDWR);
   if (fd == -1)
   {
        printf("Failed to open rootkit device!\n");
        return(1);
   }
   int ret = ioctl(fd, 0x80ff6b26, "reverser");
   if (ret == -1)
        printf("ioctl failed!\n");
   else
        printf("os.x crisis rootkit unmasked!\n");
}

After a successful connection to the rootkit, the next step is to solve the necessary kernel symbols so the rootkit can do its magic. The method responsible for this is _solveKernelSymbolsForKext at address 0xe056, and supports 32 and 64 bit symbol versions.
It opens and mmaps /mach_kernel, verifies if connection to rootkit is available via the file descriptor to /dev/pfCPU, starts solving symbols and sends them to the rootkit via ioctl calls.

solve symbols head

An example of the first symbol to be solved:

first solved symbol

The ioctl request for 32 bit symbols is 0x807AEEBF and 0x807AEEC0 for 64 bit. One somewhat cute trick is happening here. IDA identified the prototype for rootkit cdev_ioctl function as: int __cdecl cdev_ioctl(int, int command, char *string, int, int). The third argument to ioctl is composed by the hash of the symbol to be solved (0xDD2C36D6 in this case) and the returned symbol address from _findSymbolInFatBinary. The trick is that all addresses of kernel symbols terminate with a null value, so this works. I find it cute.

The kernel side for this is very simple. It matches the hash and then moves the symbol address into a variable. By the way, the symbol hashing algorithm used is the same as in the dropper. The following symbols are solved:

  • allproc
  • tasks
  • nsysent
  • kmod
  • tasks_count
  • nprocs
  • tasks_threads_lock
  • proc_lock
  • proc_unlock
  • proc_list_lock
  • proc_list_unlock
  • _ZN6OSKext21lookupKextWithLoadTagEj (OSKext::lookupKextWithLoadTag)
  • IORecursiveLockLock

After all this, another command is issued 0x807f6b0a which verifies if all symbols were solved and its variables are non-zero.

Next step is to hide the critical rootkit files from ls & friends. Allow me to do justice to my Italian “friends”. I tweeted that there were problems with a fixed space for a N number of files to be hidden. I just double checked and I was mistaken. I was fooled at the time by the rootkit design. The 0x80FF6B26 ioctl request that was used to unmask the rootkit is some kind of session initializer. So, every time you want to send a command to the rootkit you need to initialize the session and send all commands. For example, you need to send the hide file command for all files each time you want to control the rootkit. Contrary to Italian design tradition, this is (in my opinion!) poor design and not a very dynamic rootkit.

Files and folder will be hidden system wide. For example, if zero is configured to be hidden, any files or folders named zero anywhere in the filesystem will be hidden. I would prefer something more precise to increase stealthness. Backdoor module will command the rootkit to hide “com.apple.mdworker.plist”, “jlc3V7we.app”, “pfCPU”, “appleHID”.

The initial interaction of userland module with the rootkit will end with two more requests, one that calls a hide_proc function in the rootkit (I couldn’t understand yet what is the effect, since the parameter is the username), and another to hide the rootkit from kextstat.

REQUEST VALUEARGUMENTSDESCRIPTION
0x207bee7aNoneHide kernel extension
0x407e6b231called from uninstallMeh, ?
0x807aeebfhash and symbol address into a char*transfer solved symbol info, 32 bit
0x807aeec0hash and symbol address into a char*transfer solved symbol info, 64 bit
0x807e7fc2file/dir namehide file or dir
0x807f6b0aos major & minor versionscheck if all symbols were solved
0x807ffb23usernamestop backdoor? unhides procs, removes hooks.
0x80ff6b26usernameinit rootkit session
0x80ff6fdcusernamecalls hide_proc in rootkit?

This ends the interesting stuff from userland<->rootkit interaction. The last point I want to develop in this post is how the rootkit hides from kexstat. Contrary to what I wrote in previous posts (already fixed), Crisis does support Leopard. The rootkit has a function called hide_kext_leopard that removes the rootkit from kmod list – the old and deprecated way to do it. That’s not the interesting function! That role is reserved for hide_kext_osarray.

In Snow Leopard and beyond the kernel modules list is located in sLoadedKexts (an OSArray of OSKext, check libkern/c++/OSKext.cpp from XNU source). There is no symbol for sLoadedKexts so finding it is not straightforward. Snare suggestd to look for OSArrays (defined at libkern/libkern/c++/OSArray.h) and find one full of OSKexts, or work backwards from the kmod that the kext start function gets passed. Another alternative way is to find some piece of stable code and retrieve the address of sLoadedKexts. As I previously stated, this is easier in Lion or newer because we can solve symbols in memory. Crisis uses this trick assisted by the userland module to solve the symbol OSKext::lookupKextWithLoadTag (ZN6OSKext21lookupKextWithLoadTagEj). The code for that method is pretty simple and I assume stable between many versions.

lookup kext

The corresponding disassembly in Snow Leopard 10.6.8 is:

lookup kext disassembly

Crisis starts looking up for the first 0xE8 byte belonging to IORecursiveLockLock call so it can extract the address of sLoadedKexts. One curious thing here is that the symbol IORecursiveLockLock was solved by the userland module but it’s not used or stored inside the rootkit. It could assist in verifying that the right call was found (maybe something they were thinking about but forgot to implement).
With a pointer to sLoadedKexts found it’s a matter of getting the array location inside OSArray class (16 bytes away from symbol address) and start searching for the com.apple.mdworker string. If it’s found than the total kext count is decreased!

hide rootkit

This gives us more ammunition to poke fun at the Italians. Since they are decreasing the total count of kernel extensions what happens if we load another kext? It will f*ck up kextstat and tell us there’s a hidden rootkit! A picture is worth a thousand words:

kext lol

Since we are on a roll, let’s continue to make fun out of the Italian friends. If you attach GDB to the kernel (use the awesome GDB stub described at Snare’s post), and use the Kernel Debug Kit macros (kgmacros), then you can use the showalltasks to display the hidden userland backdoor module. They forgot to hide the Mach task so only the BSD layer is being hidden. I think it may be possible to use the mach* functions to do the same from userland and find the backdoor task – need to do some research on this.

The code to hide files and folders is pretty standard – hook getdirentries* stuff and hide whatever you want, and the same for process hiding – play with allproc LIST and remove whatever you need to.

There isn’t much else to reverse or at least interesting to me regarding the rootkit. Maybe some internal implementation details of its database and some flags but nothing that I think it’s relevant or important (or just too lazy to reverse them).

My conclusion is that someone is paying 200k or whatever high price for this and in return is getting a poorly designed rootkit that can be easily detected and unmasked. That doesn’t necessarily mean that it wasn’t effective in stealing information from someone! I just have high standards and it annoys me to see this kind of badly designed stuff, especially when they probably had enough resources to do something better. The upside of this is that I could make fun out of them 😃.

I was reworking Dino’s uncloak tool presented at BH 2009 to detect the rootkit from userland. It requires a patch to the kernel to enable task_for_pid(0). I need to finish it and probably will talk about it in another post.

That’s it for now. I really enjoyed writing this post and I hope you enjoy reading it (I still need to improve the writing style).

Have fun,
fG!