Discussion 8 (12)
Chapter 8. Kernel-Mode RootKits
It's now time to take the boxing gloves off and watch how some bad guys fight a bare-knuckled brawl for the very heart of the operating system: the kernel itself. In the last chapter, we focused on user-mode RootKits, which manipulated or even replaced user-level programs, such as the secure shell daemon (sshd) or Windows Explorer GUI. Now, we'll turn our attention to a more sinister attack vector. As you no doubt recall, we use the following definition to describe RootKits:
RootKits are Trojan horse backdoor tools that modify existing operating system software so that an attacker can gain access to and hide on a machine.
Using the techniques we'll cover in this chapter, attackers employ these RootKit techniques inside the operating system kernel. Kernel-mode RootKits are still RootKits, in that they modify existing operating system software (the kernel), letting an attacker gain access and hide on a machine. The goals are still the same, but the means are much nastier. Because they target the kernel itself, kernel-mode RootKits undermine the victim machine more completely and efficiently than user-mode RootKits ever could.
What Is the Kernel?
Before we get ahead of ourselves, let's take a look at the kernel's role in the operating system. In most operating systems, including UNIX and Windows, the kernel is special software that controls various extremely important elements of the machine. As illustrated in Figure 8.1 , the kernel sits between individual running programs and the hardware itself. Performing various critical housekeeping functions for the operating system and acting as a liaison between user-level programs and the hardware, the kernel serves a critical role. Many kernels, including those found in UNIX and Windows systems, include the following core features:
Process and thread control. The kernel dictates which programs run and when they run by creating various processes and threads within those processes. A process is nothing more than some memory allocated to a running program, and the threads are individual streams of execution within a process. The kernel orchestrates various processes and their threads so that multiple programs can run simultaneously and transparently on the same machine.
Interprocess communication control. When one process needs to send data to another process or the kernel itself, it can utilize various interprocess communication features of most kernels to send signals and data.
Memory control. The kernel allocates memory to running programs, and frees that memory when it is no longer required. This memory control is implemented in the kernel's virtual memory management function, which utilizes physical RAM and hard drive space to store information for running processes.
File system control. The kernel controls all access to the hard drive, abstracting the raw cylinders and sectors of the drive into a file system structure.
Other hardware control. The kernel manages the interface between various hardware elements, such as the keyboard, mouse, video, audio, and network devices so various programs can utilize them for input and output operations.
Interrupt control: When various hardware components of the machine
need attention (e.g., a packet arriving on the network interface) or a program encounters an usual event (e.g., division by zero), the kernel is responsible for determining how to handle the resulting interrupts. By taking care of the interrupt itself using kernel code or sending information to a particular process to deal with it, the kernel keeps the system operating smoothly.
Figure 8.1. A high-level view of an operating system kernel and its relationship to user-level processes and hardware.
With these features, the kernel is all about control: sitting at the interstices of user programs and hardware and controlling what happens on the machine.
As it runs, the kernel relies on hardware-level protections implemented in the system's CPU. By using hardware-level protection, the kernel tries to safeguard its own critical data structures from accidental or deliberate manipulation by user-level processes on the machine. Most CPUs include hardware features to let software on the system run at different levels of privilege. The memory space and other elements of highly sensitive software (like the kernel) cannot be accessed by code running at a less-important level (e.g., user processes). On x86-compatible CPUs, these different sensitivity levels are called rings, and range from Ring 0, the most sensitive level, to Ring 3, the least sensitive level. As it runs different tasks, the CPU switches between these different levels depending on the sensitivity of the particular software currently executing.
For the Linux and Windows operating systems, only Rings 0 and 3 are used; the other options supported by x86 CPUs (i.e., Rings 1 and 2) are not utilized. The kernel itself, in both Linux and Windows, runs in Ring 0. In fact, running in Ring 0 defines a given task as being at kernel level. If you run in Ring 0, you can access all of the kernel's memory structures, and are therefore at the same level as the kernel code. User mode processes run in Ring 3, and, under most conditions, are not able to access kernel space directly. By relying on
Ring 0 and Ring 3, all software on the machine is really carved up into two different worlds: kernel mode (running in Ring 0) and user mode (running in Ring 3). For non-x86 CPUs, operating systems utilize analogous concepts to Ring 0 and Ring 3 implemented in the CPU's hardware. Nearly all CPUs support some notion of a privileged mode, where the kernel lives, and a nonprivileged mode for user processes. Throughout the rest of this chapter, we'll use x86-specific terminology Ring 0 and Ring 3, as it so dominates literature on this topic.
So, your operating system really consists of two worlds: user mode and kernel mode. The user mode is what you typically see and interact with on a day-to-day basis on your system, as it includes the programs you run, such as a command shell, GUI, mail server, or text editor. The other world, kernel mode, lies silently underneath the whole operation managing access to the hardware and generally controlling things. When a system boots up, the kernel is loaded into memory and begins execution in Ring 0, thereby creating the first world (kernel mode). After the kernel gets itself set up in memory, it activates various user-mode processes that allow individual users to access the system and run programs, thereby creating the user-mode world.
It's important to note that kernel mode is a very different concept from root or administrator permissions. When an administrator runs a command, a given program executes within user mode; that is, in Ring 3. From the kernel's perspective, the administrator is just another user, albeit an important one, but still someone living in Ring 3.
When most programs run, control sometimes has to pass from user mode into kernel mode, such as when the program needs to interact with hardware for printing to the screen, receiving a packet, or some other action. When this happens, control is very carefully passed from user mode to kernel mode, through tightly controlled interfaces. The software that implements this transition from Ring 3 to Ring 0 is referred to as a call gate, as it acts as a gate for user-mode processes into software living in kernel mode.
When administrators ask for a list of running programs using tools like the UNIX ps, lsof, or top commands or the Windows Task Manager, they execute a command from user mode, which asks the kernel to list all running processes. The kernel grabs data from its kernel-mode data structures, responds to the user-mode command with the appropriate information, and the running processes are displayed. Similarly, the administrator or users might ask for a list of files in a directory. The kernel responds with the appropriate information. Or, you could look for which TCP or UDP ports are in use, or whether the network interface is in promiscuous mode. You might even run a file integrity checker to see if any of your critical system files have been
altered with a user-mode RootKit. All of these interactions, and far more, rely on the kernel to determine the status of the machine. That's how it's all supposed to work. The kernel takes care of business, and everyone is happy.
Neo: This isn't real…
Morpheus: What is "real"? How do you define "real"? If you're talking about what you can feel, what you can smell, what you can taste and see, then "real" is simply electrical signals interpreted by your brain…
Dialogue from the movie The Matrix, 1999
What happens if some bad guy starts manipulating the kernel itself? Because the kernel is all about control, by modifying the kernel, an attacker can change the system in a fundamental way. To apply changes to the kernel, the attacker first requires superuser privileges on the machine. To manipulate the kernel, root-level access is needed on UNIX machines, and administrator or system access is required on Windows systems. Once installed, a kernel-mode RootKit replaces or modifies components of the kernel. These alterations might make everything on the system appear to be running perfectly well, but the operating system is really rotten to the core. The attacker can change the kernel so that it lies about the status of the machine.
For example, the administrator might run a command looking to see if any backdoor processes are running. This command calls the kernel to get a list of running processes. However, the bad guy changed the kernel so that it lies, and doesn't show the attacker's backdoor process, as illustrated in Figure 8.2 . Alternatively, an administrator might run a file integrity checker to see if some critical files on the machine have been changed. The deceiving kernel tells the administrator that no files have been altered; everything looks wonderful.
Figure 8.2. Manipulating the kernel to hide processes.
Using kernel manipulation, the attackers can alter the kernel so that it thoroughly hides the attacker's activities on the machine. Most kernel-mode RootKits include the following types of subterfuge:
File and directory hiding. Most kernel-mode RootKits hide files and directories from users and system administrators. When a file is hidden, the kernel will lie to any program that comes looking for the file.
Process hiding. By hiding a process using a kernel-mode RootKit, the attacker can create an invisible backdoor that cannot be discovered using process analysis tools.
Network port hiding. By hiding listening TCP and UDP ports so that local programs cannot see them, the bad guy's backdoor is even stealthier.
Promiscuous mode hiding. The attacker doesn't want an administrator to detect a sniffer running on the box in promiscuous mode, so most kernel-mode RootKits lie about the promiscuous status of the network interface.
Execution redirection. With this feature of many kernel-mode RootKits, when a user or administrator runs a program, the kernel pretends to run the requested program. However, the kernel really substitutes a different program in a bait-and-switch maneuver. Users and system administrators think they are running one program, but are really executing some other program of the attacker's choosing. For example, instead of relying on user-mode RootKit techniques to replace the secure shell daemon (sshd) on a victim machine, with a kernel-mode RootKit, an attacker can just redirect execution of the sshd executable to another version with a
backdoor. The administrator can even check the integrity of the sshd file. However, the file will look completely intact, because it is intact. However, when a user or administrator tries to execute the sshd file by remotely logging in, the backdoor version will be executed, giving the bad guy remote access to the victim machine.
Device interception and control. Using a kernel-mode RootKit, an attacker can intercept or manipulate data sent to or from any hardware device on the machine. For example, a bad guy could modify the kernel to record any keystrokes typed into the system in a local file on the machine, thereby implementing a very stealthy keystroke logger [1] . Alternatively, attackers have implemented kernel alterations that let them spy on users' terminal sessions (TTYs), observing and even injecting keystrokes, as well as the responses generated by the system [2] .
Think about this from the attacker's point of view. With a user-mode RootKit, like those we covered in Chapter 7 , the attacker has to break into the box and modify a bunch of programs to hide and implement a backdoor. On a UNIX system, the attacker might break in, start up a backdoor shell listener, and then use a tool like URK to replace ps, ls, netstat, and several other commands. The attacker then has to run the fix routine to set the modification dates and file lengths of these commands to the appropriate values. Then, the drudgery continues as the attacker configures the various hiding components and backdoors of URK. After all of this tiring work, the attacker still has to worry about a suspicious system administrator showing up with a CD-ROM full of statically linked binaries, such as Bill Stearns' static tools for Linux at www.stearns.org/staticiso , which won't lie about the system state. These user-mode RootKits are a lot of work, and aren't very stealthy if the administrators bring their own programs on a CD.
However, with a kernel-mode RootKit, the whole equation changes in favor of the attacker. Instead of modifying a bunch of individual programs, the attacker modifies the underlying kernel that these programs all rely on. To hide a file, the bad guy won't change ls, find, du, and other commands. Instead, the attacker just modifies the kernel so that it lies to any particular command or program run by the administrator looking for that file. In this way, kernel-mode RootKits are far more efficient for the attacker.
With a kernel-mode RootKit, the attacker morphs the system so that administrators and users are in a prison, but don't even realize it. You might think you are running certain programs or looking at the status of your machine, but you don't know that you are viewing a fantasy concocted by the attacker and implemented with a kernel-mode RootKit. What you see is not
really your operating system, but only a dream world designed to hide you from the truth: the truth that your operating system is really completely owned by the attacker. Without even being aware of your prison, you blithely go on living your life, managing your system, and unwittingly letting the attackers control everything.
Have you ever seen the movie The Matrix? If you haven't, I'll be careful not to give away any spoilers for those few souls who haven't yet seen the movie or its sequels. For those who have seen it, the movie provides some excellent illustrations that help make the ideas behind kernel-mode RootKits more concrete. You know, some people have compared The Matrix to the ultimate Rorschach test. Looking into and interpreting the meaning of the inkblot that is The Matrix really reveals your own philosophy and worldview. Some fans think the movie is about Buddhism, Christianity, Gnosticism, Hinduism, Islam, or Judaism. Others think it's a great flick about martial arts or firearms. But I'm here to tell you what The Matrix is really all about: kernel-mode RootKits.
In the movie, some pretty evil beings manipulate their victims so that they are wired into a virtual reality simulation that looks like the real world. With their brains wired into the Matrix, the victims believe they are living normal lives, paying their taxes, going to church, and taking out their landladies' garbage. However, the victims are really lying in pods full of pink goo, completely unaware of their real physical circumstances. The virtual reality image of their lives is merely a mirage, designed to enslave the victims so that the evil beings could use their resources. With a kernel-mode RootKit, you think you are looking at your real system, but the attackers have altered the kernel so that they can use your system resources without your knowledge. You might not realize it, but, with a kernel-mode RootKit, your computer is living a lie. Your computer is an attacker-controlled Matrix and you are unknowingly trapped inside.
Because various operating system kernels vary so significantly, we'll break the remainder of this chapter into two sections. First, we'll look at the Linux kernel and how bad guys manipulate it, and then we'll address Windows kernel-mode RootKits in the latter half of the chapter.
Keep in mind that for each of the concepts and attacks we discuss for Linux and Windows, analogous ideas apply to other operating systems. Given the differences in the kernel implementations of various UNIX variants (and our desire to keep this chapter under 200 pages), we need to pick one specimen from the UNIX world to analyze in more detail. We'll focus on Linux as one of the most common representatives of UNIX and UNIX-like operating systems. In addition to Linux, we'll look at the Windows kernel because of its widespread deployment and popularity as a target for kernel-mode RootKits. However,
keep in mind that similar kernel-mode RootKit concepts have been implemented for other operating systems, including Solaris [3] , FreeBSD [4] , and others. By analyzing the details of kernel attacks on Linux and Windows, we can not only understand how they work in detail on the most popular platforms, but also get a high-level view of similar techniques that are used against other systems.
Way back in the heady days of 1991, Linus Torvalds started the project that created the Linux kernel. Today, Torvalds still heads the team that maintains and updates the kernel. Given the Herculean efforts of Torvalds and his team, many people refer to the entire operating system as Linux. However, this terminology, although convenient, is imprecise. If you want to be very particular, the term Linux really refers to just the kernel itself, the component of the operating system Torvalds and team crafted and currently maintain.
The rest of the operating system consists of a multitude of different open-source projects, developed by a variety of different groups and collected together in various distributions. For example, the folks over at the GNU Project created the common C language compiler included with most Linux distributions, the GNU C Compiler (gcc). GNU is pronounced "guh-NEW" and is a recursive acronym that stands for GNU's Not UNIX. The GNU project also created a lot of other programs integral to the operating system, including many of the commands utilized every day by administrators and users [5] . Beyond GNU, the GUI-based window system used in most Linux distributions was created by the XFree86 project [6] . Also, there is code from many hundreds of different development teams floating under what we sometimes sloppily refer to as merely Linux. Sure, the Linux kernel is the software that controls and coordinates all of these different parts of the operating system. However, Linux is really just the kernel itself. For this reason, some people refer to Linux-based operating systems as GNU/Linux, a nod to the GNU project and its creation of numerous nonkernel components of the operating system [7] .
So, at the heart of a GNU/Linux system, we find the Linux kernel, a very juicy target for the bad guys. The Linux kernel is really just a large piece of complex code that includes a huge number of features running in Ring 0 on x86 hardware. Before we analyze how bad guys attack this target, let's look at the Linux kernel in a little more detail. In the next section, we'll go on a brief adventure through the Linux kernel.
Adventures in the Linux Kernel
All your life has been spent in pursuit of archeological relics. Inside the Ark are treasures beyond your wildest aspirations.
Dialogue from the movie Raiders of the Lost Ark, 1981
For our Linux kernel adventure, please feel free to boot up your own Linux machine and follow along with our discussion by typing commands on your own box. Or, if you don't like hands-on analysis, you can simply read this section and tuck the ideas away for some other time. Our goal here is to demystify the kernel and explore some of its fundamental structures so that we can later understand how attackers manipulate them. In a sense, we'll be acting like archaeologists on a dig of our system for juicy tidbits associated with the kernel. Just as an archaeologist analyzes artifacts left over from ancient civilizations to determine facts about their culture and activities, so too will we be analyzing artifacts created by and associated with our kernel to get a feel for its activities. When you boot a Linux system and log in to it, you are typically staring at a GUI or terminal that exists in user mode. For our adventure, we'd like to peer inside the kernel to see what it's up to. So, how can our user-mode processes get information about the kernel? Fortunately, Linux offers an amazingly simple and intuitive way to view various kernel-mode data structures so we can see what's going on underneath the sheets.
On most Linux systems, the kernel creates a very special directory called /proc, which is pronounced "slash proc." Unlike most directories on the Linux file system, /proc isn't really a set of bits on your hard drive. It's virtual, living only in memory, appearing nowhere on your disk. The kernel creates /proc as a nifty abstraction of itself so that administrators and running programs can view the kernel's status and other aspects of the running system. In other words, /proc is the kernel's elegant way of giving you a portal to view the innards of your operating system. To make viewing these data structures easy, this portal appears as a piece of your file system, with virtual directories and files that contain vital statistics about your machine.
But /proc is more than a mere portal for you to peek into. Indeed, a lot of commands that you run on a Linux system just grab data from /proc and format it nicely for you. For example, when you run the netstat command to get a list of listening TCP and UDP ports, the command just grabs data from the directory /proc/net, where information about the network status is made available to all commands running on the box. In fact, you can think of netstat and many other commands as merely nice user interfaces that gather information from /proc and format it for your viewing pleasure.
Most of /proc is read only. However, some parts of it can be written to. Writing to various select places in the /proc directory can be used to alter the configuration of the kernel in real time. For example, by changing the value of some of the settings inside of /proc/net, an administrator can configure the
machine to forward packets (making it behave like a simple router) or adjust its firewall rules. Typically, these changes are made with a configuration tool that tweaks stuff inside of /proc. However, they can be applied to a running kernel more directly by editing some of the values of /proc.
So, /proc is very powerful. To get a better feel for its capabilities, let's take a look inside of /proc. Log in to your machine and use the cd /proc command to change directories into /proc. Note that on most Linux systems, we don't even need root-level access to look at /proc, so you can log in with any user ID you choose. You won't be able to see everything if you are a nonroot user, but you'll still be able to get a solid idea of the kernel and its status. As we explore /proc, I advise you to just look around, using the cd, ls, and less commands, which only let you view items and not change them. The cd command is used to change directories, ls shows a directory listing, and less displays the contents of a file. Hit the q key to get out of less when you are finished viewing a file. I advise you not to change anything in /proc, as such alterations could make your system unstable. If you just use cd, ls, and less, you'll be safe, as these commands only let you navigate and view the contents of directories and files, without altering any data. Once inside /proc, run the ls command to get a listing of the /proc virtual directory, as I've done in Figure 8.3 .
Figure 8.3. Peering inside /proc to look at kernel information.
In /proc, a bunch of directories have the names of various integers, starting at 1 and increasing. These directories contain information about each running user-mode process on the machine, with the directory name being set to the process ID number (e.g., 1, 1012, 1147, etc.). You can change into one of these directories, look at components of the process using the ls command, and use the less command to view various details of any running user-mode process on the system. In a sense, /proc lets you look into the soul of each running user-mode process. We can view the command-line invocation that was typed to start the process (cmdline), the process's current working
directory (cwd), its environment variables (environ), an image of the binary executable (exe), and other elements of the process. In Figure 8.4 , I've changed into the directory of process ID number 1 which is the init daemon, the master user-mode process that started all other user-mode processes on my machine during system boot. Init always gets assigned a process ID of 1 because it's the first user-mode process to exist on the box, created by the kernel at boot time. I ran the ls command to view various elements of the init daemon process. To view many of these elements, I need root privileges on the box. However, I can view the status of the process by running the command less status. The status shows information about the name, process ID, user ID, and virtual memory associated with the running process.
Figure 8.4. Looking inside a process ID in /proc to view its status.
So, looking inside the soul of running processes can be fun and informative. It sure is nice of the kernel to create this detailed view of all running processes. However, we're here to look at the kernel itself, not user-mode processes. So, let's go back into /proc and look at the kernel-specific information presented there. Inside of /proc, the kernel provides a variety of useful tidbits about itself, including those files described in Table 8.1 .
Table 8.1. A Sampling of Interesting Components of /proc
|
File or |
Purpose |
|
|
Directory |
|
|
|
|
|
|
|
|
|
|
This file contains information about the system's CPU, including its speed, cache size, and other /proc/cpuinfoparameters.
/proc/devicesThis file contains a list of various devices on the machine, such as hard drives and terminals.
/proc/kmsg This file holds log messages from the kernel, which can be read using the dmesg command.
This file includes a list of all variables and functions that are exported via loadable kernel modules
/proc/ksyms
on the machine.
/proc/net/
/proc/stat
/proc/sys/
This directory contains information about the current network configuration and status of the machine.
This file includes statistics about the kernel itself, such as data about the CPU, virtual memory, and hard drive usage.
This directory includes a variety of subdirectories and files that show kernel variables. These variables can be used to view or even tweak the configuration of the kernel.
/proc/versionThis file indicates the version of the kernel that is currently running on the machine.
Table 8.1 gives only a sampling of some of the more important elements included in /proc. Feel free to explore these items, as well as others in your /proc directory. For each of these files or directories, you can safely use the cd and less commands to view their contents on your machine.
Inside of /proc, the loadable kernel module information in /proc /ksyms and /proc/modules is of particular interest, because loadable kernel modules allow for the extension of the kernel. By altering the kernel so that it can support new features, loadable kernel modules let Linux more easily adapt to new hardware types or additional software functionality. For example, you could add a module that functions like a device driver for some unusual fancy new hard drive that a stock kernel just doesn't know how to handle. In the olden days of the Linux kernel, you had to recompile your kernel to extend its abilities. Now, you can just insert additional modules. These kernel modules are dynamically loaded into a running kernel and don't even require a reboot of the machine to take effect. What's more, these loadable kernel modules are actually part of the kernel itself, running in Ring 0, with full access to all kernel code and data. The modules referred to in the directory /lib /modules are automatically applied to the system during boot. Additionally, any root-level user can add a loadable kernel module at any time using the insmod command. These kernel modules are very important, especially as we start to talk about ways to attack the kernel.
Outside of /proc, another very interesting artifact in your file system associated with the kernel is /dev/kmem. As you might recall from Chapter 7 , the /dev directory contains pointers to various devices included on your system, such as components of your hard drive, the mouse, and terminals. As with most things kernel-related, /dev/kmem is special, in that it contains an image of the running kernel's memory. A related file, /dev/mem, contains an
Now that we've gotten a high-level tour of what the kernel wants to show us with /proc and /dev/kmem, let's look at how user-mode processes interact with the kernel. Whenever you run most programs, the kernel creates a process, which includes memory space for the program's code and data, as well as threads of execution running through the memory space. As they run, most processes usually need to tell the kernel to do something. If a process wants to interact with any of the hardware, such as reading or writing from the hard drive or network interface, it'll have to somehow interact with the kernel to get such tasks done. Or, if it wants to run another program to do some other activity, it'll have to ask the kernel to execute that other program.
How do processes make these requests of the kernel? To interact with the kernel, user-mode processes rely on a concept termed system calls. The Linux kernel supports a variety of different system calls to do all kinds of activities, including opening files, reading files, and executing programs. These system calls represent a transition from user mode to kernel mode, as the user-mode process asks the kernel to do something by invoking a system call. To get a feel for which system calls your machine supports, you can look at the header file included in your system for building software (including the kernel itself) that utilizes system calls. This file is typically located in /usr/include/sys/syscall.h, /usr/include/bits/syscall, or /usr/include/asm/unistd.h. Although these locations are pretty common for these files, the particular location of these files does sometimes vary between different Linux distributions, so you might have to hunt for them. More than 100 different system calls are supported in a modern Linux kernel, but a few of the most important ones are shown in Table 8.2 . The maximum number of system calls that can currently be supported by Linux is 256.
Table 8.2. A Small List of Some Important System Calls
|
System Call Name |
Function |
|
SYS_open |
Opens a file |
|
|
|
SYS_read Reads a file from the file system
Now, most user-mode processes don't activate these system calls directly. Instead, the operating system includes a system library full of code that actually invokes the system call when it is required. These standard system libraries, which are typically just a group of shared C language routines, are built into the Linux operating system. So, a running user-mode process calls a system library to take some action. The system library, in turn, activates a system call in the kernel. To activate a system call, the system library sends an interrupt to the CPU, essentially tapping the CPU on the shoulder, telling it that it needs to change to Ring 0 and handle a system call using kernel-mode code. To initiate a system call, the user-mode program or system library runs a machine-language instruction that triggers CPU interrupt number 0x80, a hexadecimal number that tells the Linux kernel to use its system call handling code.
To determine which kernel code to run to handle the system call, the system relies on an absolutely critical data structure in the kernel known as the system call table. The system call table is really an array maintained by the kernel that maps individual system call names and numbers into the corresponding code inside the kernel needed to handle each system call. In other words, the system call table is just a collection of pointers to various chunks of the kernel that implement the actual system calls. The system call table is not the same thing as the syscall.h header file we discussed earlier. That file is just used for compiling software and the kernel. The system call table is a live data structure stored in kernel memory mapping various system calls to kernel code. The relationships among user mode processes, system libraries, the system call table, and the kernel code that implements system calls are illustrated in Figure 8.5 .
Figure 8.5. Processes call libraries, which invoke system calls using the system call table.
To look at various system calls supported by your machine, you can view the file System.map, which is located in /boot/System.map, /System.map, or /usr/src/linux/System.map. Whereas the syscall.h file is just used for compiling software, the System.map file was created when your kernel was originally built and reflects far more specific information about your kernel. In particular, the System.map file contains a listing of various symbols used by the kernel. These symbols are nothing more than a bunch of data structures associated with the kernel, including global variables, tables, and system calls. Keep in mind that System.map doesn't hold your current running system call table for the machine; instead, it holds information about the original system call table that was created when your kernel was originally compiled. Even if you didn't compile the kernel yourself, this file was created when your kernel was originally compiled, and it came as part of your installation. The symbol information in System.map is listed by memory address location and symbol name. This memory address is the place inside of kernel memory where that particular structure is located. In Figure 8.6 , I've shown the contents of my System.map file using the command less /boot/System.map. Note that there are a lot more elements in here than just the system calls. There are a huge number of other symbols in addition to the system call information, such as other variables and signals associated with the kernel.
Figure 8.6. Looking at System.map to see the execve system call information.
In Figure 8.6 , I have paged down to the point where I can see the SYS_execve system call, which is the system call used to execute programs. When one program, such as a command shell, needs to execute another program, such as a command, it calls the SYS_execve system call to ask the kernel to start the other program. Note that the memory address associated with SYS_execve (c0105b10), as well as all other items inside of System.map, start with a hexadecimal number c. That's because, when referenced from a user-mode process on a system with a 32-bit processor, all kernel memory structures are located in memory locations ranging from 0xC0000000 to 0xFFFFFFFF [8] .
Linux includes a nice tool named strace for watching various system calls made by a running user-mode process. You can use strace to invoke any program, and strace will display all system calls, the arguments passed to those system calls, and the return values from the system calls as the program runs. In Figure 8.7 , I used the strace tool to run the command ls so we could see all of the system calls made by ls as it lists the contents of a directory. I could have straced any other program, but I chose ls because it is a familiar program to most Linux users.
Figure 8.7. Using strace to analyze the system calls invoked when running the ls command.
As you can see, as the ls command runs, the execve system call is invoked to run the /bin/ls program, and the open system call is utilized to access various shared libraries. Other system calls that are invoked by ls include fstat (which checks a file's status, including its permissions and owner) and mprotect (which limits access to a region of memory while a given program uses that memory). Using strace, we are witnessing various transitions from user mode to kernel mode, as the program uses system calls to ask the kernel to perform various operations. Therefore, we can get a feel for the relative importance of various system calls by watching which ones common commands on the system rely on. Additionally, we can start to see which system calls attackers might want to alter as they attack the kernel.
Methods for Manipulating the Linux Kernel
Our methods have not differed as much as you pretend. I am but a shadowy reflection of you. It would take only a nudge to make you like me… to push you out of the light.
Dialogue from Raiders of the Lost Ark, 1981
With our whirlwind tour of the Linux kernel complete, let's turn our attention to how attackers manipulate the kernel to achieve their dastardly deeds. Keep in mind that the goal of each of these kernel manipulation tactics is still the same main objective of all RootKits: to provide backdoor access, while hiding the attacker's presence on the system. In particular, these kernel manipulation tactics provide methods for implementing backdoors and then hiding those backdoors on the machine.
With that goal in mind, there are at least five different methods for implementing a kernel-mode RootKit in Linux. Additional possibilities might also exist, currently tucked away in a researcher's or attacker's lab waiting to
be unveiled on an unsuspecting victim. Yet these five possibilities represent the most common methods today for implementing kernel-mode RootKits on Linux machines. These kernel attacks include applying evil loadable kernel modules, altering /dev/kmem, patching the kernel image on the hard drive, creating a fake view of the system with User Mode Linux, and altering the kernel using Kernel Mode Linux. Let's analyze each method in more detail.
Evil Loadable Kernel Modules
A primary method for invading the Linux kernel to implement a kernel-mode RootKit involves creating an evil loadable kernel module that manipulates the existing kernel. This technique first emerged publicly in approximately 1997, and grew in popularity over subsequent years, with a huge variety of different evil module variations now available [9] . Today, it remains the most popular technique for implementing kernel-mode RootKits on Linux systems.
Remember, loadable kernel modules are a legitimate feature of the Linux kernel, sometimes used to add support for new hardware or otherwise insert code into the kernel to support new features. Loadable kernel modules run in kernel mode, and can augment or even replace existing kernel features, all without a system reboot. Because of the convenience of this feature for injecting new code into the kernel, it's one of the easiest methods for implementing kernel-mode RootKits on systems that support kernel modules (e.g., Linux and Solaris). To abuse this capability for implementing RootKits, some malicious loadable kernel modules change the way that various system calls are handled by the kernel, as illustrated in Figure 8.8 .
Figure 8.8. Some loadable kernel module RootKits alter the system call table to execute the attacker's module code instead of the legitimate system call code.
To launch this kind of attack, the bad guy utilizes a loadable kernel module that includes two components, identified as elements A and B in Figure 8.8 . The attacker inserts this module into the kernel, jumping the gap between Ring 3 and Ring 0 by using the insmod command to put the module's code inside of the kernel. Once inserted, the attacker's loadable kernel module, shown as element A in the figure, includes code that operates quite similarly to the original system call code within the kernel. In our example, the bad guy has created a loadable kernel module that implements the SYS_execve system call, used to execute programs, but the bad guy throws in a little twist. When the new, malicious SYS_execve system call is invoked, it will check to see which program it has been asked to execute. If the execution request is for a program that the attacker configured the system to redirect, the evil kernel module will actually execute a different program instead. Otherwise, if the execution request is for some program the attacker isn't interested in redirecting, the normal program will be run. The new SYS_execve system call includes intelligence to decide what to execute outright and what to redirect. That's the twist.
This is all nice, but how does the attacker's malicious SYS_execve get run in the first place? That's where element B from Figure 8.8 comes into play. The attacker's loadable kernel module will alter the system call table so that it no longer points to the normal SYS_execve call in the kernel. Instead, the entry in the system call table associated with SYS_execve will now point to the attacker's own code. The legitimate SYS_execve system call will remain unused on the system, lying dormant. What the attacker is doing here is
playing bait and switch with system calls to redirect execution of selected user-mode programs.
Instead of implementing all of this functionality from scratch, the attacker could just wrap the existing SYS_execve system call code with the attacker's own code that includes intelligence to determine whether to pass the execution request through to the real SYS_execve or to execute some other program instead. This system call wrapping option, which requires less custom code from the attacker and is therefore more efficient, is illustrated in Figure 8.9 . The system call table is still manipulated, but now points to the attacker's wrapper code. When the SYS_execve call occurs, the attacker's wrapper is activated, which checks to see if the execution request is for a program that the attacker wants to redirect. If so, it'll pass the request off to the real SYS_execve code to execute the alternate program. Otherwise, the wrapper will just pass in a request to execute the actual program requested in the system call. Using either alternative (creating entirely new system call code or wrapping an existing system call's software), the end result is the same: The SYS_execve call inside the kernel will include execution redirection.
Figure 8.9. Some loadable kernel module RootKits wrap existing kernel code for system calls.
This technique of rewriting a pointer in the system call table so that it executes the attacker's code is really another form of the API hooking
technique we discussed in Chapter 7 . On a Windows machine, DLL injection involves inserting DLL code into a running process. API hooking redirects various function calls into the DLL code injected by the attacker. In Chapter 7 , we discussed this concept in the context of injecting Windows DLL code into Windows user-mode processes. Of course, the Linux kernel doesn't run Windows DLLs. Here, the attacker is inserting code, in the form of loadable kernel modules, into the Linux kernel. Then, the attacker performs API hooking by overwriting various memory addresses in the system call table so that they point to the loadable kernel module. It's code injection and API hooking all right, but this time in the Linux kernel.
Of course, using this technique against the SYS_execve system call, the attacker has modified only the execution associated with some user-mode programs, and not any system calls associated with reading those programs' binary executable files. The resulting execution redirection is very powerful, because the technique can defeat the file integrity checking tools we discussed in Chapter 7 . As you recall, file integrity checking tools are programs that look for alterations to various system files, such as the login routine or sshd, which are used for accessing the system. By reading these files and comparing cryptographically strong hashes of them against known trusted fingerprints for the files, the file integrity checker can detect a user-mode RootKit, which would replace the login or sshd binary executable files with backdoor versions.
With kernel-mode RootKits, everything changes in favor of the attacker. Now, the bad guy will use execution redirection in a loadable kernel module RootKit to map the execution of the login and sshd binary executable files to some other programs that include backdoors, such as programs named alt_login and alt_sshd, where alt stands for "alternative." These alternatives include some backdoor password the bad guy can use to remotely access the machine. Now, when a file integrity checking tool comes along and compares the hashes of the login and sshd files to their previous values, they will remain exactly the same. That's because the attacker doesn't modify the login or sshd files. The file integrity checker uses the SYS_open and SYS_read system calls to look at login and sshd, and they appear completely intact, because they are intact. However, when the system tries to execute the login or sshd programs for a new user logging in, the evil SYS_execve system call will kick in. The evil kernel module will run the backdoor versions of these programs, alt_login or alt_sshd.
So far, we've just discussed kernel manipulation in the context of the
SYS_execve system call. An attacker could likewise modify the SYS_open, SYS_read, and any other system call using this technique. By modifying these and other system calls, the attacker could hide files, TCP and UDP ports, and
running processes on the system. When any user-mode program makes a system call, the attacker's code will check to see if the user's program is asking questions about some hidden item in the system. If the user program is looking for a hidden item, the kernel will lie and say that the item is not on the machine. A single evil kernel module could do all of this work, remapping or wrapping an arbitrary number of system calls, all with the same piece of code. In fact, most real-world kernel-mode RootKits alter half a dozen or more system calls to hide various nefarious activities of the attacker.
For example, suppose an attacker breaks into a machine and installs a backdoor shell listener, such as the Netcat tool we discussed in Chapter 5 . Running the backdoor shell listener creates several items on the machine an administrator could look for: the executable binary file associated with Netcat, the running backdoor process, and a TCP or UDP port on which the process is listening. An administrator might look for the file using the ls or find commands, the process using the ps or top commands, and the network ports using netstat or lsof. By installing a kernel-mode RootKit to alter various system calls, the bad guy can hide the file, process, and network ports. The kernel will fib about any of these traces associated with the backdoor, regardless of the program that comes asking about it, whether it's ls, find, ps, top, netstat, or lsof. That, dear reader, is the power of a kernel-mode RootKit in action.
At this point, we should note that installing multiple kernel-mode RootKits on a single system could have very mixed results. If each RootKit manipulates different system calls, the two could coexist on the same machine, happily unaware that the other kernel-mode RootKit has been inserted. Two attackers could coexist on the box, without even knowing or seeing the activities of each other. However, in all likelihood, the kernel-mode RootKits will go after the same set of system calls, such as the popular and powerful SYS_execve and SYS_open calls. In this case, the features associated with the last kernel-mode RootKit installed on the box would override any features of previously installed RootKits. In other words, the last one in wins the game.
So, we've seen how the attacker can hide files, processes, and network usage with loadable kernel modules, but the attacker has a problem. There's still the issue of the module itself. If anyone uses the insmod command to insert a module, under normal circumstances, that module will show up in the output of the lsmod command, as well as inside of the /proc/modules file. An administrator could check the list of modules and look for something fishy. Of course, that's only under normal circumstances, which kernel-mode RootKits deviously work to change. To avoid detection by lsmod, an attacker could add another system call modification to the kernel-mode RootKit that hides the
There is another problem for the bad guy with using loadable kernel modules to implement this type of attack. Loadable kernel modules don't survive across a system reboot. Both legitimate and evil kernel modules are flushed out when the system is shut down and have to be reloaded into the kernel during each and every boot sequence. Of course, the attacker wants to make sure that the evil loadable kernel module sticks to the machine across reboots, without tipping off an administrator about the attacker's presence.
One common technique to get around this problem is to alter some program included in the boot process so that it reloads the evil kernel module when the system starts up. The most popular choice for a carrier of the evil kernel module is the init daemon, the first process that runs on the box, as illustrated in Figure 8.10 . When you boot your system, the kernel is loaded into memory, as shown in Step 1. Then, in Step 2, the kernel starts the init daemon, which in turn activates all other user-mode processes on the machine. Attackers often add code to the init daemon so that, as soon as it starts running, it inserts the evil kernel module, which is illustrated in Step 3. By using the executable binding techniques we discussed in Chapter 6 , the code to insert the modules is just prepended to the normal init daemon code, resulting in a single binary executable file for init.
Figure 8.10. Modifying the init daemon to reload an evil kernel module during the boot sequence.
Of course, once inserted, the loadable kernel module itself masks any changes to the init file on the hard drive. If any program, such as a file integrity checker, tries to open the init program file to look at its contents, as shown in Step 4, the kernel module will respond with a lie (in Step 5), saying that the init daemon file looks perfectly intact! Therefore, a file integrity checker won't be able to detect the subterfuge, as shown in Step 6. Because the init daemon runs before any other user-mode process on the box, it poisons the kernel before any detection mechanisms can be executed. Of course, in lieu of the init daemon, an attacker can alter any other startup script or binary executable on the system to load the evil kernel module, using any of the startup techniques we discussed in Chapter 5 .
Now that we've analyzed the general methods used by most evil loadable kernel modules, let's focus on two rather popular specific implementations of these ideas. In the next two sections, we'll look at Adore and the Kernel Intrusion System (KIS), both of which implement all of the ideas we've discussed so far.
Example Loadable Kernel Module RootKit: Adore
Adore is the most popular Linux kernel-mode RootKit in widespread use today. Perhaps that's where it gets its name: Attacker's "adore" it. On some Web sites in the computer underground, the tool is even referred to as "mighty Adore," no doubt because of its solid feature set, the simplicity of its use, and the power it gives an attacker. Written by a developer named Stealth, Adore targets Linux 2.2 and 2.4 kernels, allowing an attacker to hide on the system
Hide or unhide files.
Make a given process ID visible or invisible.
Make a process ID invisible permanently, so that even Adore cannot make it visible again.
Execute any program as root, regardless of the actual permissions of the user invoking the program.
Hide the promiscuous mode status of the user interface to disguise a sniffer.
Hide the Adore loadable kernel module itself.
To accomplish these tasks, Adore consists of two components: a loadable kernel module (called Adore) and a program the attacker uses to interact with the kernel module (named Ava). Think of Ava as the user interface for Adore. After installing the Adore module using the insmod command, the attacker must configure it by running Ava on the same system where the module resides. Ava doesn't work across a network; it must be used to configure Adore on the local system. Ava presents a simple menu-driven interface, as shown in Figure 8.11 .
Figure 8.11. Ava, the Adore user interface.
Adore also hides TCP and UDP port numbers configured by the attacker. That way, other network-listening processes created by the attacker will be disguised.
Although Adore does have many features, it does have a significant shortcoming from a capability perspective. The tool does not include execution redirection capabilities; its focus is solely on hiding files, processes, TCP and UDP ports, and promiscuous mode. Interestingly, execution redirection was available in an earlier version of Adore (version 0.32), but was inexplicably removed in subsequent releases (versions from 0.39b to 0.42 lack the feature).
Example Loadable Kernel Module RootKit: The Kernel Intrusion System
Although Adore might be the most popular kernel-mode RootKit on Linux, there are more powerful tools available. KIS, written by Optyx, actually includes more features, and is one of the most powerful kernel-mode RootKits released to date. Implemented as a loadable kernel module, KIS targets the Linux 2.4 kernel. It offers a standard complement of kernel-mode RootKit functionality, including the hiding of files and directories, processes, network ports, and promiscuous mode. KIS also offers execution redirection capabilities.
You might shrug your shoulders and say, "We've seen that before, so what's the big deal?" Well, the big deal associated with KIS is its incredible ease of use, manifested in two forms: a slick GUI and an interface centered around hidden processes. First, let's look at its user interface, shown in Figure 8.12 . Using a series of helper screens, the attacker can configure the KIS kernel module and attach it to any binary executable on the file system, such as the init daemon, to get KIS restarted at system boot. Once the kernel module has been loaded, the GUI lets the attacker remotely control the kernel module using the same GUI. The attacker configures various settings in the GUI, and encrypted commands are carried across the network to the victim machine, where the KIS kernel module executes them. The KIS user interface is highly reminiscent of earlier application-level Trojan horse programs, such as the
Back Orifice 2000 and Sub Seven tools that we referenced in Chapter 5 . However, the KIS GUI controls a kernel-mode RootKit, not a mere application-level Trojan horse backdoor.
Figure 8.12. The KIS user interface.
As a bonus feature, for its communication across the network, KIS even implements a nonpromiscuous sniffing backdoor to receive commands on the network without listening on a port. As we discussed in Chapter 5 , this type of backdoor listens for commands from an attacker by sniffing them off of the line, thereby avoiding a listening port and throwing off the investigation team. So, embedded inside of KIS, we have a kernel-mode nonpromiscuous sniffing backdoor. What a nasty combination!
The KIS GUI is certainly a major step forward in the evolution of ease of use in kernel-mode RootKits, endearing it to legions of script kiddie fans around the globe. However, the GUI is not the most significant innovation introduced by KIS. The real paradigm shift introduced by KIS is its use of hidden processes as the conceptual model for interacting with the kernel module.
To understand why the KIS fixation on hidden processes is so important, let's take a step back to other kernel-mode RootKits, such as Adore, for a moment. Suppose an attacker breaks into a machine and creates a backdoor listener on the box. After creating the backdoor, the attacker has to load the evil kernel
In a sense, most kernel-mode RootKits go too far in hiding various items, confusing some attackers in the process. KIS doesn't have this problem. By using hidden processes as the central mental model for interacting with the tool, KIS is far easier to use. With KIS, anything created by a hidden process is itself hidden, so an attacker can break into a machine and create a hidden process. From this hidden process, the attacker can install a backdoor. All aspects of the backdoor, which likely consists of a file, a running process, and some TCP or UDP port, will automatically be hidden because they were created by the original hidden process. Similarly, if an attacker runs a sniffer from within a hidden process, the resulting promiscuous mode status is automatically hidden. The attacker doesn't have to remember to go back and hide each element, because they are already hidden. That saves the attacker time.
However, the hidden process model goes even deeper. You see, a hidden process can view all hidden items on the machine. Outside of a hidden process, all hidden items are, of course, hidden. So, an attacker doesn't have to jot down paper notes about where various hidden elements are located. Instead, the bad guy can just fire up a hidden process and then use it to view all hidden files, processes, and port usage on the machine. However, a system administrator, who logs into the machine without a hidden process, will not be able to see all of the attacker's subterfuge. In this way, as illustrated in Figure 8.13 , the attacker uses KIS to create a cone of silence, carving user mode into two worlds: a visible environment and a cloaked environment. From inside the cone of silence, where the attacker lives, everything on the system is viewable, hidden items and visible items alike. Outside the cone of silence, where users and administrators dwell, all hidden items are completely invisible. The KIS kernel module keeps the two worlds separate by carefully manipulating the system call table to hide things from visible processes, yet
Figure 8.13. Using KIS, the attacker creates a cone of silence, dividing user mode into a visible world and a hidden world.
With all of these innovations, you might be wondering why Adore remains the more popular choice for attackers over KIS. This phenomenon is likely due to the fact that Adore is far easier to compile and install than KIS, so the script kiddies often migrate to Adore. Once it's installed, however, KIS is easier to use and more powerful.
Who Needs Loadable Kernel Modules? Attacking /dev/kmem Instead
/dev/kmem is our friend.
Kernel-mode RootKit developers Sd and Devik, 2001
Although modifying a running kernel using loadable kernel modules is a widespread and effective technique, it's not the only game in town for implementing kernel-mode RootKits. Suppose the target machine was built without kernel module support. When compiling a custom kernel for a Linux machine, an administrator can choose whether to add loadable kernel module support or omit it from the resulting kernel. Without module support in the kernel, the administrator will have to build all kernel-level functionality right into the core kernel itself. Such kernels cannot be abused with evil loadable kernel modules, as the hooks necessary for loading such modules into the kernel (stuff like the /proc /ksyms file) are left out. For information about building a kernel that doesn't require or support modules, you can refer to various free Internet guides [10] . Alternatively, you could use Bill Stearns' wonderful kernel-building package (called, appropriately enough, buildkernel), at www.stearns.org/buildkernel/ , which includes an option for creating a kernel that doesn't support modules.
So, if you build a kernel that lacks module support, are you safe from kernel-mode RootKits? Sadly, the answer is no. Various kernel-mode RootKit developers have honed their wares so they can now invade the kernel even without using any loadable kernel modules. To accomplish this, they utilize the facilities of /dev/kmem, that interesting file that holds an image of the kernel's own memory space where the running kernel code lives. By carefully patching the kernel in memory through /dev/kmem, an attacker can implement all of the attacks we discussed in the loadable kernel module part of this chapter, but without using any modules at all.
"But wait a minute," you might be thinking, "earlier in the chapter you said that /dev/kmem was incomprehensible gibberish for humans." Yes, that's true. However, with the appropriate parsing tools, /dev /kmem can be read from and written to by a root-level user. In fact, some hard-core system administrators utilize debuggers and custom code to interact directly with /dev/kmem when troubleshooting systems. However, the concept of using /dev/kmem for implementing kernel-mode RootKits was originally introduced publicly in a detailed technical discussion and political manifesto written by Silvio Cesare in November 1998 [11] . The ideas were further refined and simplified by two kernel-mode RootKit developers named Sd and Devik in their white paper devoted to the topic in late 2001 [12] .
In their white paper, Sd and Devik released code that searches /dev/kmem, looking for the system call table. When it finds the system call table, their software searches the table for various system call entries, such as those associated with SYS_open, SYS_read, and SYS_execve. Then, things get very interesting. The code released by Sd and Devik includes a variety of functions,
but of most interest are the functions rkm (an abbreviation for read kernel memory) and wkm (which stands for write kernel memory). Using rkm, the attacker can read various useful items inside of kernel space. With wkm, the bad guy can insert code directly into kernel memory space. With rkm and wkm, in a sense, these developers have used /dev/kmem instead of modules to jump the divide between user mode and kernel mode.
Using this technique for altering /dev/kmem in a live kernel, an attacker can
implement any of the ideas we discussed in the loadable kernel module
section, without the use of any loadable kernel modules at all. For example,
the attacker can use rkm and wkm to insert alternative code for the
SYS_open, SYS_read, and SYS_execve system calls. Additionally, the attacker
can modify or even replace the system call table inside the kernel so that it
points to the attacker's code and not the legitimate kernel code. With these
capabilities, shown in Figure 8.14 , the attacker has complete control over the
system and can implement file, process, network port, and promiscuous mode
hiding that we saw in earlier kernel-mode RootKits. Additionally, as before, an
attacker can tweak the kernel so that it performs execution redirection.
Figure 8.14. Altering a running kernel by reading and writing to /dev/kmem.
As with loadable kernel module RootKits, these changes to a live kernel through /dev/kmem do not survive across a reboot. Therefore, most attackers
In addition to providing the useful parsing tools for searching, reading, and writing /dev/kmem, Sd and Devik also released a sample kernel-mode RootKit built on these ideas. They gave their tool the very elegant name SucKIT, which is an acronym for Super User Control Kit. From a functionality and usability perspective, the SucKIT kernel-mode RootKit is very similar to Adore, offering file and process hiding, as well as a password-protected backdoor shell listener. The biggest difference with SucKIT, of course, is that no kernel module is included, and module support isn't required on the target machine. By simply running SucKIT at the command line while logged in as root, the program automatically locates the system call table in memory, allocates space in the kernel to use, injects code into kernel memory, and alters the system call table to point to the new code. Although there is no GUI, installation couldn't be much simpler than that. All of the hard work of reading, searching, and altering /dev/kmem is done by the software itself. The attacker just runs a single command line to completely take over the system.
Patching the Kernel Image on a Hard Drive
You know, having to do that little dance of reloading kernel alterations, whether loadable kernel modules or /dev/kmem manipulation, every time the system reboots can be complex. Unnecessary complexity could lead to failures, either crashing the system or breaking the kernel-mode RootKit. There is in fact a simpler way to manipulate the kernel. With root-level permissions on the box, the attacker could just replace or patch the kernel image file on the hard drive itself. That way, on the next reboot of the system, the attacker's evil kernel would be reloaded into the system instead of the original wholesome kernel. Because the kernel image on the hard drive is just a file (readable and writable by root-level accounts), there's no need for the attacker to jump from user mode to kernel mode to make changes to this file. User mode to kernel mode transitions (e.g., those that occur through system calls, insmod, and /dev/kmem) are only required to interact with a running kernel, but aren't necessary to change the kernel image file on the hard drive. By just exercising rootly privileges, the attacker can overwrite the kernel image file on the hard drive and get the new, evil kernel loaded into memory at the next reboot, as illustrated in Figure 8.15 .
Figure 8.15. Replacing the kernel image on the hard drive.
In the Linux file system, the kernel image is stored in a file called vmlinuz, typically located in the /boot directory. To minimize storage requirements for boot devices, most of this kernel image file is compressed. During system boot, the first portion of vmlinuz gets loaded into memory and executed. This first portion of vmlinuz then decompresses the rest of the vmlinuz file and loads the entire uncompressed kernel image into memory. Sometimes, if you build your own kernel, you'll find a file called vmlinux, with a trailing x instead of a z. A vmlinux kernel image isn't compressed, and must first be compressed to prepare it for booting, converting it to vmlinuz. When replacing an original kernel with an evil version, the attacker must create the alternative kernel image, compress it, and overwrite the existing /boot /vmlinuz file with the evil replacement.
Replacing the entire kernel image file with a nasty variant is rather easy. An attacker could build a custom kernel on his or her own machine, and deploy this evil kernel on the victim's machine. Because Linux is an open-source operating system, the bad guy can modify the kernel source code to create a custom kernel that provides the attacker with backdoor access and hides nefarious activities on the machine. For example, with a dozen or so tweaks to some system calls in the kernel source code, an attacker can create a kernel image that would hide files with certain names, mask specific TCP and UDP ports, render processes with some names invisible, and implement execution redirection. Rather than monkeying with the system call table, the attacker can just sprinkle some new code right into the existing system call functions. In other words, the entire new kernel would be the RootKit, replacing the old kernel outright. The attacker could even program the new evil kernel so that it looks like the original kernel. For example, the evil kernel can be configured so that if anyone opens the altered /boot/vmlinuz file, the kernel will return the old, unmodified kernel image file, which it has squirreled away on the hard drive, instead of the modified version. In this way, an attacker can foil any file integrity checks against the kernel image file by altering system call code associated with opening and reading files.
There is a bit of a problem for the bad guys with the wholesale replacement of the kernel, though. Perhaps the victim machine has very specific kernel
options, tricked out with custom code created by a system administrator who dabbles in specialized kernel development. Or, perhaps the existing kernel has some very special hardware support compiled in it that the attacker doesn't know about. If the attacker creates a brand new kernel and swaps it in place of the customized kernel, the administrator might quickly notice the attack or some hardware might become inaccessible. To avoid this situation, the attacker can simply edit the existing vmlinuz file instead of replacing it. By applying patches to the kernel image file on the hard drive instead of replacing it entirely, most of the existing functionality of the custom kernel will be preserved. The attacker's options will just be grafted into the existing kernel image file, as pictured in Figure 8.16 .
Figure 8.16. Applying patches directly into the kernel image on the hard drive.
In 2002, someone called Jbtzhm released a white paper and some code that allows an attacker to open, uncompress, parse, and apply patches directly to a vmlinuz file [13] . Jbtzhm's technique lets the attacker append new code to the end of the kernel image file, and then modify pointers within the existing code to point to the new functionality. Jbtzhm designed his software so that it would insert the code from a loadable kernel module right into the kernel image file,
rather than having to load modules the old-fashioned wayafter system boot. Loadable kernel modules, after all, are nice little chunks of kernel-mode code, ready to be applied into the kernel. Jbtzhm's technique just inserts the bundles of code from loadable kernel modules into the kernel image file to simplify the implementation of code to be grafted into the kernel. Therefore, using this technique, an attacker could patch a kernel image file with the Adore or KIS loadable kernel module RootKits, and have them automatically applied from the vmlinuz file itself during system boot.
The three methods for altering kernels that we've discussed so far (loadable kernel modules, altering /dev/kmem, and altering kernel images on the hard
Faking Out Users with the User Mode Linux Project
Do you think that's air you're breathing now?
Dialogue from The Matrix, 1999
The substitute or patched kernel idea from the last section could be extended even further, employing an amazing tool called User Mode Linux (UML), a project originally created and currently headed by Jeff Dike. Freely available at http://user-mode-linux.sourceforge.net/ , UML lets its user run an entire Linux kernel inside of a normal user-mode process. It's called User Mode Linux because it runs an entire Linux system, with its own kernel, applications, and so forth, inside a user-mode process on a host Linux system. So, with UML, I can take my Linux machine, with its normal kernel intact and running just fine, and create multiple UML instances running as user-mode processes on my existing system. Each of these additional UML instances has its own kernel mode and user mode inside.
With UML, my underlying operating system acts as a host, with all of my UML instances as guest operating systems running on top of the host. These guest operating systems are entire Linux installations, each with its own kernel, network options, file system, and applications, all wrapped up inside of a standard Linux user-mode process. Each UML instance is independent of the others, running whatever programs it requires inside its own user-mode space. I can therefore create virtual Linux machines that run on top of my real system, right alongside of normal user processes, as illustrated in Figure 8.17 .
Figure 8.17. Legitimate uses of User Mode Linux involve creating multiple virtual Linux Machines on a single Linux system.
Perhaps you're familiar with VMWare or VirtualPC, two tools that let users create guest operating systems running on top of a host operating system. UML can also be used to implement guest operating systems on a host, but it differs from VMWare and VirtualPC in two important ways. First, UML is free and open source. Second, VMWare and VirtualPC implement a virtual x86-compatible processor, so almost any x86-compatible operating system (e.g., Linux, Windows, BSD, etc.) can be installed as a guest on them. UML, on the other hand, doesn't emulate an x86 processor. Instead, it acts as a proxy for making Linux system calls, creating the abstraction of Linux guest kernels living on top of a Linux host operating system. The current iteration of the project is Linux-centric. Still, despite this difference, UML is quite useful.
Please keep in mind that UML wasn't designed as an attack tool. It can be employed in all sorts of positive roles. For hard-core programmers working on changes to their kernel or writing new applications, UML provides a nifty little sandbox to run experiments inside. If the kernel modifications or new application completely crash the UML instance, the developer can simply restart that UML instance without rebooting the entire host system. Therefore, UML provides a great deal of convenience for developers and experimenters. Additionally, service providers could utilize UML to provide virtual Linux hosting services to clients. Each client could rent (or be given) a UML instance on the service provider's single Linux machine. The UML instances are independent of each other, so, to users, it would appear that they are logging into and utilizing their own separate Linux machine. In fact, as of this writing, there are numerous commercial UML hosting service providers available on the Internet [14] .
How could an attacker apply the otherwise virtuous UML in a subversive role, undermining the existing kernel on the machine? Consider the attack shown in Figure 8.18 . The bad guy could break into the machine with root privileges, make a copy of the existing file system, including the kernel, all applications, and user data, and load them into a guest UML instance on the machine. Then, after starting this UML guest containing a copy of the original system, the
Figure 8.18. Employing User Mode Linux to confine legitimate users inside a prison.
To successfully implement this subterfuge, the attacker would need to ensure that the UML instance with the image of the real system is restarted at each and every reboot of the overall host operating system. This isn't a major problem, as the various scripts and programs associated with running UML can be set as startup scripts on the host operating system. Of course, the rather complex process of booting up the actual (but evil) kernel, followed by initiating a UML session with its own virtuous kernel wrapped inside, might get noticed by a suspicious system administrator watching messages from the startup scripts during the boot process. However, the attacker could carefully disguise the actual boot-up messages and the UML initiation messages so that the system appears to be normal during the boot-up phase.
By deploying UML on a victim machine, attackers turn the whole system into their playground, confining normal users and administrators into a small UML prison tucked away in a corner of the system. The real concern here, of
course, is that the users and administrators have no idea that they are in a prison. UML becomes a cone of silence wrapped around legitimate users and administrators. With UML going about its business, the system looks normal to them. Their normal kernel is running, all of their files are still on the hard drive, and programs run the same way they did before the attack occurred. The victims are blissfully ignorant of their UML-induced cage.
The Kernel Mode Linux Project
With UML, we've just seen the power of running an entire Linux kernel inside a user-mode process. There's another technique that sort of reverses this concept, which can again be exploited in a kernel-level attack. Instead of running an entire Linux kernel inside a user-mode process, how about simply running a user-mode process in kernel mode itself? That is, we could run a user-mode process, but have it execute in Ring 0 of the CPU, giving it full access to all kernel data structures. As with UML, there's even an open-source project devoted to this concept, called, appropriately enough, Kernel Mode Linux (KML).
KML is the brainchild of Toshiyuki Maeda, and is freely available at http://web.yl.is.s.u-tokyo.ac.jp/~tosh/kml/ . To deploy KML, an administrator (or attacker) must compile a special kernel with KML support. Implementing KML isn't a major feat of coding, however. The KML implementer just needs to download Maeda's code, and answer "Y" in the kernel-build script when prompted whether to insert KML functionality. Then, once the KML-capable kernel is installed on a system, a special directory called /trusted is created. Any binary executable located in /trusted will run in kernel mode on the machine. So, for example, if you want to run the ls command inside of kernel mode, you'd just copy ls into /trusted, and then execute /trusted/ls. The ls command now runs, but this time in kernel mode. Actually, the ls command, while executing, is a separate process, not grafted into the kernel memory. However, it runs with all of the permissions of the kernel, existing in Ring 0, not Ring 3. Because ls is fairly well-behaved, it won't hurt the system. However, we've just employed KML to cross the Rubicon from Ring 3 to Ring 0, as shown in Figure 8.19 .
Figure 8.19. Using KML to run a process in kernel mode.
Like UML, KML wasn't created with evil intentions. It was designed so that a software developer or administrator could run well-behaved programs in the kernel mode to improve efficiency and performance. On a normal (non-KML) Linux system, whenever a user-mode process makes a system call (which happens all the time), a major context switch occurs. When the flow of execution transitions from Ring 3 to Ring 0, several user-mode data structures have to be saved in memory, and new kernel-mode data needs to be loaded. This transition takes time and CPU cycles. Maeda created KML for applications with very high performance demands to avoid the context switch.
Of course, running programs designed to execute as user-mode processes in kernel mode can be very dangerous. The process could accidentally (or purposely) alter data structures inside the kernel, making the system highly unstable, or instantly crashing it. Therefore, KML isn't for the faint of heart, nor is it appropriate in the vast majority of production environments. Still, for experimental systems and playing with running kernels, KML is a fascinating project.
Of course, an attacker could use KML in a kernel-level attack. Suppose a bad guy takes over your machine. The attacker could replace your kernel or patch it so that it now supports KML. Then, the attacker could write a malicious program that runs a process in kernel mode, utilizing KML to make the jump from Ring 3 to Ring 0. Once running, the malicious process would search for and alter the system call table and system call code to replace them with the attacker's own software. The attacker's software would implement a kernel-mode RootKit, with all of the hiding and execution redirection tricks we've seen with other forms of kernel-level malware. This type of attack is illustrated in Figure 8.20 . Although this type of attack hasn't yet been reported in the wild, it is certainly possible.
Figure 8.20. Using KML to attack the kernel, altering the system call table and system call code.
Defending the Linux Kernel
So, as we've seen, there are a myriad of possibilities for attacking the Linux kernel, all of which result in complete domination of the victim machine by a nefarious attacker. How can you defend against such attacks? Well, as with the user-mode RootKits we discussed in Chapter 7 , the defenses fall into three different categories: Prevention, Detection, and Response. Let's explore the defenses available in each of these categories.
Kernel Mode RootKit Prevention on Linux
An ounce of prevention is worth a pound of cure.
Anonymous
Just like the user-mode RootKits we discussed in the last chapter, all of the kernel manipulation attacks we've discussed in this chapter require the bad guy to obtain root-level permissions on the victim machine first, before installing any kernel-manipulation code. Therefore, you can stop would-be kernel-altering attackers in their tracks by preventing them from getting superuser privileges on your machines in the first place. Vigorously apply the defenses we've discussed throughout this book. Use tools like Bastille Linux, which we discussed in more detail in Chapter 7 , to harden your system
configuration. Disable unneeded services and make sure you rapidly deploy patches to your sensitive systems. Older versions of the Linux kernel are particularly susceptible to kernel attacks, and they have widely known vulnerabilities that an attacker could exploit, such as the ptrace flaw that plagued Linux kernel version 2.4 in 2002 and 2003 [15] . By keeping your system, and especially the kernel, patched and up to date, you won't have such vulnerabilities acting as entry points for the bad guys. Furthermore, educate users about the need to secure their systems and not run untrusted code. With kernel-mode RootKits on the loose, it's more important now than ever to run a tight ship when configuring and maintaining your systems.
In addition to configuring your systems securely and patching them, you might want to consider deploying Linux kernels that do not support loadable kernel modules on your most sensitive systems, such as your publicly accessible Web, e-mail, DNS, and firewall systems. You likely don't need kernel module support on such machines, as patching the kernel on a live production system with a module is very dangerous and could crash the system. When was the last time you inserted modules into your critical production Web, e-mail, DNS, or firewall servers? Probably never. Following directions readily available on the Internet [10] , or using Bill Stearns' kernel building script [16] , you can easily create a custom Linux kernel that has all the functionality that you require built in, without supporting kernel modules.
Of course, as we saw earlier in the chapter, bad guys could go after /dev/kmem directly and poison your kernel even if module support isn't available. Still, by just getting rid of loadable kernel modules, you've raised the bar against the rank-and-file script kiddies who rely solely on loadable kernel modules for their attacks. Instead of allowing an attacker to completely hose your kernel with a simple insmod command, you've increased security so that your adversaries will have to work somewhat harder to undermine your kernel. We should note that some people use the term monolithic to refer to a kernel without module support, although hard-core kernel developers blanche at using this word for this concept. They call such kernels non-modular, reserving the word monolithic to indicate a kernel that supports numerous features in kernel mode, instead of pushing almost all capabilities into user space [17] .
A related approach is to utilize a kernel that was specifically modified to prohibit a module's ability to alter the system call table. In particular, some versions of the Linux kernel do not export the system call table [18] . Exporting of the system call table allows modules to read and even update this crucial data structure in the first place. Without this export, loadable kernel modules cannot alter the system call table, foiling some kernel-mode RootKits. In particular, RedHat grafted this feature into the version of the kernel
included in RedHat 8.0 and 9.0, and Linus Torvalds built it into the development kernel version 2.5.41. For this reason, the stock version of Adore and most other module-based kernel-mode RootKits will not work on RedHat 8.0 and 9.0. That's pretty nice, as Adore is the most popular kernel-mode RootKit in use today. Outside of recent RedHat versions and experimental kernels, though, this feature hasn't been widely included in other kernel versions as of this writing. Also, it's important to note that, even with this feature, the /dev/kmem-style RootKits, like SucKIT, will still function appropriately. To make Adore or KIS work on these systems, an attacker would have to modify the RootKit code to take advantage of /dev/kmem, or add the system call table export feature that RedHat removed back into the kernel. As you'd no doubt guess, there is even freely available code for re-adding the system call table export, called addsyms, available at http://xenion.antifork.org/files.html .
After hardening your machine and removing kernel module support, you might want to turn to some freely available tools to help limit attackers' access to your systems. One noteworthy free tool for identifying and controlling the flow of action between user mode and kernel mode is Systrace by Niels Provos, available at www.citi.umich.edu/u/provos/systrace/ . Don't get confused by the name Systrace. Earlier in this chapter, we ran a tool called strace, which merely shows the system calls made by an application. Systrace goes far beyond simple strace. Once installed on Linux, FreeBSD, and Mac OS X machines, Systrace tracks and limits the system calls that individual applications can make.
So, using Systrace, you can run an application under normal, controlled circumstances and record which system calls it makes. For example, you could run your Web server on a test machine and log all of its system call activity. You now have a known set of system calls required by the intact Web server. Now, you can use Systrace to limit that application so that it cannot make any other system calls on the machine. In a sense, you've locked the application so that it can only access the normal set of kernel functionality that it requires to do its job. If it tries to make other system calls, such as those calls associated with inserting a module into the kernel, Systrace will stop the activity and return a failure notice for that system call. In this way, you can isolate various programs inside of little cages, where they can only execute the system calls they normally require. If Systrace observes an application trying to run other system calls, it'll alert you about a misbehaving application, possibly due to an attacker's undermining that program.
In addition to Systrace, you could also turn to security-enhancing loadable kernel modules. Just as the bad guys employ evil kernel modules to undermine
the Linux kernel, system administrators and security personnel can utilize wholesome modules to buttress the overall security of a Linux system. Of course, if you've removed module support from your kernels, you'll have to compile in any code offered by a security-related kernel module directly into your kernel. One worthwhile project that focuses on increasing the overall security of Linux, starting with the kernel, is the Linux Security Module (LSM) initiative, described in detail at http://lsm.immunix.org . It's important to note that LSM doesn't stop evil kernel modules directly. Instead, LSM technology makes the overall system more secure, closing various avenues that attackers typically employ to break into root. By denying them root access, LSM improves security so that the bad guys cannot modify the kernel or otherwise compromise the machine.
Let's look at the origins of LSM to get a feel for its design goals. Back in March 2001, the U.S. National Security Agency (NSA) delivered a presentation on its Security Enhanced Linux (SELinux) project in front of the Linux Kernel Summit, an annual gathering of hard-core kernel developers. Prior to the presentation, the NSA publicly released a version of the Linux kernel that includes far more detailed security controls, applying mandatory access controls to critical system components and functionality. "Normal" Linux is built around discretionary access controls, which allow users and administrators to apply permissions to various system files at their own discretion. Under this paradigm, a user or administrator can purposely or accidentally weaken the security of a system by changing the read, write, and execute permissions on various critical files. With mandatory access controls, such as those implemented in SELinux, access to certain critical system components, including data structures and files associated with the kernel, is controlled by default and cannot be altered by a user or administrator. That's why these controls are mandatory and not discretionary. In a sense, many security settings, like the read, write, and execute permission of some critical files, are hard coded into the machine. Therefore, if the mandatory access controls are implemented properly, the kernel and other pieces of the operating system are less exposed to manipulation by a bad guy. Based on the NSA presentation at the 2001 Linux Kernel Summit, Linus Torvalds and other kernel developers began to discuss how to incorporate some of the SELinux ideas into the overall Linux kernel, and the LSM project was born.
Mandatory access controls are just one possible security feature that could be implemented via LSM, but other options are certainly available. In fact, LSM is an architectural framework for plugging all kinds of security features into the Linux kernel. The LSM project is currently spearheaded by Immunix, a company that creates a commercialized hardened version of Linux. In essence, LSM adds security hooks to the Linux 2.4 and 2.5 kernels. These hooks allow a
In plain old vanilla Linux, a base set of security controls is built into the kernel itself. However, these controls are a one-size-fits-all approach that Linux inherited from UNIX systems of decades ago. These default controls focus on access to files, specifying who can read, write, and execute each file on the file system. With LSM, a kernel module can specify all kinds of different or additional access controls, specifying, for example, files that should be strictly off limits or even data structures in the kernel that shouldn't be altered.
LSM provides the overall framework and interface for writing these security modules. A variety of different groups have created LSM-compatible modules that increase the built-in security of Linux. After all, a security specification is nice, but only implementations make it real and usable. Table 8.3 includes a variety of free, open-source LSM implementations that improve the overall security of a Linux machine. Each of these modules can boost the underlying security of Linux to prevent a bad guy from getting root and mounting a kernel-mode RootKit attack. It's crucial to note, however, that use of any of these modules fundamentally changes the security controls of your Linux system. Therefore, it's possible that applications installed on a Linux box will break if you install LSM without first carefully configuring and testing the system. Also, because it changes the underlying access control rules in Linux, an LSM module could complicate administration of the machine. A system administrator fully versed in "normal" Linux could be completely confounded by the security controls introduced by an LSM. Therefore, system administrators and security personnel must gain experience on the specific security features implemented in an LSM before rolling it into production.
Table 8.3. Various LSM Implementations
LSM Name Location Purpose
|
|
|
|
|
This LSM implements a security architecture based on SELinux, |
|
|
SELinux |
created by the NSA. It includes mandatory access controls, as well as |
|
|||
|
|
|
role-based access controls, which assign users to different roles and |
|
||
|
|
|
|
|
|
|
|
|
|
|
|
determine their privileges based on their assignments. |
|
|
Domain and |
|
|
|
This module groups processes together into a set of domains. Various |
|
|
Type |
www.cs.wm.edu/~hallyn/dte/ files are then assigned an attribute called a type. Then, various |
|
|||
|
Enforcement |
|
|
|
domains are given controlled and explicit access to specific types. |
|
LIDS www.lids.org
This module implements several security restrictions, including limits on user access of the /proc file system and nonexecutable process stacks to prevent a variety of buffer overflow attacks.
The Linux Intrusion Detection System (LIDS) provides a variety of security features, including:
File protection, locking files so that they cannot be altered, even with root permission
Process protection, to prevent access to critical processes
Fine-grained access control lists
Security alerts for attacks against the kernel
Kernel-level port scanning detection
Restrictions on processes from listening on network ports
Kernel Mode RootKit Detection on Linux
Even with the best defenses, an attacker still might find a hole in your armor and install a kernel-mode RootKit. Once a kernel-mode RootKit is installed, we cannot fully trust any results from our system. It all comes down to how thoroughly the kernel-mode RootKit software hides itself and how carefully the attacker configures it. Although detection can be a major challenge, we do have numerous mechanisms at our disposal to discover traces of kernel-mode RootKits on our systems.
First, look for suspicious network activity coming from a system. Even though local activity is hidden from system administrators, a network-based IDS can observe attack packets coming from a machine infected with a kernel-mode RootKit as the attacker tries to take over other systems across the network. Furthermore, if the attacker plants a backdoor listening on a TCP or UDP port, a port scanner such as Fyodor's Nmap (which is free at www.insecure.org ) can remotely detect the listening ports, even though they are hidden from all local users and administrators. Also, look for unexpected reboots of your systems. Although loadable kernel module and /dev/kmem alterations don't require a reboot, the other methods of kernel manipulation we've discussed (overwriting the kernel image, using UML, and installing KML) do require the attacker to reboot the system. Although an unexpected reboot is no guarantee that an attacker has taken over your box and installed one of these nasties, it is an indication that something might be out of the ordinary. You should take a deeper look, using the response tools we'll discuss in this section, if your system reboots itself from time to time.
Additionally, you should still use file integrity checking tools, such as Tripwire, AIDE, and the related programs that we discussed in Chapter 7 . A thorough bad guy will configure the manipulated kernel with execution redirection and other alterations that lie to the file integrity checker about all file changes on the system. If the attackers very carefully cover all of their tracks, they can fool a file integrity checker. However, a less careful attacker might forget to configure the kernel-mode RootKit to hide alterations to one or two sensitive system files. Even a single mistake in the file-hiding configuration of the kernel-mode RootKit by the bad guys could expose them to detection by your file integrity checker. Therefore, file integrity checking tools remain very valuable, even though a kernel-mode RootKit can foil them if the attacker is super careful. I'd rather not depend solely on the attackers' making mistakes to discover their treachery, but you better believe I'll be sure to take thorough advantage of their errors. Deploying file integrity checking tools on all of my sensitive systems lets me prepare for such circumstances.
Another tool that we discussed in Chapter 7 can be useful in detecting these kernel-mode attacks, namely chkrootkit. By looking for various system anomalies introduced by kernel-mode RootKits, the free chkrootkit tool can detect Adore, SucKIT, and several other kernel-mode RootKits. For you fans of The Matrix, chkrootkit is really looking for glitches in the Matrix. In the movie, glitches in the Matrix occur when the bad guys start changing things, creating a déjà vu. Similarly, with a kernel-mode RootKit, an inconsistency in the system's appearance could be an indication that something foul has been installed. The scripts included in chkrootkit perform tests that can be used to catch the kernel in a lie about the existence of certain files and directories, network interface promiscuous mode, and other issues that kernel-mode RootKits generally fib about.
One of the ways that chkrootkit finds kernel-mode RootKits is by looking for inconsistencies in the directory structure when a file or directory is hidden. Each directory in the file system has a link count, which indicates the number of other directories and files that a given directory is connected to in the file system structure. For each directory, this link count should be two more than the number of files in the directory. That way, the directory would have one link for each file, plus one for the parent directory (..) and one for itself (.). Many kernel-mode RootKits, such as Adore, hide files and directories without manipulating the link count of the parent directory. Chkrootkit combs through the entire directory structure, counting the number of files and directories that it can see inside each directory and comparing it to the link count. If it finds a discrepancy, chkrootkit prints a message indicating that there might very well be directories that are hidden by a kernel-mode RootKit. Unfortunately, as of this writing, the current version of chkrootkit cannot detect KIS, which
manipulates even the link count associated with hidden files and directories.
KIS is smart enough not to introduce that glitch into the Matrix.
Beyond general RootKit detectors like file integrity checkers and chkrootkit, there are also tools that specialize in detecting the behavior most often associated with kernel-mode RootKits, such as altering the system call table or loading modules. In particular, a tool called KSTAT (an awkward acronym that stands for Kernel Security Therapy Anti-Trolls) is freely downloadable from www.s0ftpj.org/en/tools.html . On Linux 2.4 kernels, KSTAT helps find and uninstall kernel-mode RootKits. For detection, KSTAT looks for changes to the system call table. It'll even scan /dev/kmem to look for the memory locations associated with all system calls, and compare these results with the information in the System.map file. If it finds a discrepancy, KSTAT warns a system administrator that someone has altered the system call table. Just as the bad guys look through /dev/kmem to break our systems with tools like SucKIT, we can use KSTAT to look through /dev/kmem to find their attacks.
Additionally, like Systrace, the KSTAT tool can also create a list of fingerprints for the system calls used by various critical programs, such as a Web or mail server program. If any of these system calls are altered, or additional system calls are invoked by these programs, KSTAT can warn an administrator that something foul might be occurring.
In addition to KSTAT, another free project that looks for manipulation of the system call table on Linux is called Syscall Sentry, written by Keith J. Jones. Syscall Sentry is a loadable kernel module that is typically inserted during system startup. If an attacker inserts a module that alters the system call table, the Syscall Sentry module detects the alteration, logs the event, and alerts the system administrator about this anomalous activity.
Beyond Linux, other tools provide system call table monitoring for other varieties of UNIX. In particular, a tool named KSEC provides such services on FreeBSD and OpenBSD, available at www.s0ftpj.org/tools/ksec.tgz . On Solaris systems, you can use a tool called Listsyscalls by Bruce M. Simpson, available at www.packetstormsecurity.org . Both KSEC and Listsyscalls provide very similar functionality to that offered to Linux users through KSTAT and Syscall Sentry.
Kernel Mode RootKit Response on Linux
Now, suppose these detection mechanisms or even your intuition tells you that some dastardly attacker has installed a kernel-mode RootKit on your machine.
Again, I refer you to the tools we discussed in Chapter 7 . Do you remember how we said that to respond to a RootKit attack, you should use a bootable CD-ROM that includes a Linux operating system? We even discussed using William Salusky's FIRE and Karl Knopper's Knoppix distributions, which include specific customizations for security and computer forensics investigations. Well, back in Chapter 7 , I specifically included the word bootable in our description of FIRE and Knoppix because that very characteristic would become helpful in this chapter. An investigator can insert the FIRE or Knoppix CD-ROM in a potentially compromised machine, and boot from the CD-ROM. As the system shuts down, the potentially evil, deceiving kernel will stop running. When the system reboots, the trusted kernel from FIRE or Knoppix will be loaded into memory. Because this new kernel is grabbed from the CD-ROM, an investigator can use it to read the victim machine's file system with more trustworthy results than one can get from an evil kernel. Therefore, after booting from the CD-ROM, the investigator can run a file integrity checker (built into the CD-ROM, of course) to look for changes to critical files on the hard drive.
Now that we've seen how attackers have their way with the Linux kernel, as well as how we can stop them, we turn our attention to the Windows kernel. Given its widespread popularity on desktops and servers, the Windows operating system and its underlying kernel are a choice target for attack by the bad guys. In this section, we'll start by discussing what the Windows kernel is and going on an adventure looking for kernel artifacts, just like we did for Linux in the last section. After that, we'll see how attackers can invade and manipulate the Windows kernel. For this discussion, we'll focus on the Windows 2000 kernel, the most widely deployed professional version of Windows at the time of this writing. The Windows NT, XP, and 2003 kernels are quite similar to the Windows 2000 kernel, but include minor differences due to the evolution of the kernel over time. I'm very happy to point out that the techniques and tools we'll draw on during our Windows kernel adventure all function on Windows 2000, XP, and 2003. So, as we look at various Windows kernel artifacts, you should be able to follow along with your own machine if you use Windows 2000, XP, or 2003. The innards of Win9x (including Windows Me) differ radically, and won't be our focus in this chapter. So, without further adieu, grab your dusty old cowboy hat and bullwhip as we go on an archeological adventure in the Windows kernel.
Adventures in the Windows Kernel
Oh my God! It's full of stars!
Dialogue from the movie 2001: A Space Odyssey, 1968
As you'd certainly expect, the Windows kernel includes numerous components for interacting with and supporting user-mode processes. As we'll see, a lot of the concepts we covered in the Linux kernel have directly analogous ideas in the Windows kernel. After all, they are both operating system kernels, trying to achieve the same goal: servicing user-mode programs by sitting in between these processes and the hardware. The overall Windows kernel architecture is shown in Figure 8.21 .
Figure 8.21. An overview of the Windows kernel and its relationship to vital user-mode components.
To get a feel for how all of these layers operate, let's start out at the top: user-mode processes, the programs you run on a day-to-day basis, such as your favorite word processor, a game, or even an e-mail server. To interact with the operating system, a user-mode process makes function calls into various Win32 subsystem DLLs, roughly analogous to the system libraries we discussed earlier for Linux. When developers create programs to run on Windows, these Win32 function calls are the crucial interface into Windows itself, implementing the API into the Windows operating system. These DLLs include all kinds of capabilities, such as displaying information on the screen, opening files, or running other programs.
To encourage development of applications for Windows, Microsoft has provided a great deal of documentation about the function calls available in the Win32 subsystem DLLs. The Win32 DLLs are grouped into several different files, each with its own lump of code to accomplish certain tasks, including User32.dll, Gdi32.dll, Advapi32.dll, and Kernel32.dll. Yup… That's right. The file named Kernel32.dll is not the kernel. Instead, along with User32.dll, Gdi32.dll, and Advapi32.dll, it runs in user mode and provides an API to various user-mode applications for reading files, writing files, and performing other actions. It's called Kernel32.dll because it provides an API for user-mode programs to send requests to the kernel, but these requests don't go directly to the kernel. Instead, they must pass through Ntdll.dll first.
We should note that Windows supports other groups of subsystem DLLs beyond the Win32 set. Since its inception, Windows NT and its successors include subsystems for programs written for OS/2 (a venerable operating system championed by IBM years ago) and POSIX (a generic UNIX-like environment). The vast majority of Windows programs rely solely on the Win32 APIs, but these other subsystems are available to run older applications or for new programs to be built in those other programming environments.
So, most user processes make function calls directly into the Win32 DLLs. Each
function call inside of Win32 can, in turn, do one of three things [19] . First, as shown in element A of Figure 8.22 , for relatively simple requests that don't require kernel-level interaction with hardware or other processes, the Win32 function could just handle the request and send a response. An example function of this type is the GetCurrentProcessId function, which lets a process get its own process ID number from user space. No deeper level calls are required.
Figure 8.22. Three ways the Win32 DLLs handle requests from user-mode processes.
Another possibility for handling a function call from a user-mode application involves the Win32 DLL needing information from a very special user-mode process that is responsible for keeping the Win32 subsystem running. This type of interaction is illustrated as element B of Figure 8.22 . The Csrss.exe process, which is an abbreviation for Client/Server Run-Time Subsystem, keeps the Win32 subsystem operating by invoking user processes and maintaining the state associated with each process. A user-mode process can ask Csrss.exe for information about itself or other processes without calling the kernel.
The third possibility for a Win32 function call is the most interesting for our purposes, and is shown as element C in Figure 8.22 . The user-mode application could ask a Win32 DLL to take some action that requires invoking a kernel function. For example, the user-mode process could call the ReadFile or WriteFile function calls in a Win32 DLL. To interact with the hardware as required by these functions, we are clearly going to need to take a step downward toward the kernel. The highly documented Win32 DLL that developers utilize will map the ReadFile and WriteFile function calls into another piece of code, called Ntdll.dll, which is an internal and relatively
Once the Ntdll.dll code maps the function calls, we need to make a transition from user mode to kernel mode, jumping through a call gate into the kernel. Using a mechanism we'll explore shortly, the Ntdll.dll code invokes kernel-level functionality called the Executive. The Executive, named for its high and mighty capabilities, serves numerous purposes, including making kernel function calls available to user mode, making various kernel-level data structures available to other kernel-level processing, and managing certain kernel state and global variables. The Executive is implemented inside of a critical file called Ntoskrnl.exe. When the Executive is invoked, it determines which piece of underlying kernel code is needed to handle the request, such as reading or writing a file. After determining which piece of kernel code is required to handle the request, the Executive transitions execution to another component of Ntoskrnl.exe. This bottom piece of Ntoskrnl.exe is called the kernel, even though the Executive itself runs in kernel mode and is implemented in Ntoskrnl.exe as well.
The code in the kernel now needs to interact with the hardware. In our ReadFile and WriteFile example, the kernel needs to interact with the hard drive. To accomplish this task, the kernel itself relies on yet another level of code, called the Hardware Abstraction Layer (HAL). Implemented in a file called HAL.dll, the purpose of this component is to make various different vendor hardware products look consistent to the kernel itself. By sending messages to HAL, the kernel can read from or write to the file. So, we've traversed the layers of this onion that is the Windows operating system: a user program can make function calls into the documented Win32 DLL, which calls Ntdll.dll, which invokes the Executive, which calls the kernel, which asks the HAL to do something, which interacts with the physical hardware. In the end, a user-mode process can read from or write to a file, or perform other interactions with the hardware.
There's one crucial component of this process that we need to zoom in on: the transition from user mode to kernel mode, that all-important and nifty call gate concept. How does Ntdll.dll make calls into the kernel, invoking the Executive? In a sense, we're doing the equivalent of making a system call in Linux. However, Windows documentation doesn't refer to this concept using the words system call. Instead, the Windows terminology for this transition is referred to as system service dispatching, a much more high-brow sounding phrase than the simple system call wording of the Linux world. The idea,
however, is very much the same, as shown in Figure 8.23 , which is really a zoomed-in view of our earlier Figure 8.22 .
Figure 8.23. System service dispatching in Windows.
As with Linux, the transition between user mode and kernel mode occurs through the use of a CPU interrupt signal. For Windows, Ntdll.dll triggers interrupt number 0x2E on x86-compatible processors to invoke this transition. At this interrupt, a piece of code inside the Executive, called the system service dispatcher, needs to determine which kind of system service call is required of the kernel to invoke the appropriate underlying kernel code. Based on the information provided in the registers of the CPU at the time of the interrupt, the system service dispatcher looks in a table called the system service dispatch table. This table indicates where the appropriate system service code to handle the request is located in kernel memory. Sounds familiar, right? In essence, the system service dispatch table works a lot like the system call table in Linux. Execution flow is then transitioned to the appropriate kernel code. A good deal of this kernel code for implementing various system service calls is loaded into the kernel from a file called Win32k.sys, which implements much of the kernel mode functionality needed to service the user-mode Win32 API. In fact, about 200 kernel function calls are implemented in Ntoskrnl.exe itself, but more than 500 more function calls are loaded into the kernel during system boot from Win32k.sys. The Ntoskrnl.exe and Win32k.sys functions implement the required system service calls (e.g., reading or writing a file) by relying on even deeper code located in the HAL. All of the kernel data structures and code live at memory addresses
starting at 0x80000000 up to 0xC0000000.
So, we've got a high-level view of how Windows user mode and kernel mode fit together. Now, let's see it all in action on a live system. If you'd like to follow along at home, boot your Windows 2000, XP, or 2003 system and log on to the box. As we explore the Windows kernel, it's important to note that Windows includes fewer built in features for looking at the kernel than does Linux. In Linux, all of the tools that we used as kernel archaeologists to look at artifacts were built into the operating system. With a default Windows installation, there aren't nearly as many good built-in tools for kernel analysis. Some people might feel that less information about the bowels of a running Windows kernel helps improve security, as the bad guys cannot as easily find or alter sensitive data structures in the kernel. In essence, this is a security-through-obscurity argument. Unfortunately, security through obscurity isn't a huge hurdle for the bad guys. It might slow them down a bit as they reverse-engineer the system, but it also could lull system administrators into a false sense of the security level they've really achieved. Many gifted reverse engineers (both noble researchers and evil bad guys) are quite adept, and have created all kinds of tools for peering inside the Windows kernel. Just because the operating system doesn't ship with such tools built in, good guys and bad guys alike commonly rely on various tools to analyze the kernel as they develop software on Windows. We'll use some of these tools ourselves shortly.
To analyze Windows kernel artifacts, we'll use some built-in tools and a couple of additional freely downloadable tools on our machines. I'll let you know when we get to the point where you need to install extra software to follow along. Initially, we'll just use the built-in tools that Microsoft provides with Windows.
First, take a look at running processes on your machine. Hit the Ctrl+Alt+Del keys, select Task Manager, and click the Processes tab, as I have done in Figure 8.24 . Look at the top few processes, which are all associated with the kernel.
Figure 8.24. The Task Manager Process tab shows running processes.
The first process you see in your listing is the System Idle Process with a process ID (PID) of zero. The System Idle Process, truth be told, isn't really a process at all. Instead, it's a place where the kernel accounts for CPU time that isn't being used by real processes to do work. Next in the list, we see the System process, which always has a PID of 8. Now, this one is very important, as it is used to aggregate information about all of the running threads in kernel mode, whether they are in Ntoskrnl.exe, Win32k.sys, or other kernel-mode code.
Moving down the list, we see the process called Smss.exe, also known as the Session Manager. This crucial item is the first user-mode process that runs on the machine, activated by the kernel during system boot. In a sense, it is analogous to the UNIX init daemon, as the Session Manager's job is to prepare user mode and to activate other user-mode processes while the machine starts up.
Smss.exe, in turn, invokes Csrss.exe (the process that manages the Win32 subsystem) and Winlogon.exe (which lets users log on to the machine). Smss.exe, Csrss.exe, and Winlogon.exe, as well as everything invoked after them, run in user mode. However, although they all run in user mode, all of these processes do invoke numerous system service calls inside the kernel as they run, especially Csrss.exe.
Next, let's get a feel for how often the system runs in kernel mode by looking
at the performance view of the Windows Task Manager. Within the Task Manager, click the Performance tab, as I have done in Figure 8.25 . Go to the View menu and select Show Kernel Times. The CPU Usage History screen will now display the amount of CPU time devoted to user-mode processes in green. The red line indicates how much CPU time is spent running in kernel mode. Move your mouse around and run an application or two to see how the relative amount of time in user and kernel mode changes as you perform various actions on your system. The Performance view in Windows Task Manager also shows you the number of kilobytes of memory the kernel is using.
Figure 8.25. The Task Manager Performance tab separates user-mode and kernel mode performance data.
So, the kernel is indeed there, and it's burning up some CPU cycles. So far, we've just looked at the kernel usage of the CPU aggregated into a big amorphous blob of kernel time, without regard to which processes are making demands on the kernel, causing it to burn that kernel time. Using the Performance tool built into Windows, we can separate the amount of kernel time burned by individual processes. To accomplish this, bring up the Performance tool, by going to Start Control Panel
Administrative Tools
Performance.
Inside the Performance tool, click Add (which looks like a plus "+" sign). In the middle of the screen, in the pull-down menu labeled "Performance Object:," select Process. Note that we want to select Process and not Processor. The Process view will let us look at the CPU activity of individual processes, whereas the Processor view lumps everything together. Now, in the "Select counters from list" box, click %Privileged Time and, while holding down the Ctrl key, also select %User Time. Finally, click a process to analyze. We'll start out by looking at the System process. I've illustrated the settings for this view in Figure 8.26 .
Figure 8.26. Configuring Performance Monitor to look at individual processes, like the System process.
Select the Plus sign.
Select the "Process" Performance object.
Select the %Privileged Time and the %User Time.
Select the System process.
Now, click Add and then Close. The resulting graph is pretty tiny, so you might want to zoom in. To do so, right-click on the graph, select Properties, go to the Graph tab, and enter a Vertical Scale maximum of 5, instead of 100. Now, you'll see the relative amount of CPU time spent for that process on Privileged Time (which means that it's running in kernel mode) and User Time (which is, of course, user mode). To get some action going on the system, run your favorite word processor or a browser, which will burn some CPU cycles and cause system services dispatching to occur. You'll notice, as you might expect, that the System process spends all of its time in kernel mode. As we discussed earlier, that's because the System process is used to aggregate the time for all threads running in the kernel.
After looking at the System process, reconfigure the Performance tool to look at the privileged (i.e., kernel) time and user time associated with the Csrss and Explorer processes. Use the X icon to remove the previous graphs, and the + icon to add new ones. In Figure 8.27 , I've shown my Performance tool views of the System, Csrss, and Explorer processes on my box.
Figure 8.27. Performance tool view of privileged (kernel) and user times for the System, Csrss, and Explorer processes.
Note that the Csrss.exe process spends the vast majority of its time in kernel mode, but every once in a while burns a little time in user mode. Although a user-mode process, Csrss.exe invokes kernel functionality through system service dispatching a lot. The Explorer process, as you might recall from Chapter 7 , implements the Windows GUI, drawing all of those pretty pictures on your screen. The Explorer's performance view includes a fair amount of time in both user mode and kernel mode. It's important to note that Explorer really is a full user-mode process. However, the Performance tool displays the amount of kernel time that is spent by the kernel handling system service calls on behalf of the Explorer process. Therefore, we can see its normal user-mode time, as well as the time it takes the kernel to handle the requests of the Explorer process.
As we discussed earlier, there are a limited number of tools built into Windows for looking at kernel artifacts. We just looked at a few of them, but to get deeper into the kernel's activities, we need to install some additional tools on our Windows boxes. If you want to continue to follow along on our kernel adventure on your own system, please get a copy of the Process Explorer tool, written by Mark Russinovich, freely available at www.sysinternals.com/ntw2k/freeware/procexp.shtml . Additionally, snag yourself a copy of the no-cost Windows Dependency Walker tool, created by Steve P. Miller, at www.dependencywalker.com/ . To follow along, go ahead and download each tool and install them by simply unzipping their contents into a directory on your hard drive. These tools are for reading information only and not altering it, so they shouldn't have a negative impact on your system.
Based on the indentations you can see in Figure 8.28 , the System process (which contains the various kernel threads) started the Smss.exe process (which, as we've discussed, is the first user-mode process that runs).
Smss.exe, in turn, invoked the Csrss.exe and Winlogon.exe processes. I can look for each DLL used by these processes, including Gdi32.dll, Ntdll.dll, and others. By right-clicking a process and selecting Properties, I can even view the kernel time, security parameters, and environment variables associated with each running process.
Figure 8.28. Using Process Explorer to look at kernel and user time, as well as DLLs loaded by every running process.
Process Explorer gets us pretty deep into the guts of the system, looking at the elaborate dance of running processes. However, I'd like to go deeper, getting a glimpse of the function calls made between various components of the system. With that information, we could trace requests through the onion-like layers of Windows. Unlike Process Explorer, which showed us running processes, the Dependency Walker tool opens executable files and DLLs and determines the function calls and other DLLs that glue different EXEs and DLLs together. With Dependency Walker, we're not looking at real-live running processes. Instead,
we're checking out the relationships between the function calls and code stored in different executable and DLL files on our systems. One executable might call a given DLL, which, in turn, calls another DLL, which relies on yet other DLLs, right on down into the kernel. This information is tremendously useful in seeing how the kernel operates, as we can trace the relationships of user-mode processes, the various user-mode DLLs, Ntdll.dll, and the kernel itself. If you are following along, go ahead and run Dependency Walker by double-clicking it, or activating it from a command prompt by typing depends.exe in the directory where you unzipped the tool.
After invoking Dependency Walker, we need to select some application for which to analyze dependencies. Let's start out by opening up the simple editor Notepad, which has been built into Windows for years. On the File menu, select Open, and browse to your C:\Winnt\System32 directory (on Windows XP, you should look at C:\Windows\System32). Click Notepad.exe and then Open. You should see the view shown in Figure 8.29 , which tells us that the Notepad executable depends on the Comdlg32, Shell32, Msvcrt, Advapi32, Kernel32, Gdi32, User32, and Winspool DLLs. That's quite a list of code, for little old Notepad!
Figure 8.29. Dependency Walker shows the dependencies of Notepad.exe.
Next, expand the Kernel32.dll item under Notepad.exe. As I've shown in Figure 8.29 , we can see that Kernel32.dll depends on Ntdll.dll. Additionally, while Kernel32.dll is selected, the upper right-hand component of the window shows what function calls the parent (Notepad.exe) relies on from the selected DLL (Kernel32.dll). The column is labeled PI which stands for Parent Import. In particular, check out how Notepad.exe uses the WriteFile function provided by Kernel32.dll. In the middle of the screen, we can see all of the functions that Kernel32.dll offers up, whether Notepad.exe uses them or not. This primary
Now, let's take a step deeper down this rabbit hole. Under Kernel32.dll, select Ntdll.dll. Now, as illustrated in Figure 8.30 , we can see the NtWriteFile function that Kernel32.dll imports from Ntdll.dll. The linkage between the higher level WriteFile and lower level NtWriteFile is not displayed, however, as such intricacies could only be determined by processing the code inside of Ntdll.dll, an activity beyond Dependency Walker's capabilities.
Figure 8.30. Looking at Ntdll.dll in Dependency Walker to see NtWriteFile.
Unfortunately, we cannot jump past Ntdll.dll in Dependency Walker because the transition between user mode and kernel mode doesn't occur by a traditional function call. Instead, the system services dispatcher is invoked by a CPU interrupt, something Dependency Walker just cannot walk across. So, to peek inside of the code that runs in kernel mode, we'll have to open up the Ntoskrnl.exe file itself, located at C:\Winnt\System32\Ntoskrnl.exe on Windows 2000 and C:\Windows\System32\Ntoskrnl.exe on Windows XP. In Figure 8.31 , I've done just that.
Figure 8.31. Looking at the Ntoskrnl.exe program's dependencies, and the functions it makes available.
Here, we can see that the Ntoskrnl.exe file, (i.e., the kernel image on the hard drive itself) is dependent on HAL.dll, the HAL, and Bootvid.dll, a piece of code used to interface with the video drivers on the machine. Also, check out how Ntoskrnl.exe exports various functions to its parent (which as we discussed earlier, is Ntdll.dll). In particular, look at the NtWriteFile function that the kernel makes available. This is the function that Ntdll.dll will invoke through the system service dispatcher to write to a file.
At this point, we can go deeper into our kernel analysis by using a free tool that implements strace functionality for Windows NT, 2000, and XP. As you might recall from our earlier Linux discussion, strace shows a list of system calls made by a program as it is running. The folks at Bindview Corporation have released a free Windows version of strace that shows all system service dispatch calls made into the Windows kernel, available at http://razor.bindview.com/tools/desc/strace_readme.html . Although this Windows strace tool is extremely nifty, I caution you about using it. The Windows strace tool could make your system unstable, so you might want to avoid running it on anything but a test system that you can easily rebuild if it trashes your system. To give you a feel for how the Windows strace tool works, I've run it on my own system, displaying the system services invoked by the familiar Notepad file editor. As you'd expect, the Windows strace tool shows the invocation of the NTWriteFile function when I save a file using Notepad, as shown in Figure 8.32 .
Figure 8.32. Strace on Windows shows the system services called by Notepad.
Now that we've got some feel for how user-mode code invokes functions inside kernel mode, there's one final area of the kernel we need to look at: device drivers. In Windows, an administrator can alter the functionality of the kernel by adding device drivers, which are chunks of kernel-mode code. Device drivers can add or even replace various system service calls by altering the system service dispatch table or other kernel structures. In this regard, device drivers operate rather like Linux kernel modules. On Windows 2000, to view the installed device drivers, open your Control Panel, and select Administrative Tools. Now, open up Computer Management System Information
Software Environment
Drivers, to get the list shown in
Figure 8.33
. On Windows XP, select Start
Control Panel
Administrative Tools
Computer Management
Device Manager for a list of all devices and their drivers.
Figure 8.33. Looking at installed Windows device drivers on Windows 2000.
These installed device drivers include all kinds of goodies, such as code for
Methods for Manipulating the Windows Kernel
So, the Windows kernel and its associated APIs make up a complex beast, but they function appropriately for millions of users around the globe. Can you imagine anyone wanting to mess with such fine-tuned, complex harmonies? Well, of course, computer attackers want to manipulate the kernel to create kernel-mode RootKits. As you might expect with such complexity, there are numerous options for the bad guys in compromising a Windows kernel. Several kernel-mode RootKit projects are up and running on the Internet, but the most information-rich and prolific site dedicated to Windows RootKits is the www.rootkit.com Web site. Created and maintained by Greg Hoglund, www.rootkit.com is a virtual watering hole for developers of Windows RootKits to share code and ideas for improving their wares. The site features several discussion lists for different Windows RootKits, and offers up a few choice specimens for free download, including RootKits named Hacker Defender, HE4Hook, NT Rootkit, and GINA Trojan. To download any of the RootKits offered at www.rootkit.com , you'll need to register with the site for a free account. After receiving a user ID and password during the online registration process, anyone on the Internet can download and experiment with the user-mode and kernel-mode RootKits available at the site.
Interestingly, all five of the different Linux kernel manipulation tricks we discussed earlier in this chapter have direct analogies in the world of the Windows kernel. Namely, the bad guys could employ evil device drivers, alter a running kernel in memory, overwrite the kernel image on the hard drive, deploy a kernel on a virtual system to trick users, and try to run user-mode code at the kernel level. Now, each of these five elements on Windows machines is a possible avenue of attack, but the first two (employing evil device drivers and altering a running kernel in memory) are by far the most widely used. The other options are possible attack vectors, which could become more popular in the future. Let's look at each of these attack types in more detail.
Evil Device Drivers
One of the first and most popular techniques for manipulating the Windows kernel involves inserting a malicious device driver into the system, which patches the kernel to alter system service call handling. Just as bad guys exploit Linux kernel modules to load malware inside the Linux kernel, they utilize very similar tricks on Windows. By loading a specialized device driver that alters specific system service calls associated with listing running processes, showing files and directories, and identifying TCP and UDP port usage, an attacker can very effectively alter the kernel to hide a backdoor on the machine, as illustrated in Figure 8.34 .
Figure 8.34. Using a device driver to manipulate the Windows kernel.
So, an attacker can inject an evil device driver into the kernel to alter existing functionality and hide backdoor processes. Windows supports digital signatures on device drivers so that an administrator can verify the integrity of all drivers while they are first installed on the machine. However, with administrator privileges on the target machine, an attacker can easily install a device driver even without an appropriate signature. The system will warn the attacker that the device driver isn't signed by a trusted source, but the attacker can easily accept the warning and apply the malware driver.
However, once the device driver containing the attacker's code is inserted into the kernel, how does the attacker coax the Windows kernel into running the attacker's own code, instead of existing Windows kernel code for system service calls? Given the complexity of the Windows kernel, a huge variety of
options are available, three of which are illustrated as elements A, B, and C of Figure 8.34 . Each of these elements is really a form of API hooking, but this time inside the Windows kernel itself.
In element A, the attacker uses an evil device driver to simply overwrite existing kernel functionality, replacing the code inside the kernel with new code that will hide the attacker's actions by changing system service handling functionality. Alternatively, in element B, the attacker uses a device driver that implements various kernel functions, and then alters the system service dispatch table so that it points to the attacker's code instead of the existing kernel functionality. Finally, an attacker could employ a technique called interrupt hooking to modify how the kernel handles CPU interrupts, as shown in element C. By changing the table associated with interrupt handling in the kernel, the attacker could redirect calls to the system service dispatcher to the attacker's own code, instead of the built-in kernel functionality. Using interrupt hooking, the attacker could grab all calls to the system service dispatcher, and pick and choose which functions to handle with normal kernel processing, and which to deal with using the bad guy's code.
For an example of a popular kernel-mode RootKit for Windows that mixes elements A and B from Figure 8.34 , consider the Slanret/Krei tool, which is sometimes referred to as the Ierk8243.sys RootKit based on an embedded string and file name associated with the tool. Originally discovered in early 2003 on Windows 2000 and XP machines, Slanret/Krei actually consists of two pieces: the Slanret device driver and a remote access backdoor tool called Krei [20] . With administrator privileges, an attacker first loads the Slanret device driver onto the victim machine. In a mere 7 kilobytes of code, Slanret modifies the kernel so that it will lie about an attacker's hidden processes, files, registry keys, and TCP and UDP port numbers for any user-mode application that asks about them. What does Slanret hide in particular? It hides Krei, of course. After installing the device driver, the attacker loads the Krei backdoor, a 27-kilobyte user-mode application that listens on TCP port 449 and grants the attacker remote backdoor access to the victim machine. Of course, Slanret and Krei work hand in hand, in that Slanret masks all of Krei's actions.
Slanret is a pretty nasty kernel-mode RootKit, but its developers overlooked one important aspect. The Slanret device driver doesn't hide itself in the list of device drivers. When installed, Slanret will show up in the device driver list under the name IPSEC Helper Services or Virtual Memory Manager. These names sound like reasonable drivers, perhaps fooling a user or administrator into thinking that this driver somehow supports IPSec or the virtual memory system of the machine [21] . Some variations of Slanret call their device driver Ierk8243.sys, a more confusing but less subtle name.
An alternative strain of the Slanret tool uses the same basic code, but listens on a different port and uses a different driver name. The so-called BackDoor-ALI RootKit borrows almost all of Slanret/Krei, but listens on TCP port 961 and uses a driver called P2.SYS PentiumII Processor Driver [22] . With that name, it sounds like a pretty reasonable driver, right? Actually, it's a nasty kernel-mode RootKit.
Although Slanret doesn't do a good job of hiding its own device driver, a more thorough kernel-mode RootKit device driver could certainly hide itself. By using API hooking to grab the system services used by the machine to display active drivers, an attacker could eliminate this piece of evidence to create an even stealthier tool. Be on the lookout for such nasties in the very near future.
Altering a Running Kernel in Memory
Instead of using a device driver, an attacker could directly patch the kernel in the memory of the victim machine, a technique first described in detail by Greg Hoglund [23] . To understand Hoglund's technique, as well as the work of those who built on it, we need to look at how memory is handled in Windows, specifically with regard to the CPU running in Ring 0 and Ring 3. On a Windows machine, the Global Descriptor Table (GDT) contains information about how memory is divided into various segments, allocated to user programs and the kernel itself. As we discussed earlier, all memory locations between 0x80000000 and 0xC0000000 are for use by the kernel, and, under normal circumstances, can't be touched by user-mode processes. The GDT stores data about how various memory segments are carved up, and the CPU ring that is required for a program to touch each part of memory. The segments defined by the GDT can overlap with each other. That is, the same range of memory addresses can simultaneously be in multiple segments in the GDT. As you'd no doubt expect, the default GDT says that to access memory locations between 0x80000000 and 0xC0000000, you need to be running in Ring 0. That's kernel territory.
Here's the rub. By using several tricks to alter memory, an attacker can add a new entry to the GDT, thereby describing a new, attacker-defined segment that maps to a memory range. This new entry won't overwrite existing GDT entries, but will add another entry that refers to the same memory range included in other lines of the GDT. Guess what the new entry says. Yup, the new GDT entry could map out a memory space starting at 0x00000000 and going to 0xFFFFFFFF. On a 32-bit architecture, that's the entire memory space. If you are going to give yourself access to memory, you might as well go for the
whole enchilada. Of course, the new GDT entry allows someone running in Ring 3 to read from and write to this new overlapping segment. Bingo! By writing some machine language code that adds an entry to the GDT, the attacker can read and write kernel memory directly, as illustrated in Figure 8.35 .
Figure 8.35. Adding an element to the GDT creates a new segment that can be accessed from Ring 3.
Hoglund's paper includes code for altering the GDT in this way, and then exploits this technique to patch the running kernel so that it disables all security checking features of the machine. When any user tries to access a given object, such as a file or registry key, the SeAccessCheck function of the kernel verifies that the user has been granted the rights to touch the given object. By overwriting a mere 4 bytes of the Windows NT kernel in memory, Hoglund's patch bypasses all security checks associated with accessing objects on the victim machine by changing the internal kernel function call to SeAccessCheck. Suddenly, by applying this patch, an attacker can access any file, user account, registry setting, or anything else on the victim machine, without any pesky interference from the kernel and its security controls. This little 4-byte patch demonstrates the power of being able to manipulate the running kernel's memory image. If I can read or write the kernel's memory, I can alter its code to shut off security. Alternatively, I could intercept system service calls and implement code for all of the fancy hiding techniques we saw with evil device drivers.
Building on some of the concepts in Hoglund's paper and introducing additional ideas, a developer named Crazylord released another Windows kernel manipulation paper that delves deeper into the kernel [24] . Crazylord's technique involves utilizing an object in the Windows kernel called \Device\PhysicalMemory. As you might expect with that name, this object contains a representation of all physical memory on the Windows system, both
user and kernel memory. Microsoft included this object inside the Windows kernel so that the kernel could track and help control memory use on the box. To look at this interesting object, Mark Russinovich released a free tool called PhysMem, available at www.sysinternals.com , that shows the contents of this device. Starting with Hoglund's ideas and the PhysMem tool, Crazylord implemented code that gives an attacker the ability to view, search, and alter any memory on the victim machine, including kernel memory. In essence, Crazylord's project provides a view of Windows kernel memory much like /dev/kmem in Linux. And, of course, Crazylord's project allows for manipulating this memory in much the same way as the read kernel memory (rkm) and write kernel memory (wkm) functions exploited by Sd and Devik on Linux. So, now we have /dev/kmem-like attacks on a Windows machine.
Using these techniques, an attacker can manipulate the kernel and change system service functionality, thereby hiding files, processes, registry keys, and any other aspect of the system from an administrator. Although Crazylord's article didn't include a RootKit, his Windows /dev/kmem technique offers a starting point for other attackers to create kernel-mode RootKits without the use of device drivers.
Building on his earlier techniques, Hoglund released a tool called NT RootKit. Don't be thrown by the name, however. This tool will run on Windows NT, 2000, and XP. By the time you read this, a Windows 2003-compatible version might have been released. The NT RootKit includes several kernel-mode RootKit features, including file, process, and registry key hiding. It can also perform execution redirection for any user-mode executable process on the machine. Some versions also include a built-in keystroke logger, which records everything typed at the keyboard inside a hidden file for the attacker.
Configuration of the NT RootKit couldn't be much easier. Any file, registry key, or process with a name that starts with _root_ will be automatically hidden. So, the bad guy can just name all of the malicious stuff loaded on to the victim machine appropriately, and it disappears.
The NT RootKit also implements a form of the cone of silence concept we saw
earlier with the Linux KIS Tool. If a running process has a name that starts
with _root_, it is, of course hidden, but any hidden process is able to see
hidden files, processes, and registry keys. Therefore, an attacker could make a
copy of the Windows command shell (Cmd.exe), prepending _root_ to its
name. Whenever _root_cmd.exe is executed, the resulting command shell will
not only be invisible, but it will also have the ability to see any of the hidden
items on the machine. Similarly, a version of the Windows Task Manager
(Taskmgr.exe) or the Registry Editor (Regedit.exe or Regedt32.exe) with
_root_ prepended to its name will be able to see hidden processes and registry
Patching the Kernel on the Hard Drive
Instead of patching the Windows kernel in memory, an attacker could also alter the kernel image file on the hard drive, replacing functionality inside of Ntoskrnl.exe with modified software that provides a backdoor and hides an attacker's presence on the machine. Now, an attacker cannot alter the Ntoskrnl.exe file by itself, because the integrity of this file is checked each time the system boots. During the boot process, a program called NTLDR verifies the integrity of Ntoskrnl.exe before the kernel is loaded into memory. If the Ntoskrnl.exe file has been altered, the NTLDR program displays a fearsome blue-screen-of-death message, indicating that the kernel itself is corrupt. The system boot never completes, and both the administrator and the attacker are unhappy. Believe me, it's extremely disconcerting to have your system tell you that your kernel is corrupt during a system boot!
To get around this difficulty, the bad guys manipulate both the NTLDR and the Ntoskrnl.exe files. Using a small patch to overwrite a few machine language instructions inside of NTLDR so that it skips its integrity check, the attackers can then freely alter Ntoskrnl.exe at will, as illustrated in Figure 8.36 . In Step 1, the modified NTLDR file is copied to memory at the start of system boot. The NTLDR program had been altered to skip the integrity check of the Ntoskrnl.exe file. Therefore, in Step 2, the manipulated Ntoskrnl.exe file is loaded into memory, with all kinds of nasty surprises loaded inside.
Figure 8.36. Modifying NTLDR to skip the Ntoskrnl.exe integrity check, and then modifying Ntoskrnl.exe.
Although this technique hasn't yet been widely used to implement backdoor access and full-fledged RootKits, several viruses have used the technique over the past few years. In particular, the Bolzano and FunLove viruses from 1999 altered NTLDR and Ntoskrnl.exe [25] . Both viruses applied a small patch to the kernel file so that the SeAccessCheck security functionality was disabled, implementing in the kernel file the same basic attack that Hoglund applied to a running kernel's memory. With the security checking functionality disabled, the Bolzano and FunLove viruses could access and alter any objects on the infected machine. Although these viruses targeted just Windows NT and only disabled the SeAccessCheck function, a complete Windows RootKit could be implemented using similar tactics to alter system service calls inside Ntoskrnl.exe and Win32k.sys. To date, no mainstream Windows RootKit has employed such techniques, leaving it relegated to just a handful of rather obscure viruses for the time being.
Creating a Fake System Using a Virtual Machine
Earlier we saw how an attacker could employ UML to create a virtual Linux machine running on top of a compromised Linux system. Administrators and users would think they are logging into the real machine, but are instead logging into a guest operating system built on top of the real system owned by the attacker. A similar approach could be applied against a Windows environment as well. To run a virtual Windows machine, an attacker could install any one of several virtual machine environments that run on Windows, listed in Table 8.4 .
Table 8.4. Virtual Machine Tools That Could Be Abused to Trick Users
|
Tool Name |
Commercial/Free |
Host Operating Systems Supported |
Location |
||||
|
|
|
|
|
|
|
|
|
|
VMWare |
Commercial |
Linux and Windows |
|
||||
|
VirtualPC |
Commercial |
Windows, MacOS X, and OS/2 |
|
||||
|
Plex86 |
Free |
Linux | |||||
|
Bochs |
Free |
Linux, Windows, and MacOS X |
|
||||
|
|
|
|
|
|
|
|
|
One significant disadvantage for the attacker of using any of the tools listed in Table 8.4 involves the complexity and lack of transparency in the virtual machine initialization process on Windows. Sure, the attacker could break into a Windows machine, install a virtual machine tool, build a virtual system that mimics the original machine, and then configure the entire mess to start up appropriately at boot using startup scripts. However, the boot process of the compromised host operating system and the activation of the virtual machine tool would likely be noticed by a system administrator. A similar hurdle is faced by the Linux attackers who employ UML. However, the UML startup script can be disguised so that it doesn't really show any activity to a user watching the boot process on the screen. Fooling an administrator or user sitting at the console of a machine that suddenly starts VMWare, VirtualPC, Plex86, or Bochs is a much more daunting task for the attacker. Each of the virtual machine tools listed in Table 8.4 displays significant amounts of information on the screen as it is activated. Therefore, although still a possibility, this virtual machine approach is less likely to be used on a Windows machine than on Linux.
Kernel Mode Windows? Maybe Someday…Soon
Earlier we discussed how an attacker could use the KML project to run in Ring 0 arbitrary programs designed for user mode, provided that the Linux kernel was built with the appropriate KML hooks. As of this writing, no one has created a full-fledged kernel-mode Windows tool that runs user-mode-style programs inside the Windows kernel. However, there is ongoing work moving in this direction.
In particular, the NT RootKit development team is extending the NT RootKit itself so that it can run any user-mode program inside the kernel. In particular, they are focused on running the Cmd.exe command shell from within kernel mode. That way, an attacker can get a shell prompt that has complete access to any kernel mode data structures, at the same time remaining hidden to all
user-mode processes.
Generalizing such a tool beyond a command shell is an arduous task, as the developer has to carefully manage memory access inside the kernel to create a kernel-mode Windows tool. A user-mode program running in kernel mode could easily behave like a bull in a china shop, accidentally smashing critical data structures, rendering the system unstable or even crashing it. Still, in time, I expect to see a generalized kernel-mode Windows implementation that acts as a shield of protection around the bull (i.e., a user-mode program) inside the china shop of the Windows kernel. The shield doesn't protect the bull, mind you, but is instead designed to protect the china shop itself from accidental destruction. Of course, for a bad guy to use such a tool for manipulating the kernel, the shield of protection would need selective holes so the attacker could alter some aspects of the china shop without bringing the whole thing down. Stay tuned for more development on this front.
Defending the Windows Kernel
With Windows kernels exposed to similar types of attacks as the Linux kernel, we must carefully shore up the security of our Windows machines as well. Let's analyze the defenses against Windows kernel manipulation by stepping through the same three categories we discussed for Linux kernel mode attacks: prevention, detection, and response.
Prevention
As with most of the malware we've covered in this book, a crucial element of your defensive plan is to keep the bad guys off of your system by hardening the configuration and applying patches in a timely manner. Such defenses are just as important on Windows as they are on UNIX systems. In addition to these incredibly important base recommendations, though, you might want to consider another class of tools that can help prevent installation of kernel-mode RootKits: intrusion prevention systems (IPSs).
Frankly, I'm not a big fan of the terminology intrusion prevention system, as that name is so ambiguous, it could refer to a multitude of products, ranging from firewalls to smart card authentication tokens and more. However, due to various marketing initiatives, the IPS moniker has stuck to a class of products that are installed on individual end systems to thwart various attacks used to break into the box. I don't like the name IPS, but I am a fan of the functionality offered by these tools. These IPS solutions limit the exposure of
your system by locking out functionality often abused by attackers to obtain superuser privileges on a target system. Think of an IPS like a little shield surrounding various critical components of your system, watching and stopping suspicious activity associated with breaking into the box. These activities include some buffer overflow attacks, various race conditions, and suspicious system service calls.
Cisco's Security Agent (formerly known as Okena Storm Watch), Network Associates' Entercept, and Watchguard's ServerLock products are all examples of commercial IPS tools that run on a Windows platform. They offer a variety of protection strategies, but one of the most worthwhile capabilities of these tools involves limiting the system service calls that various applications can make on the machine. As you might recall from earlier in this chapter, the free Systrace tool offers such protection on Linux, FreeBSD, and MacOS X systems. The commercial IPS tools offer similar capabilities on Windows. By configuring the IPS to limit what system calls a given program (e.g., a Web server, mail server, or DNS server application) can make, the bad guys will have a far more difficult time compromising administrator privileges and installing RootKits. Also, some commercial IPS tools support operating systems besides just Windows. In particular, the Cisco Security Agent runs on Windows and Solaris. Entercept is available for Windows, Solaris, and HP-UX. Watchguard focuses on Windows and Solaris systems.
We should note that configuration and maintenance of these IPS tools is no small task in a production environment. You need to install the tool and carefully configure it so that it interoperates appropriately with the application mix on a given machine, allowing the functionality the application needs to run while locking out those functions that aren't required. In a sense, the tool has to be trained regarding normal activity for the machine so that it can spot and stop abnormal behavior. However, after configuring the IPS tool to support the given machine, you've added a significant extra measure of security to the box.
Detection
To detect a kernel-mode RootKit on Windows, many antivirus tools include signatures for dozens of kernel manipulating tools, such as Slanret and the NT RootKit. When an antivirus tool spots a kernel-mode RootKit on the hard drive by matching the contents of the file to one of its signatures, it will quarantine the file so that it cannot be executed and installed. Therefore, a widely deployed and up-to-date antivirus infrastructure, as we first discussed in Chapter 2 , supports both the prevention and detection of Windows kernel-
These antivirus signatures work best before the kernel modifying attack tool is installed, so proactive deployment of antivirus tools is now more important than ever. After the kernel-mode RootKit is installed, the antivirus tool has less of a chance to detect it, because the hiding capabilities of the RootKit could help mask it from the antivirus program. However, many kernel-mode RootKits on Windows can be spotted by an antivirus tool even after the RootKit is installed, due to holes in the RootKit's hiding mechanisms. For example, Slanret leaves its device driver name exposed, a telltale sign that can be detected by an antivirus tool even after the kernel manipulation is applied.
Although antivirus solutions offer a significant level of protection from these forms of malware, you should also consider deploying a file integrity checking tool, such as the commercial Tripwire, GFI LANguard System Integrity Monitor, and Ionx Data Sentinel tools. As we discussed in Chapter 7 , each of these tools can spot file changes made by a kernel-mode RootKit, if the developer or attacker utilizing the RootKit forgets to disguise such changes. As we noted in the Linux kernel-mode RootKit defenses, it's quite common for attackers to fail to hide all of their file changes with a kernel-mode RootKit. Therefore, looking for these changes with a file integrity checking tool is a sound strategy.
Response
When responding to an attack that employs a kernel-mode RootKit on your Windows machine, make sure you bring a CD-ROM with a fresh copy of your antivirus tool installation and the latest signatures. Many Windows antivirus tools can detect and then uninstall various kernel-mode RootKits, and having this capability in the field for incident response is invaluable. Just install the antivirus program on the victim machine, keep your fingers crossed that it has a signature to find the already-installed RootKit, and then tell the antivirus tool to remove the offending malware.
If the antivirus tool cannot find or remove the malware, you'll need to perform a more detailed analysis of the system without relying on the embedded kernel. Again, the FIRE and Knoppix bootable Linux CD-ROMs come in handy. "How can I use a Linux CD-ROM to analyze my Windows system?" you might ask. Well, although FIRE and Knoppix are bootable Linux images, they include a variety of tools for looking at Windows disk partitions. So, to analyze the system in more detail, you'd configure the system to boot from FIRE or Knoppix, thereby starting a Linux environment. Then, you'd run various Linux tools inside of FIRE or Knoppix to analyze the Windows partition of your
machine. FIRE, my favorite tool for performing such analyses, includes a variety of items for analyzing a Windows hard drive, shown in Table 8.5 .
Table 8.5. FIRE Tools for Analyzing a Windows CD-ROM
Tool Name Description
F-prot
Editreg
The Sleuth Kit (formerly called TASK)
A free demo version of the commercial F-prot virus scanner from FRISK Software International. This version can search for Windows and Linux malware, including a variety of kernel-mode RootKits.
A Linux command-line tool for searching and altering the registry on a Windows partition.
A Linux tool for forensics analysis of hard drive images, including various UNIX drive formats, but also Windows FAT and NTFS partitions.
Currently, a bootable Linux CD-ROM is the best way to go, as there aren't any solid bootable forensics CD-ROM images of Windows publicly available at the time of this writing. Microsoft's licensing for Windows prohibits people from creating such a Windows distribution, developing a CD-ROM image of it, and making it available for download on the Internet. Doing that, someone would in essence be giving away Windows, certainly a no-no from a license perspective. Therefore, in our incident-handling operations, we utilize a Linux CD-ROM like FIRE with its built-in tools to support incident handling on our Windows machines.
Armed with these tools on the handy, free FIRE CD-ROM, you'll be able to conduct solid searches of your registry and file system to conduct a detailed forensics analysis of the machine. This book doesn't cover forensics analysis in detail, but I recommend that you grab a copy of Computer Forensics: Incident Response Essentials by Warren Kruse and Jay Heiser for an introduction to the craft of computer forensics, or Incident Response by Chris Prosise and Keven Mandia for more details on forensics investigations.
Attackers have a plethora of options for manipulating the kernel, from hooking a few kernel-level API calls to complete replacement of the kernel itself. Using these powerful techniques, bad guys can implement extremely stealthy RootKits, making it very difficult to detect and remove them once they gain superuser access on a victim machine. In the last few chapters, we've seen the gradual progression of malware attacks from general backdoors, to user-mode RootKits, to kernel manipulation itself. But is the kernel the deepest possibility we face when fighting malware? Actually, bad guys might go even deeper, as we'll explore in the next chapter.
Summary
By manipulating the underlying kernel of an operating system, an attacker can exercise fundamentally deeper control of a victim machine than with user-mode RootKits. Burrowing into the kernel with a kernel-mode RootKit is a remarkably effective technique for masking the attacker's presence on a system. The kernel is the heart of the operating system, controlling processes, memory, the file system, other hardware elements, and interrupts. The kernel relies on protections built into the CPU hardware, such as the various rings on an x86-compatible CPU. Both Linux and Windows use Ring 0 for kernel mode operations and Ring 3 for user mode. Running in kernel mode (i.e., Ring 0) is different from running with root or administrator privileges. Programs running with root or administrative privileges still live in user mode and have very limited access to kernel-mode data structures. Code that allows program execution to transition from Ring 3 to Ring 0 is sometimes referred to as a call gate.
With a kernel-mode RootKit, an attacker can alter the underlying kernel to hide files, directories, network ports, and promiscuous mode. Additionally, an attacker could configure the kernel to redirect any execution requests to different programs of the attacker's own choosing. Finally, the attacker can intercept and control any requests for the system's hardware. By controlling the kernel, the attacker alters the underlying structures that programs like ls, netstat, and lsof rely on, making disguising the attacker's actions more comprehensive.
The Linux kernel creates a virtual file system called /proc. Inside of /proc, the kernel stores information about each running process and its own state. By looking inside of /proc, we can view these structures and even tweak various kernel settings. Of particular interest in /proc is the list of modules installed in the kernel. The /proc/modules file shows which kernel modules have been installed to extend the capabilities of the kernel. The /dev/kmem file holds a view of kernel memory. However, without an appropriate parser, this memory is mostly gibberish to any human wanting to comb through it.
To interact with the kernel, a user-mode process calls a system library. The system library, in turn, makes a system call into the kernel by causing an interrupt on the CPU. The system call table determines which part of the kernel's code will be used to handle the system call. The original system call table for the machine can be found in the syscall.h or a related file. System calls include SYS_open, SYS_read, and SYS_execve, for opening, reading, and executing files, respectively. To view the base set of system calls supported on
your machine, you can look at the System.map file. All kernel data and code in a Linux system is stored at memory location 0xC0000000 and above. The strace tool shows the various system calls made by a running application.
To manipulate a Linux kernel, an attacker could use five different strategies: using evil loadable kernel modules, altering /dev/kmem, patching the kernel image file, creating a fake system with UML, and altering the kernel with KML. The most common type of kernel-mode RootKit involves loadable kernel modules. These modules typically alter the system call table so that it points to the attacker's code. In a sense, the attackers are implementing API hooking inside the kernel itself. Adore and KIS are two tools that utilize this technique. To reload any modules during system boot, the bad guys frequently alter the init daemon to apply kernel changes at system boot. Manipulating /dev/kmem allows an attacker to alter the kernel without using modules. The SucKIT kernel-mode RootKit employs this technique.
An attacker could patch the kernel image on the hard drive by changing the vmlinuz file. This file can be altered to build various evil kernel modules right into the kernel file itself. With UML, an attacker can create a fake guest operating system to trick administrators and users into thinking they are on the real system. The attacker really owns and controls the underlying host operating system. KML extends a kernel so that user-mode programs can run in Ring 0 and have direct access to kernel structures.
To defend the Linux kernel, you need to prevent attackers from getting root-level access in the first place by hardening the system's configuration and applying patches. You can also build a kernel that doesn't support loadable kernel modules to complicate the process of installing a kernel-mode RootKit for the attackers. The Systrace tool can limit the system calls made by specific applications to prevent abuse by attackers. Linux Security Modules also add extra security capabilities to a Linux system, thereby limiting the attacks a bad guy can mount.
To detect a kernel-mode RootKit on Linux, you can use a file integrity checking tool to look for mistakes made by the attacker or the RootKit. Also, the chkrootkit tool helps identify several kernel-mode RootKits by looking for inconsistencies introduced by the kernel manipulation. Finally, the KSTAT, Syscall Sentry, KSEC, and Listsyscalls tools look for alterations to the system call table on various types of UNIX systems.
When responding to a kernel-mode RootKit attack, a bootable Linux CD-ROM is extremely helpful. By booting to a trusted kernel, a forensics analyst can scour the file system of the impacted machine and trust the results displayed by the tool. The FIRE and Knoppix CD-ROMs are especially valuable in this type of
analysis.
The Windows kernel offers several analogous structures to the Linux kernel. A user-mode program calls various Win32 subsystem DLLs running at user mode to interact with the kernel. These calls are passed through a piece of user-mode code called Ntdll.dll, which causes an interrupt to pass control into the kernel. The Win32 DLLs are highly documented, as Microsoft intends for developers to write code for these interfaces. The Ntdll.dll interface is not documented in detail, as it applies to internal Windows functionality, such as interfacing with the kernel.
The interrupt caused by Ntdll.dll makes the system activate the system service dispatcher to determine which kernel code inside of Ntoskrnl.exe to invoke to handle the system service. The system service dispatcher relies on the system service dispatch table to make this determination. The system service dispatch table in Windows is roughly analogous to the Linux system call table. This table points to functionality inside of Ntoskrnl.exe, including those functions loaded from Win32k.sys.
To analyze the Windows kernel, you can look for the total kernel time burned by the CPU using the Task Manager. Alternatively, you can use the Performance tool to view the kernel time (called Privileged Time) used by individual processes. Using the Process Explorer tool, you can see how various processes are invoked during system startup, including the Smss.exe process, which is the first user-mode process to run on the machine, and Csrss.exe, which invokes and controls various other user-mode programs. The Dependency Walker tool shows various function calls made by programs and the DLLs that support these calls. You can use Dependency Walker to view the functions offered by Ntdll.dll, Ntoskrnl.exe, and other parts of the system. Finally, the Windows strace tool from Bindview shows each system service call a particular program makes as it runs.
Each of the five methods for manipulating a Linux kernel has a counterpart that could be implemented against a Windows system. The most popular Windows kernel attacks involve device drivers that manipulate interrupt handling, system service dispatching, or the underlying kernel functionality for handling system services. Each of these techniques is really a form of API hooking. The Slanret/Krei tool is one example of an evil device driver that alters the Windows kernel by inserting a driver called IPSEC Helper Services, Virtual Memory Manager, or P2.SYS PentiumII Processor Driver. In reality, the tool hides an attacker's backdoor on the machine.
An attacker could alter a running kernel in memory by manipulating the Global Descriptor Table or altering the \Device\Physical Memory object. The NT
RootKit employs such techniques to create a cone of silence around all files, processes, and registry keys with names that start with _root_. To patch a kernel image file on the hard drive, the attacker first must alter the NTLDR program to disable its kernel integrity check. Otherwise, the system will not boot. The Bolzano and FunLove viruses employ this technique to disable security settings on the victim machine. Alternatively, an attacker could employ a virtual machine environment such as VMWare or VirtualPC to create a fake system that is a prison for administrators and users. Finally, an attacker could alter the kernel so that user-mode programs could run in Ring 0, thereby implementing a kernel-mode Windows tool. Although no such kernel-mode Windows project currently exists, there are development efforts moving in that direction.
To defend the Windows kernel, you should apply patches and harden the system. Also, IPSs can be used to limit the system calls and other actions of applications on a protected machine, thereby increasing the security of the system. Antivirus tools can detect and prevent kernel-mode RootKits before they are installed, and on some occasions even after installation occurs. File integrity checking tools can also help find a kernel-mode RootKit, if the attacker or developer forgets to hide some changes to critical system files. When responding to attacks with a kernel-mode RootKit on Windows systems, you can utilize the free bootable FIRE and Knoppix CD-ROMs, which include Linux programs for analyzing the Windows registry and file system.