Tales from Crisis, Chapter 3: The Italian Rootkit Job

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 64bits kernels (this post is about the 32bits 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 latter behavior. The following screenshot shows the rootkit initialization from the userland backdoor:

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”, 0xB44).

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

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 chars 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.

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” (0xe056), which supports 32 and 64bits 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.

An example of the first symbol to be solved:

The ioctl request for 32bits symbols is 0x807AEEBF and 0x807AEEC0 for 64bits. 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 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 value Arguments Description
0x207bee7a None Hide kernel extension
0x407e6b23 1 called from uninstallMeh, ?
0x807aeebf hash and symbol address into a char* transfer solved symbol info, 32 bits
0x807aeec0 hash and symbol address into a char* transfer solved symbol info, 64 bits
0x807e7fc2 file/dir name hide file or dir
0x807f6b0a os major & minor versions check if all symbols were solved
0x807ffb23 username stop backdoor? unhides procs, removes hooks.
0x80ff6b26 username init rootkit session
0x80ff6fdc username calls 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+ 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.

The corresponding disassembly in Snow Leopard 10.6.8 is:

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!

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:

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 a little research on this.

The code to hide files and dirs 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!

P.S.: No good story is without bonus contents! I present you the worldwide exclusive to first Snare’s presentation draft, featuring some missing rootkit related scenes not available in the final version. Many thanks to Snare for this great bonus content. Grab it here.

4 thoughts on “Tales from Crisis, Chapter 3: The Italian Rootkit Job

  1. Great, Great!!!
    You finally revealed Crisis’s trick to find sLoadedKext and hide kext!!!
    Today, I learned useful trick : ), Thank you fG!

  2. Thanks I enjoyed reading this :). 200k is serious business, I’m guessing Crisis was a targeted attack, any ideas about victims?

    why not go final step and remove the ktext from sLoadedKext like snare’s example….lazy devs

    1. There aren’t many clues about who was the target, C&C went down asap so no way to even try to hack it back heheeh
      Snare’s slides weren’t available before so they probably didn’t even notice to remove it. Poor quality control!

Leave a Reply

Your email address will not be published. Required fields are marked *