Here, you will learn what a rootkit is and how does it work. Also you will find an attack using DKOM.
For this article I’m using:
- Windows XP SP3
- WDK Windows Driver Kit
- Some debuggers: WinDbg, DebugView
- Coffee and good cakes
1.1. What is a Rootkit?
First and foremost, a rootkit is not a new concept. Indeed, they use the same techniques as viruses in the late 1980s like: modifying program logic, key system tables, memory, for example. But also, it is a way to keep a privileged access on a computer “leaving no traces”.
It is not a virus, or an exploit, it a “set of programs and code that allows a permanent or consistent, undetectable presence on a computer”. To say it differently, it is a technique to hide malicious things on your victim’s system from an anti-virus, a firewall, a forensic tool and so on.
A virus is a self-propagating automaton, it makes copies of itself and is out of control. On the other hand, a rootkit enables attackers to have an entire control. Moreover, a rootkit will be deployed thanks to a software exploit, for example: we can load it into the kernel after a buffer-overflow exploit. But most of the time, the attacker uses Social Engineering or install it physically.
The concept of rootkits evolved with the time to response to new protections and difficulties.
1.2. Three generations of Rootkits (and more)
First generation rootkits were very primitives. They were just backdoor programs that replace files system binaries to hides files and processes, like for example: “dir” on Windows and “ls” on Linux. So using this method, a malicious user could hide some suspicious files like a repertory named “Hack_the_Planet”[jff_1] and the victim was only able to see:
Another example is the UNIX login program which was commonly replaced by a kind of spyware which logged user passwords. But as we could see, these rootkits were limited to system files on disk and came the time of system integrity checkers such as Tripwire.
The response of this was to move into the computer kernel. The second generation rootkits were based upon hooking that altered execution path and some operating system components such as system calls, according to the Shadow Walker paper. But these techniques remained detectable by searching for heuristic abnormalities (see VICE).
To finish, for the third generation of rootkits, you can look for the FU Rootkit, which has implemented techniques like Direct Kernel Object Manipulation (DKOM). This response show the weakness of current detection software. Indeed, it is impossible to establish a static trusted baseline by modifying kernel data structures.
You can also find other generation of rootkits: HVM, Firmware, SMM and so on.
1.3. Attackers’ Motives
Attackers want to penetrate a system to gather some important information from a computer or simply destroy a computer putting a kind of “logic bomb”. The first one is more interesting for us, because the role of rootkits is to hide every action leaving no trace. So it has do be undetectable from anti-viruses and forensic tools.
It is very similar to viruses which attempt to hide on the file system from memory scanners, using polymorphic and metamorphic techniques. But in fact, rootkits do not need to change their form, they just compromise everything, hiding any modified region of the memory from scanners giving a ‘fake’ view of the memory.
So now we know rootkits are capable of controlling the view of memory management, we imagine we can keep the control of a system giving the illusion of a safe and clean system, and it is true.
1.4. How a rootkit can be used ? Is it legal ?
Rookits are commonly designed to a particular OS. It can be generic but it is limited to the family of the OS when they have the same data structure and behavior. For example it will be possible to affect Windows family OS like Windows NT, 2000, 2003 and XP. But if we would like to create a generic rootkit for both Linux and Windows at the same time for example, it will not be possible depending to the data structure and behaviour.
The use of rootkits can be legitimate. Indeed, for many reasons people use them like the famous firmware rootkits known as CompuTrace or LoJack for Laptops, to recover stolen laptops by tracing them across the Internet (can be turned to malicious purposes ).
On the other side, it can be used for malicious reasons, but a rootkit itself is legal. In fact, like in cracking, the illegal thing is the modification of copyrighted softwares. Because rootkits work with the concept of “modification, there are many ways to modify a software: patching, easter eggs, spyware modifications, source-code modifications, updater source-list & packages modification. For the last one, see the slides about the Malware Injection Lightweight Framework (MILF) by Julien Reveret .
OS components attacked by Rootkits are:
- I/O Manager: Logging keystrokes or network activity.
- Device & file system drivers: Hiding files.
- Object Manager: Hiding process/thread handles.
- Security Reference Monitor: Disable security policies.
- Process and thread manager: Hiding processes and threads.
- Configuration manager: Hiding registry entries
2. Subverting the kernel
2.1. Intel X86 Protection Rings
Modern OS are interrupt driven. The system is always waiting for something to do: process execution, I/O devices to services, respond to users. Events are signaled by an interrupt or a trap. A trap is a software generated interrupt caused by an error (For example: invalid memory access, division by zero). Unlike MS-DOS written for the Intel 8088 architecture, most contemporary OS such as Windows XP/Vista/Seven, Unix, Linux take advantage of two separate modes of operation : UserLand (Ring3) and KernelLand (Ring0).
Sometimes applications need more privileged accesses to resources, this can be done by interfacing the kernel using system calls (figure 1).
Figure 1. Transition from UserLand to KernelLand.
In Intel x86 family, there are four rings used for access control. The Ring0 is the most privileged and Ring3, the least privileged (figure 2). Actually, Windows and Linux only use Ring0 and Ring3 and the Ring1, Ring2 may be used, but those operating system architecture do not need their use. The kernel code runs in Ring0. A Ring0 program will have accesses to virtual memory, hardware, and so on.
Figure 2. The rings of Intel x86 processors.
Ring3 program cannot access to Ring0 program and if we try to have an access, it will result with an interrupt (terminates the program abnormally). A bit of code controls the access restriction and there is also a code that allows a program to access in Ring0 under special circumstances.
2.2 Write your first device driver
Here we will learn how to make a kernel driver (or device driver), which will be our first kernel extension. We know that if we have a code running in the kernel, we can modify the code and data structures of any software on the computer.
A module includes an entry points and a cleanup routine, if we want to load newer versions of our rootkits.
2.2.1. Get your hands dirty
We can now begin with a file mydriver.c:
DbgPrint() is like printf() in C but only visible on DebugView.
To start your first project, make sure to place mydriver.c in a clean directory (For exemple: “C:\myRooktit”).
Create now a file named SOURCES (in all-capital letters and no file extension). The SOURCES file should contain this code:
TARGETNAME gives a name for you driver.
TARGETPATH is usually set to OBJ.
SOURCES precises what we have to compile. We can specify more than one source with backslashes “\”.
Optionally, you can add the INCLUDES variable which specifies where included files will be located:
2.2.2. Build it
To build your project, make sure you have installed the Windows Driver Kit (WDK) before. Now go to the Start Menu → Programs → Windows Driver Kits and find the Checked Build Environment for your OS.
After that, change the active directory to your rootkit directory “myRootkit” and type the command “build”:
This environment is very similar to GCC, it is very easy to check errors. The checked build results in debugging checks compiled into your driver. When you have finish, to release your final rootkit, use the “Free Build environment”.
After that you should see a “mydriver.sys” file which is our driver in a generated subdirectory.
2.2.3. Load the driver: Easy way
First of all, download the debugger “Debug View” for Windows and start it.
To load your driver, it is very easy beginning with “OSR Driver Loader”. You just have to select the correct Driver Path and click to “Register Service” to register the driver, then click to “Start Service”.
You should now get something like this :
Figure 3. Debugging for MYDRIVER
The debugger shows you the messages of the function DbgPrint() when you load and unload your driver.
2.3. Communication between User and Kernel modes
2.3.1. I/O Request Packets
Nowadays, rootkits use both User and Kernel mode, but how does it work ? Precisely they can communicate through a variety of means. One of the most common we will use is the I/O Control (IOCTL) commands. In Windows, to communicate a device driver needs I/O Request Packets (IRPs). IRPs are buffers of data and a user can open a file handle to read and write to it.
If we look at the structure DRIVER_OBJECT with WinDbg, there is a member named MajorFunction:
Major Functions are used to handle the IRPs and are marked with the defined values :
* … and so on
For example, if we handle a CREATE, READ and CLOSE event, these events are triggered when a user-mode program case CreateFile(), ReadFile(), CloseHandle() with a handle to the driver (Initialized with CreateFile).
For some Major Functions, we specify a function each, that will be called:
Then we implement the functions OpenFunction(), CloseFunction(), ReadFunction() and WriteFunction() with a DebugPrint:
2.3.2 File handle in kernel
In order to communicate between user and kernel modes, we must open a handle to the driver. However, if we do not register a named device, it will be impossible to open a handle. So what we are going to do is to define a device and create it:
To make life easier for programmers in userLand to find the device, we can create a symlink as follows:
With this Symbolic link, we can open a handle using the string “\\.\myDevice”.
2.3.3. The UserLand
To finish we need to write a program which will open the device and send a control code to our device driver (which will be controlled to perform different actions).
As we can see CreateFile() opens the device “myDevice” and by DeviceIoControl() we send the ponter for input (welcome) and output (out).
To finish build the program using “gcc” and execute with the Windows command line: gcc -o userland.c userland.
You should see something like this with DbgView:
2.4. Load a driver properly
2.4.1 What we want to do here?
Imagine you are an attacker and you would like to install a rootkit on your victim’s computer. To use it this rootkit as to be loaded and started, and we saw how to do it with OSR Driver Loader. But really, do think you will say “Hello victim! I’m your attacker and I want you to load my driver, so download OSR Driver Loader, load the rootkit for me and start the service.” ? No! We have to be as less suspicious as we can.
It will be much more interesting if we embed an loader in the user land part, right ? So, we will use the SCM to do it.
2.4.2 The Service Control Manager (SCM)
If we use an API call to load our driver without having to create any register keys, the driver will be pageable and any part of it can be swapped to disk. Using this method, it will result in a super Blue Screen of Death (BSOD)
The SCM cause registry to be created and in this way the driver will be “non-pageable”.
First, we got to establish a connection with the service manager using OpenSCManager, and specify we want all accesses into it :
After that, we have to create the registry key :
(Please note: You can change the Start Type to let your rootkit start at boot time).
To finish, we open the created service and start it :
3. Hide processes with DKOM
3.1. Direct Kernel Object Manipulation
Direct Kernel Object Manipulation (DKOM) is extremely hard to detect, but has also its drawbacks and we must understand several things about the object (Chapter 7 ).
With DKOM we can:
* Hide processes
* Hide device drivers
* Hide ports
* Elevate privilege level of threads and processes
* Skew forensics
But as we manipulate objects in memory, it is not possible to hide files for example. So it has also some limitations.
3.2. Process Hiding
Windows operating systems list active processes using a double linked structure in EPROCESS:
As you can see, it is like a doubly-linked list in C programming , when you have a previous and a next node.
So now in theory, if an active process is referenced into a EPROCESS structure, then we just have to point the Front LINK (FLINK) in the back of the process we want to hide to the next structure, and point the Back LINK (BLINK) of this next structure to the previous structure as follows:
That was theory, but in practice, it really needs a good understanding of EPROCESS structure. Indeed, it change often between releases, so to get a correct offset any “field”, we will use WinDbg.
In this example on Windows XP SP3, the offset for the PID is 0x84, for the doubly-linked list it is 0x88. With this information, you can now program a function which change the FLINK and BLINK pointers of a precise process using his PID. But who really knows the PID of a specific process when it is being executed? The EPROCESS structure has this information as know as “ImageFileName” at 0x174.
We will use IoGetCurrentProcess() to get the pointer of the current process and use the doubly-linked list to switch from a process to the next with his FLINK. Then we compare the value of “ImageFileName” with the process name we are searching to get his address:
Then with the process' address we can now hide it, using the same theory about BLINK and FLINK as at the beginning of this part:
Note that if you are using another version of Windows, you got to apply a valid offset to make it work.
Here I'm using the software Process Explorer  to watch all active processes in memory:
So imagine we would like to hide a very dangerous* process like “explorer.exe” (*joke). We use the userland to tell to the kernel that we want to hide “explorer.exe”:
And it disappeared in the wild!