Kernel mode rootkits are extremely dangerous to the runtime integrity of a Linux system. These rootkits have the power to add, delete, or modify any data that kernel or userland applications request about the state of the system. This can include information such as lists of running processes, loaded kernel modules, active network connections, files within a directory, and even the contents of those files. Kernel mode rootkits can also monitor user activity including keystrokes, network packets, and interactions with hardware such as removable media or security devices. To find kernel mode rootkits, you must perform deep inspection of the running kernel, including its code and data structures.
Kernel rootkits can hook many places to subvert the system, and accordingly, Volatility has powerful support for enumerating and verifying in-kernel data. Many of these are newly developed capabilities and exclusive to Volatility, such as finding Netfilter hooks and copied credential structures. Throughout this chapter, you’ll see several of these plugins in action and you’ll learn how to use them in your own investigations.
To install and use a kernel mode rootkit, attackers must first obtain root-level privileges. They can gain access to this permission level either through a social engineering attack (e.g., convincing an administrator to install a malicious application) or by remotely exploiting a network service that runs with root privilege. Alternatively, an adversary could gain access to a local user account and then execute a privilege escalation attack. Such attack techniques are out of the scope of this book, but you can learn about them in many places, such as A Guide to Kernel Exploitation: Attacking the Core by Enrico Perla and Massimiliano Okdani, Syngress, 2010 (http://www.attackingthecore.com). Once the adversary has acquired root level privileges, they can leverage one of the native methods that Linux provides for writing into the kernel address space (also known as ring 0) of a running system.
One method of getting “inside” the kernel is to use loadable kernel modules (LKMs). In Chapter 23, you learned how modules are loaded, how they are enumerated, and how they are recovered to disk through memory forensics. LKMs are the most popular method of gaining access to the kernel because you can write them in the C programming language. Additionally, LKMs have full access to the kernel’s APIs and data structures. This makes rootkits portable across different kernel versions and also provides stability because the LKM can obtain the proper synchronization locks before manipulating data structures.
Another interface into kernel memory that Linux provides involves the character devices /dev/mem
and /dev/kmem
. These devices were discussed in Chapter 19, in which you learned about memory acquisition methods. On older distributions on Linux, the /dev/mem
device exports (i.e., allows access to) the first gigabyte of physical memory, and /dev/kmem
exports the kernel virtual address space. However, on modern Linux distributions, /dev/mem
allows access only to the first megabyte of RAM. This lets programs that read physical memory of hardware devices, such as the X server, to operate correctly without exposing a significant amount of sensitive kernel code and data.
Despite the aforementioned challenges, rootkits can still access more than the first megabyte of RAM using /dev/mem
. For example, as you will see in Chapter 27, the Phalanx2 rootkit loads an LKM to disable the /dev/mem
restrictions and then maps its own view of /dev/mem
. The new mapping allows it to read and write physical memory from its userland process. However, directly accessing /dev/mem
and /dev/kmem
poses a number of challenges because the rootkit author cannot rely on system APIs to perform tasks. Instead, the rootkit must read and write data structures found in memory directly, similar to the process that memory forensics tools use.
The first task that many malicious LKMs perform is to hide the kernel module from the running system. System administrators and security tools frequently use the lsmod
command, which reads from /proc/modules
, to check for malicious LKMs. By simply deleting itself from the module list in kernel memory, a malicious module cannot be found through such methods, but the rootkit will still operate correctly. This hiding technique requires only a single line of code:
list_del_init(&__this_module.list);
This code obtains a reference to the current module through __this_module
and then uses the list_del_init
macro to remove it from the list. __this_module
is a special section of the ELF file in memory that holds the module
structure of the LKM.
The sysfs
file system also exports module information. For example, on a live system, the /sys/module
directory has a subdirectory for each active module. These directories contain information such as the module’s name, its sections, and its parameters. Security tools can cross-reference the modules in this list with the ones reported by lsmod
to detect certain rootkits. However, it’s also possible to hide from sysfs
. Rootkits that want to do this can use the following line of code to accomplish the task:
kobject_del(__this_module.holders_dir->parent);
This code finds a reference to the sysfs
data structure (holders_dir
) and then removes itself from the parent’s list of children.
For the rootkits that hide only from the modules list, not from sysfs
, you can use the linux_check_modules
plugin to automatically compare the modules and display discrepancies. The following shows this plugin against a system infected with the KBeast rootkit:
$ python vol.py -f kbeast.lime --profile=LinuxDebianx86 linux_check_modules
Volatility Foundation Volatility Framework 2.4
Offset (V) Module Name
---------- ----------------
0xf841a258 ipsecs_kbeast_v1
Volatility found the ipsecs_kbeast_v1
module of KBeast because it hides only from the module list, not sysfs
. You can then use the linux_moddump
plugin to extract and statically analyze the kernel module. The following command dumps the module to disk:
$ python vol.py --profile=LinuxDebianx86 -f kbeast.lime
linux_moddump -b 0xf841a258 -D outdir
Volatility Foundation Volatility Framework 2.4
Wrote 33798 bytes to ipsecs_kbeast_v1.0xf841a258.lkm
If you examine the beginning of the module’s init
function, you see that the module is immediately deleted from the global list:
static int init(void) {
list_del_init(&__this_module.list);
Examination of list_del_init
shows that it unlinks the current entry from the given list and then sets the next
and prev
pointers of the list back to itself:
1 static inline void INIT_LIST_HEAD(struct list_head *list) {
2 list->next = list;
3 list->prev = list;
4 }
5
6 static inline void __list_del(struct list_head * prev,
7 struct list_head * next) {
8 next->prev = prev;
9 prev->next = next;
10 }
11
12 static inline void list_del_init(struct list_head *entry) {
13 __list_del(entry->prev, entry->next);
14 INIT_LIST_HEAD(entry);
15 }
As shown, the list_del_init
function, on lines 12–15, calls __list_del
with the prev
and next
members of the entry. Then it passes the entry to INIT_LIST_HEAD
.
The disassembly of the extracted module in the following code shows these steps as well. We have added labels to several instructions to assist with interpretation of the instructions:
public init_module
init_module proc near
mov edx, ds:0f841A25Ch ; this_module.next
mov eax, ds:0F841A260h ; this_module.prev
push ebx
mov [edx+4], eax ; this_module.next.prev = this_module.prev
mov [eax], edx ; this_module.prev.next = this_module.next
mov eax, ds:0C14CBB2Ch
mov dword ptr ds:0F841A25Ch, 0F841A25Ch ; this_module.next = this_module
mov dword ptr ds:0F841A260h, 0F841A25Ch ; this_module.prev = this_module
The first mov
instruction accesses 0xf841a25c
, which is four bytes into the start of the module
structure for KBeast (remember that the parameter you passed to linux_moddump
was 0xf841a258)
. To determine exactly which structure member the code is accessing, you can display the module
structure in linux_volshell
. For example, in the following code, the member at offset four is list
:
>>> dt("module")
'module' (352 bytes)
[snip]
0x4 : list ['list_head']
Because next
is the first member of list_head
, its address is the same as the beginning of the list_head
structure.
The second mov
shown in the previous code is eight bytes into the module
structure, which corresponds to the prev
member. The next two commented lines set the entry.next.prev
and entry.prev.next
members, just like lines 6–9 of the source code listing. The last two commented lines correspond to lines 1–4 of the source code, in which the next
and prev
members of the module are set to the module itself. These instructions successfully unlink the module from the module list.
Carving is the process of searching raw input (disk image, memory sample, network capture) for data such as files and structures. To find LKMs that hide from both the module list and sysfs
, you can use the linux_hidden_modules
plugin. This plugin scans memory for instances of a module
structure and then compares the results with the list of modules reported by linux_lsmod
. Modules that are found through carving, but not the linked list of modules, are reported. The results can include modules that are hidden and those that have been freed but not overwritten (i.e., lingering in unallocated storage).
To improve performance, the plugin uses the module_addr_min
and module_addr_max
variables within the kernel. These variables indicate the range of addresses of where kernel module structures are allocated, thus they provide a lower and upper bound for the scanner to search. Scanning this area is considerably faster than scanning the entire virtual address space, but it is just as thorough because the OS should never allocate module
structures from outside of the range. The validity of each candidate kernel module is then evaluated by leveraging a set of constraints. To reduce the opportunities for subversion, the constraints check members of the structures that are essential to the stability of the system, such as the size of sections and pointers to the module’s code and data.
The following code shows the linux_hidden_modules
plugin against a memory sample infected with suterusu
:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime linux_hidden_modules
Volatility Foundation Volatility Framework 2.4
Offset (V) Name
------------------ ----
0xffffffffa03a15d0 suterusu
The output presents the virtual address of the module
structure as well as the module name gathered from the name
member. You can then use linux_moddump
with the given virtual offset to recover the kernel module from memory:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime linux_moddump
-D dump -b 0xffffffffa03a15d0
Wrote 3625087 bytes to dump/suterusu.0xffffffffa039e000.lkm
The kernel module is dumped to a file with the .lkm
extension. You can now analyze the LKM using traditional executable analysis methods (e.g., disassemble with IDA Pro; scan with antivirus signatures). In the next output, the readelf
command parses the ELF’s symbol table and identifies various functions that appear to hide TCP and UDP ports:
$ readelf -W -s suterusu.0xffffffffa039e000.lkm
<snip>
15: 0000000000000878 69 FUNC GLOBAL DEFAULT 2 hide_udp4_port
17: 000000000000096e 69 FUNC GLOBAL DEFAULT 2 hide_proc
18: 0000000000000938 54 FUNC GLOBAL DEFAULT 2 unhide_udp6_port
24: 0000000000000782 69 FUNC GLOBAL DEFAULT 2 hide_tcp4_port
25: 00000000000007fd 69 FUNC GLOBAL DEFAULT 2 hide_tcp6_port
29: 00000000000007c7 54 FUNC GLOBAL DEFAULT 2 unhide_tcp4_port
30: 0000000000000a2f 85 FUNC GLOBAL DEFAULT 2 unhide_file
32: 0000000000001144 144 FUNC GLOBAL DEFAULT 2 hijack_stop
<snip>
Presumably, the rootkit hooks system APIs and points them at these functions in the malicious LKM. In the upcoming “Resolving Malicious Functions” section of this chapter, we describe how to associate the hooked addresses with specific functions in the rootkit.
Regardless of the way a rootkit hides its own LKM, the LKM must request resources from the operating system to function—such as file handles and network connections. Thus, in the rare case when you cannot locate an LKM using the aforementioned plugins, you can identify artifacts that the malicious LKM creates. For example, usually only processes (not kernel modules) open files. Therefore, any file open within the kernel, except for a swap device, warrants further inspection.
One of the suterusu
rootkit’s capabilities is to log keystrokes from within the kernel to the file /root/.keylog
on disk. To gain write access to the log file, it uses the filp_open
function within the kernel. This function takes a path to a file and returns the file
structure pointer, as shown in the following code:
1 #if defined(_CONFIG_LOGFILE_)
2 logfile = filp_open(LOG_FILE, O_WRONLY|O_APPEND|O_CREAT, S_IRWXU);
3 if ( ! logfile )
4 DEBUG("KEYLOGGER: Failed to open log file: %s", LOG_FILE);
The next example shows the linux_kernel_opened_files
plugin used against a sample infected with suterusu
:
$ python vol.py --profile=LinuxDebian-3_2x64 -f suskl2.lime
linux_kernel_opened_files
Volatility Foundation Volatility Framework 2.4
Offset (V) Partial File Path
------------------ -----------------
0xffff8800307109c0 /media/usb/suskl2.lime
0xffff8800306c58e000 /root/.keylog
In the output, you can see two files reported along with the virtual address of their dentry
structure. The first is suskl2.lime
, which is the memory sample being investigated. It appears in the output because LiME also uses filp_open
from within the kernel to write the memory sample to disk. The other file is /root/.keylog
, which you know is part of the rootkit. Even if you did not know what the file was for and it had a more benign filename, the fact that it is opened from within the kernel is enough to warrant further investigation.
The linux_kernel_opened_files
plugin operates by gathering all recently used dentry
structures from the dentry
cache and compares them with the set of files that processes open (using the linux_lsof
and linux_proc_maps
plugins). As previously mentioned, aside from swap files, the only files that these two plugins should not find are those opened through filp_open
inside the kernel.
Malicious kernel rootkits also have the capability to subvert the system without using or keeping a kernel module in memory. In this case, the rootkit doesn’t necessarily hide its LKM; it just relocates and then unloads immediately. The following steps describe how this technique works:
init
function, the rootkit allocates executable kernel memory by calling functions such as kmalloc
and vmalloc_exec
.init
function returns an error condition so that the module is unloaded and its metadata structure (module
) is destroyed. This means it will not appear in lsmod
or sysfs
.This technique allows the LKM to place code and hooks into the kernel that will persist after the LKM unloads. As long as the allocated memory regions containing the rootkit code aren’t freed, they remain valid and accessible to the operating system, thus allowing the malicious code to survive. This trick is important to understand, because it explains why you might not be able to find a malicious module with the aforementioned plugins. However, if you can find at least one of its hooks, you will then have a function pointer or data structure allocated within an unknown region of memory.
Many Linux rootkits are designed to be as flexible and reusable as possible (so they can be sold as kits and then bundled with any crimeware the buyer desires). Thus, rootkits can often hide their associated processes (based on a static or dynamic configuration) from userland tools and system administrators. Although the attack typically takes place by manipulating process–related kernel data structures, there are many variations. As a forensics investigator, you must recognize and detect the artifacts associated with the different methods.
The first Volatility plugin that can help you analyze systems with hidden processes is linux_psxview
. It enumerates processes from multiple sources throughout the operating system and then cross-references the results to find discrepancies.
The sources that the plugin uses are these:
init_task
symbol points to. This is the list that linux_pslist
walks to enumerate processes.linux_pidhashtable
plugin you learned about in Chapter 21. This plugin not only enumerates processes but also their threads.task_struct
structures in the kernel memory cache (kmem_cache
).parent
pointers of processes and threads found in the PID hash table.By correlating the data from these various sources, you can easily spot processes that are hiding in one or more ways. The following example shows the linux_psxview
plugin against a memory sample infected with malware that is hiding a process from both the process list and the PID hash table:
$ python vol.py --profile=LinuxDebian-3_2x64 -f psrk.lime linux_psxview
Volatility Foundation Volatility Framework 2.4
Offset(V) Name PID pslist pid_hash kmem_cache parents leader
------------------ -------------- ---- ------ -------- ---------- ------- ------
True
0xffff8800371c63f000 apache2 2209 True True True False True
0xffff88003baf29f0 smbd 2166 True True True True True
0xffff880037175710 apache2 2211 True True True False True
0xffff88003e3a2180 crypto 27 True True True False True
0xffff88003e282e60 migration/0 6 True True True False True
0xffff880036f89810 bash 2878 True True True False True
0xffff88003e2a2100 kintegrityd 19 True True True False True
0xffff880036d60830 postgres 2514 False False True False True
0xffff880036cce0c0 scsi_eh_2 155 True True True False True
0xffff88003c29e027b0 kworker/0:1 11 True True True False True
0xffff88003c31a080 dlexec 2938 True True True False True
0xffff88003bdd41c0 getty 2828 True True True False True
0xffff88003b6b6ab0 nmbd 2163 True True True False True
0xffff88003e253510 init 1 True True True True True
0xffff88003c32d1a0 rpciod 1742 True True True False True
0xffff88003e2acf20 khelper 14 True True True False True
0xffff88003bac91f020 exim4 2797 True True True False True
0xffff88003d71c240 winbindd 2509 True True True False True
0xffff880037216a30 apache2 2221 False True True False False
0xffff88003baf37d0 apache2 2255 False True True False False
<snip>
In the output, the postgres
process displays False
in the pslist
, pid_hash
, and parents
columns, but was found within the kmem_cache
and leaders
sources. The absence of the postgres
process from these lists is suspicious, and it should signal you to examine it more closely.
When studying the output of linux_psxview
for hidden processes, there are a number of cases in which legitimate processes appear as False
in certain columns. You should become familiar with these cases in order to draw correct conclusions. These cases are described in the following list:
pid_hash
and kmem_cache
. You can verify that a listed entry is a thread by checking for its thread ID in the output of the linux_threads
plugin.False
in the kmem_cache
column because SLUB does not maintain data structures necessary to track all objects. On SLAB systems, all processes and threads should appear in the kmem_cache
. This rule can be simplified this way: If any process or thread appears in kmem_cache
, they all should. swapper
process, all other entries should be within the process list.Besides hiding a process on the system, kernel-level rootkits often elevate their associated processes privileges to root (UID 0) in order to give the process full control of the system. Attackers often compromise a non-root-user’s account, use privilege escalation to install a kernel rootkit, and then leave the system for some period of time. When the attacker wants to regain access, he can simply log in using previously stolen credentials and communicate with the rootkit to gain full privileges for his new processes. To system administrators monitoring the computer, the login simply looks like the normal user accessing the system. Furthermore, with the heightened privileges, the attacker can perform anti-forensics to remove traces of the login.
On older Linux systems, elevating a process’ privileges was relatively simple. The user and group ID of the process were stored as integers within task_struct
. Setting both members (uid
and gid
) to zero was enough to elevate a process to root. This type of attack is difficult to detect, because although you can see a process running as root, it is challenging to find evidence that the current uid
and gid
values are different from the originals.
Starting with the 2.6 series of kernels, the operating system provides more thorough sources of privilege information. A process’ credentials are now stored within the cred
structures. The kernel provides two APIs (prepare_creds
and commit_creds
) for creating and manipulating these structures, so it’s possible for rootkits to create their own credential structures, initialize them with fake values, and basically give themselves root. However, an even easier way to accomplish the same goal is to point the target processes’ cred
structure to that of an existing process. In particular, the source process is often one that runs as root and does not exit. During our investigations, the process most frequently chosen was init
. This makes sense because init
runs as root and exits only when the machine is powering off or rebooting.
Average Coder (http://average-coder.blogspot.com/2011/12/linux-rootkit.html) is a publicly available rootkit that performs the described method of privilege escalation. Specifically, it creates an artifact that Volatility can leverage to determine which processes are running with unauthorized privilege levels. First, you will see how to interact with Average Coder.
To accept commands from userland, Average Coder hooks the write
handler of the /proc/buddyinfo
file. This file is installed by default in Linux distributions and it exports memory management information. However, typically the file is meant only to be read. As shown in the following output, attempting to write to it produces an error, regardless of whether a normal user or root user initiates the write:
$ echo 42 > /proc/buddyinfo
bash: /proc/buddyinfo: Permission denied
$ sudo bash
# echo 42 > /proc/buddyinfo
-bash: echo: write error: Input/output error
On a system infected with Average Coder, writing to the file still seemingly produces an error, but the rootkit’s handler is actually processing the command. For example, the following command shows how to elevate a process to root access by sending the /proc/buddyinfo
file a root
command:
$ id
uid=1000(x) gid=1000(x) groups=1000(x),24(cdrom),25(floppy),29(audio),
30(dip),44(video),46(plugdev)
$ echo $$
9673
$ echo "root $$" > /proc/buddyinfo
-bash: echo: write error: Operation not permitted
$ id
uid=0(root) gid=0(root) groups=0(root)
The user initially does not have root access (user ID 1000). By sending the root
command with the PID of the bash shell (9673), the shell is then elevated to root privileges. You can use Volatility to detect this modification because the cred
structure pointer of the bash shell is now the same as the init
process. As the following code shows, the linux_check_creds
plugin detects this by walking the list of processes and seeing whether any share a credentials structure. This never happens on a clean system!
$ python vol.py -f avg.hidden-proc.lime --profile=LinuxDebian-3_2x64
linux_check_creds
Volatility Foundation Volatility Framework 2.4
PIDs
--------
1, 9673
In the output, PID 1 and 9673 are reported to have the same credentials structure. Because process 1 (init
) already runs as root and is critical to operating system stability, it is very likely that it is the one whose credentials were stolen and process 9673 is related to the rootkit. As shown in the previous output, the PID of the elevated bash process was 9673.
One of the most popular methods that Linux rootkits use to control a userland application’s view of the system is by hooking system calls. As you learned in Chapter 1, processes use system calls to request actions such as opening, reading, and writing files; creating and destroying processes; network communications; and much more. By hooking system calls, kernel rootkits can return false information to the caller, log parameters passed to the APIs, or modify requests.
The system call table is an array of function pointers, one for each system call. On a clean machine, the entries should point within the kernel’s static code (as opposed to inside an LKM or an unknown region of kernel memory). Volatility provides two plugins to detect hooked system call tables. The first one, linux_check_syscall
, is discussed in this section; the second, linux_check_kernel_inline
, is discussed at the end of this chapter. The first one operates by locating the system call table, determining its size, and then validating each entry.
The following code shows the use of linux_check_syscall
against a memory sample infected with KBeast.
$ python vol.py -f kbeast.lime --profile=LinuxDebianx86
linux_check_syscall
-i /usr/include/x86_64-linux-gnu/asm/unistd_32.h > ksyscall
$ head -10 ksyscall
Table Index System Call Handler Symbol
----- ----- ---------------- ---------- ----------------------------------
32bit 0 restart_syscall 0xc103be51 sys_restart_syscall
32bit 1 exit 0xc1033c85 sys_exit
32bit 2 fork 0xc100333c ptregs_fork
32bit 3 read 0xf841998b HOOKED: ipsecs_kbeast_v1/h4x_read
32bit 4 write 0xf84191a1 HOOKED: ipsecs_kbeast_v1/h4x_write
32bit 5 open 0xf84194fc HOOKED: ipsecs_kbeast_v1/h4x_open
32bit 6 close 0xc10b227a sys_close
32bit 7 waitpid 0xc10334d8 sys_waitpid
$ grep HOOKED ksyscall
32bit 3 read 0xf841998b HOOKED: ipsecs_kbeast_v1/h4x_read
32bit 4 write 0xf84191a1 HOOKED: ipsecs_kbeast_v1/h4x_write
32bit 5 open 0xf84194fc HOOKED: ipsecs_kbeast_v1/h4x_open
32bit 10 unlink 0xf841927f HOOKED: ipsecs_kbeast_v1/h4x_unlink
32bit 37 kill 0xf841900e HOOKED: ipsecs_kbeast_v1/h4x_kill
32bit 38 rename 0xf841940c HOOKED: ipsecs_kbeast_v1/h4x_rename
32bit 40 rmdir 0xf8419300 HOOKED: ipsecs_kbeast_v1/h4x_rmdir
32bit 220 getdents64 0xf84190ef HOOKED: ipsecs_kbeast_v1/h4x_getdents64
32bit 301 unlinkat 0xf8419381 HOOKED: ipsecs_kbeast_v1/h4x_unlinkat
For each system call, the plugin prints the following information:
sys_
. Malicious handlers are printed as HOOKED
, followed by the hooking module name and handling function name (if available).An artifact that stands out in the previous output is that all malicious handlers are stored within the same page of memory: 0xc84f019xxx
. The fact that the handlers are so close to each other is a good indication that they point to the same kernel module or injected code segment. According to the Symbol
column, the name of the malicious module is ipsecs_kbeast_v1
. Furthermore, Volatility determines the name of the functions within the malicious module that handle the hooked system calls (h4x_read
, h4x_write
, and so on). The upcoming “Keyboard Notifiers” section describes how the name resolution works.
In 2012, a research team from Bowdoin College published a paper, Bridging the Semantic Gap to Mitigate Kernel-level Keyloggers (http://www.ieee-security.org/TC/SPW2012/proceedings/4740a097.pdf), which detailed two new methods for performing in–kernel key logging on Linux systems. Shortly after the paper was released, Joe Sylve contributed two new plugins (linux_keyboard_notifiers
and linux_check_tty
) to Volatility that detected these attacks.
The first attack described in the Bowdoin paper showed how to implement a malicious keyboard notifier in order to receive a copy of a user’s keystrokes. The notifier is registered through the register_keyboard_notifier
function, which takes a notifier_block
structure as a parameter. This structure’s notifier_call
member is called each time a key is pressed. To detect this attack, the linux_keyboard_notifier
plugin walks the list of keyboard notifiers and prints each one, along with the handler and whether it is valid. On all default Linux installs that we have tested, no notifiers have been present.
suterusu
is an example of a rootkit that implements key logging by installing a notifier. An example of the code follows:
1 int notify(struct notifier_block *nblock, unsigned long code, void *_param)
2 {
3 struct keyboard_notifier_param *param = _param;
4
5 DEBUG_KEY("KEYLOGGER %i %s
",param->value,(param->down ? "down" : "up"));
6
7 <snip>
8 }
9
10 static struct notifier_block nb = {
11 .notifier_call = notify
12 };
13
14 void keylogger_init ( void )
15 {
16 DEBUG("Installing keyboard sniffer
");
17
18 register_keyboard_notifier(&nb);
19
20 <snip>
Lines 1 through 8 contain the notify
function that receives each keystroke. This function prints the key to the kernel debug buffer and later writes it to /root/.keylog
. This could obviously be modified to capture keystrokes to other locations on disk or send them over the network. The keyboard notifier structure is declared on lines 10 and 11. On lines 14 through 18, the rootkit registers the keyboard notifier.
The following code shows how to use Volatility on a memory image with the suterusu
sample installed:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime
linux_keyboard_notifier
Volatility Foundation Volatility Framework 2.4
Address Symbol
------------------ -------------------
0xffffffffa039f5d7 HOOKED
In the output, you can see that there is a notifier present and the plugin reports HOOKED
because the handler (0xffffffffa039f5d7
) is not within the kernel or an LKM in the linked list. Thus, the handler is in an anonymous region of kernel memory or in an LKM that’s unlinked from the list.
If you want to determine whether the malicious handler is inside a hidden kernel module, you can use linux_hidden_modules
. This plugin, introduced in the “Carving Hidden Modules” section of this chapter, finds LKMs by carving rather than relying on the linked list or the sysfs
method:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime
linux_hidden_modules
Volatility Foundation Volatility Framework 2.4
Offset (V) Name
------------------ ----
0xffffffffa03a15d0 suterusu
In this case, one module is reported as hidden and its base address is 0xffffffffa03a15d0
. You can use linux_lsmod
to determine where its sections are mapped:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime
linux_lsmod -b 0xffffffffa03a15d0 -S
Volatility Foundation Volatility Framework 2.4
ffffffffa03a15d0 suterusu 18928
.note.gnu.build-id 0xffffffffa03a0000
.text 0xffffffffa039e000
.text.unlikely 0xffffffffa039fdcc
.init.text 0xffffffffa0030000
.exit.text 0xffffffffa039fdfc
.rodata 0xffffffffa03a0028
.rodata.str1.1 0xffffffffa03a00c5
.parainstructions 0xffffffffa03a0ab0
.data 0xffffffffa03a1000
.gnu.linkonce.this_module 0xffffffffa03a15d0
.bss 0xffffffffa03a1820
.symtab 0xffffffffa0031000
.strtab 0xffffffffa00322a8
In this invocation, linux_lsmod
is instructed to analyze the sections for the hidden module at 0xffffffffa03a15d0
. The malicious handler is within the .text
section of the module, which is determined because the handler is at 0xffffffffa039f5d7
, and the .text
section starts at 0xffffffffa039e000
. No other section is mapped between the .text
section and this address. This provides additional evidence that the notifier handler points within this hidden module.
At this point, you can use linux_moddump
to extract the module to disk, as was done earlier in the chapter. To find the function name, you subtract the address of the handler from the address of where the .text
section starts:
$ python
Python 2.7.3 (default, Jan 2 2013, 13:56:14)
[GCC 4.7.2] on linux2
>>> "%x" % (0xffffffffa039f5d7-0xffffffffa039e000)
'15d7'
Next, you could use readelf
to search the symbol table of the extracted module for the function at the calculated offset:
$ readelf -s -W suterusu.0xffffffffa039e000.lkm | grep 15d7
28: 00000000000015d7 129 FUNC GLOBAL DEFAULT 2 notify
You now know that the notify
function of suterusu
is used as the malicious handler. You also saw this in the source code, but in most cases you do not have the source code of a rootkit you are analyzing. To automate the identification tasks, we extended the linux_keyboard_notifier
plugin to automatically incorporate the linux_hidden_modules
plugin. We then used Volatility’s symbol_for_address
API to find the corresponding symbol name. The output of the patched plugin looks as follows:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime
linux_keyboard_notifier
Volatility Foundation Volatility Framework 2.4
Address Symbol
------------------ ------------------------------
0xffffffffa039f5d7 HOOKED: suterusu/notify
The plugin now adds <module name>/<symbol name>
for the malicious hook after the HOOKED
string.
The second key-logging technique disclosed in the paper from Bowdoin College was the use of a malicious TTY input handler. These handlers read input (keystrokes) on TTY devices such as terminals. Instead of relying on a callback mechanism, this technique overwrites a function pointer with a malware-controlled pointer so it can gain access to each keystroke. As with many other rootkit techniques, this activity is immediately detectable with memory forensics.
To hook a TTY handler, you can perform the following steps:
file
pointer structure of a TTY device. You accomplish this by passing the name of the device (for example, /dev/tty1
) to the filp_open
function.private_data
member to get a reference to the tty_file_private
structure, which in turn points to a tty_struct
. tty_struct->ldisc->ops->receive_buf
pointer with the address of your key logging function. After these steps are complete, your function can record keystrokes as they are passed through the device. To detect this activity, the linux_check_tty
carries out the following steps:
tty_drivers
global variable.tty_driver
structure, it accesses the ttys
member, which holds the list of TTY devices managed by the driver.receive_buf
pointer is validated. On a clean system, it should point to the n_tty_receive_buf
function.Here is an example of running the Volatility plugin against a clean system, whose TTY devices are not hooked:
$ python vol.py -f centos.lime --profile=LinuxCentosx64 linux_check_tty
Volatility Foundation Volatility Framework 2.4
Name Address Symbol
---------------- ------------------ ------------------------------
tty1 0xfffffffc81f031a0b0 n_tty_receive_buf
tty2 0xfffffffc81f031a0b0 n_tty_receive_buf
tty3 0xfffffffc81f031a0b0 n_tty_receive_buf
tty4 0xfffffffc81f031a0b0 n_tty_receive_buf
tty5 0xfffffffc81f031a0b0 n_tty_receive_buf
tty6 0xfffffffc81f031a0b0 n_tty_receive_buf
On an infected system, the function points elsewhere. In the case of the following malware sample, it points to a function within a hidden module:
$ python vol.py -f tty-hook.lime --profile=LinuxDebian-3_2x64 linux_check_tty
Volatility Foundation Volatility Framework 2.4
Name Address Symbol
---------------- ------------------ ------------------------------
tty1 0xffffffffa0427016 HOOKED
tty2 0xffffffffa0427016 HOOKED
tty3 0xffffffffa0427016 HOOKED
tty4 0xffffffffa0427016 HOOKED
tty5 0xffffffffa0427016 HOOKED
tty6 0xffffffffa0427016 HOOKED
One thing to notice with this malware and the way the technique is described in the paper: All devices have their receive_buf
members overwritten to the same function. This occurs because all devices share a default operations structure. Thus, overwriting the pointer of one will overwrite all others. Alternatively, it is possible to make a copy of this structure, alter the receive_buf
member, and then point only the desired device at the altered copy. For this reason, it is required to check all devices in order to ensure that no tampering has occurred.
Kernel rootkits have a number of ways to hide connection information from userland applications, but one of the most popular is hooking the kernel’s network protocol structures. These structures include the sequence operations handlers that are used when exporting network socket and connection data through the /proc
file system, which is then read by tools such as netstat
and lsof
. In other words, for the various parts of the kernel to have a uniform API for exporting information through /proc
, Linux provides sequence operations functions. These functions support all the operations that userland applications can perform against the files, such as open
, read
, seek
, and close
. To filter or hide data, all a rootkit needs to do is overwrite the function pointers associated with handling these operations.
On a live system, the files under the /proc/net/
directory export all network-related information from the kernel to userland. For example, /proc/net/dev
shows information for each network interface:
$ cat /proc/net/dev
Inter-| Receive | Transmit
face |bytes packets errs drop fifo compressed multicast|bytes packets
lo: 3396353 10929 0 0 0 0 0 3396353 10929
eth0: 7412216 69623 0 0 0 0 0 17223107 57677
Likewise, /proc/net/arp
shows the current ARP cache:
$ cat /proc/net/arp
IP address HW type Flags HW address Mask Device
192.168.174.2 0x1 0x2 00:50:56:fa:ad:55 * eth0
192.168.174.1 0x1 0x2 00:50:56:c0:00:08 * eth0
192.168.174.254 0x1 0x2 00:50:56:e7:00:e1 * eth0
And /proc/net/tcp
shows information on each active network socket:
$ cat /proc/net/tcp
sl local_address rem_address st uid inode
0: 00000000:C60E 00000000:0000 0A 102 4965
1: 00000000:006F 00000000:0000 0A 0 6108
2: 00000000:0016 00000000:0000 0A 0 6404
3: 0100007F:1538 00000000:0000 00 104 7585
4: A9AEA8C0:0016 01AEA8C0:D878 01 0 16238
5: A9AEA8C0:0016 01AEA8C0:D8B9 01 0 16287
As you can see in the previous output, quite a bit of useful network information is exported under /proc.
It is important to note that these examples just show a subset of the information that is available.
The linux_check_afinfo
plugin validates each handler within the file operations and sequence operations structures of each network protocol. These protocols currently include the following:
tcp4_seq_afinfo
tcp6_seq_afinfo
udp4_seq_afinfo
udp6_seq_afinfo
udplite4_seq_afinfo
udplite6_seq_afinfo
For each protocol, the handlers are checked to be inside the kernel or within a known module. The plugin reports any handler that does not meet these criteria. Unlike other plugins discussed in the chapter, this one reports only hooked entries. The following shows this output against the KBeast rootkit:
$ python vol.py -f kbeast.lime --profile=LinuxDebianx86 linux_check_afinfo
Volatility Foundation Volatility Framework 2.4
Symbol Name Member Address
----------- ------ ----------
tcp4_seq_afinfo show 0xe0fb9965
This rootkit hooks the show
member of tcp4_seq_afino
to hide a connection on a specified network port from /proc/net/tcp
, userland tools, and system administrators. A rootkit that hooks the show
member can either filter the information or completely skip records. The handler function from this specific rootkit is implemented as follows:
1 int h4x_tcp4_seq_show(struct seq_file *seq, void *v)
2 {
3 int r=old_tcp4_seq_show(seq, v);
4 char port[12];
5
6 sprintf(port,"%04X",_HIDE_PORT_);
7 if(strnstr(seq->buf+seq->count-TMPSZ,port,TMPSZ))
8 seq->count -= TMPSZ;
9 return r;
10 }
On line 6, the _HIDE_PORT_
variable, which stores the port to hide, is converted to a hexadecimal value. It is then compared with the hex value inside the TCP buffer. If the port matches, the entire record related to the connection is removed by subtracting the size of the record from the returned count. This process effectively hides it from userland applications.
Netfilter is the packet-filtering engine built into the Linux kernel. The iptables
tool leverages Netfilter to implement Network Address Translation (NAT) and firewall filtering. Besides Netfilter’s userland interfaces, it also enables kernel modules to implement their own network stack hooks. While these hooks can serve a legitimate purpose, such as adding new firewall capabilities, they can also be used maliciously to intercept, modify, and inspect packets entering and leaving the computer. In this section, you learn how to enumerate and analyze Netfilter hooks.
Netfilter enables kernel modules to hook into a number of stages within the network stack, which allows for inspection at different times during the processing and routing of packets. The following list enumerates the possibilities:
PRE_ROUTING
: Activates before the packet enters the routing subsystem of the kernel.LOCAL_IN
: Activates for packets that are sent to the computer itself, and it is the last hook before a process receives the packet’s data.FORWARD
: Activates before a packet is forwarded to another machine.LOCAL_OUT
: Activates for packets generated on the local machine.POST_ROUTING
: Activates right before a packet is sent out on the network.When a hook is activated, it must decide what the kernel should do with the examined packet. The following list shows the available options.
NF_DROP
: Drop the packet and perform no further processing.NF_ACCEPT
: Allow the packet to continue through the stack.NF_STOLEN
: Gives control of the packet solely to the handler. Its resources are not freed, but it is also not sent further through the network stack.NF_QUEUE
: Queues packets for userland processing.NF_REPEAT
: Tells the Netfilter manager to call the hook function again.NF_STOP
: Processes the packet without calling any other Netfilter hooks on it.As you can imagine, the type of hooks and processing options provide many opportunities for malware to hide packets, set up command and control channels, and monitor the contents of network activity on the system. The following shows the Netfilter hook that suterusu
uses to watch for a magic packet:
1 void icmp_init ( void )
2 {
3 DEBUG("Monitoring ICMP packets via netfilter
");
4
5 pre_hook.hook = watch_icmp;
6 pre_hook.pf = PF_INET;
7 pre_hook.priority = NF_IP_PRI_FIRST;
8 pre_hook.hooknum = NF_INET_PRE_ROUTING;
9
10 nf_register_hook(&pre_hook);
11 }
In this function, the rootkit sets up an nf_hook_ops
structure with a handler that executes the watch_icmp
function (not shown). The protocol is PF_INET
, the priority is NF_IP_PRI_FIRST
, and the network stack position is NF_INET_PRE_ROUTING
. This means that when packets come into the system, the handler is the first hook called and it has complete control of the packet.
If you were to analyze the watch_icmp
function, you would see that it searches for an ICMP packet with a magic value, followed by an IP address and port from which to download a malicious executable. When the packet is not ICMP or does not match the magic value, the NF_ACCEPT
return value is used. This allows the packet to continue as normal. If a magic packet is found, after downloading the instructed file, the NF_STOLEN
return value is used so that other handlers will not process the packet. This means that packet sniffers, such as Wireshark and Tcpdump, running on the local system will not see the packets.
The linux_netfilter
plugin can enumerate Netfilter hooks. Additionally, the plugin reports the position of the hook in the stack, which protocol they use, and the addresses of the hook handlers. The plugin works by enumerating all entries within the nf_hooks
global variable. nf_hooks
is declared as follows:
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]
It holds a list of hooks for each protocol and position within the network stack. Each list element has type nf_hook_ops
, and these elements contain all the information that the plugin requires. The following shows linux_netfilter
against the suterusu
sample with the ICMP back door monitor activated:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime linux_netfilter
Volatility Foundation Volatility Framework 2.4
Proto Hook Handler Is Hooked
----- ---------------- ------------------ ---------
IPV4 PRE_ROUTING 0xffffffffa039fcd4 True
In this output, you can see that the PRE_ROUTING
hook of suterusu
(the watch_icmp
function) was found. You can then analyze the code at 0xffffffffa039fcd4
to determine its functionality.
All file-related operations, such as reading, writing, and listing directories, eventually go through the file_operations
structure of a previously opened file. You have already seen several places in the book where we briefly describe how Linux rootkits interact with these structures to hide a file, prevent a file from being removed or overwritten, and more. In this section, you learn the details of how these rootkits operate and how Volatility detects their activity.
The linux_check_fop
plugin gathers file_operations
structures from a number of sources:
linux_lsof
plugin is used to gather the file structure for all opened files./proc
file system is walked, and all file structures are gathered.linux_find_file
plugin is leveraged to find all files within the page cache.For each structure found within these sources, the plugin checks all the function pointer members to ensure they are within the static kernel or within a known kernel module. Now that you know how the file operation structures are identified, the following section discusses some specific types of attacks you can detect.
One of the most infamous examples of malicious code that hooks file operations is adore-ng
(see http://lwn.net/Articles/75990/). This rootkit hooked the readdir
function of the /proc
and /
(root) file systems to hide files from directory listings. Since adore-ng
was released, many rootkits, both public and private, have utilized the same file hiding technique.
An interesting example from the wild was a rootkit that hooked the read
call of several files that contained stolen data (for example, the output of a key logger). The hook hid information from live forensic tools, but the attackers could still read the actual file contents by sending special parameters to the read
operations function. We have also encountered malware samples that hook the write
function to prevent protected files (such as configurations and executable components) from being overwritten.
Another example is the public Average Coder rootkit discussed previously in this chapter. It installs a number of hooks that you can detect with the linux_check_fop
plugin:
$ python vol.py -f avgcoder.mem --profile=LinuxCentOS63x64 linux_check_fop
Symbol Name Member Address
-------------------------- --------------- ------------------
proc_mnt: root readdir 0xffffffffa05ce0e0
buddyinfo write 0xffffffffa05cf0f0
modules read 0xffffffffa05ce8a0
/ readdir 0xffffffffa05ce0e0
/var/run/utmp read 0xffffffffa05ce4d0
The first entry hooks the readdir
function of the /proc
file system. This hook allows Average Coder to hide processes from the running system. On Linux, each process is given a directory immediately under /proc
, named according to the PID of the process. These process-specific directories subsequently contain information about the state of the process. By hiding a process’ /proc
directory from the live system, utilities such as ps
and top
cannot find and report them. The next entry hooks /proc/buddyinfo
, as we explained previously.
By hooking the read
method of /proc/modules
, Average Coder can hide kernel modules from the system. The lsmod
command is the main tool system administrators use to look for loaded modules on a Linux system, and this command gathers its data from /proc/modules
. By filtering the results of the read
function, loaded modules can be easily hidden on the live system. By hooking readdir
of the root directory, Average Coder can hide files just as adore-ng
did. The purpose of hooking utmp
is explained next.
The last hook listed in the previous output is the read
function of /var/run/utmp
. The reason that Average Coder hooks this function is to hide logged-in users from system administrators who use the w
or who
command. These commands read the binary structure of /var/run/utmp
and then format and print the information to the console. By hooking the read
function of utmp
, Average Coder can filter out entries associated with the attacker.
As you have seen many times, although this rootkit technique will fool an investigator on a live machine, the activity can be detected with memory forensics. In the case of the Average Coder sample, you can extract the utmp
file from memory and determine the users logged in at the time of the memory dump. To start this process, use the linux_find_file
plugin to find the inode
structure address of the file:
$ python vol.py -f avgcoder.mem --profile=LinuxCentOS63x64
linux_find_file
-F "/var/run/utmp"
Volatility Foundation Volatility Framework 2.4
Inode Number Inode
---------------- ------------------
130564 0x88007a85acc0
Next, this address is passed back to linux_find_file
to extract it:
$ python vol.py -f avgcoder.mem --profile=LinuxCentOS63x64
linux_find_file
-i 0x88007a85acc0
-O utmp
Then you can use the who
command on the forensics analysis machine to examine the extracted utmp
file:
$ who utmp
centoslive tty1 2013-08-09 16:26 (:0)
centoslive pts/0 2013-08-09 16:28 (:0.0)
In this particular sample, the centoslive
user was being hidden from the live system, but it appears after the utmp
file is extracted from memory and analyzed without malware interference.
Inline hooking is a rootkit technique that overwrites instructions within a function to change the function’s runtime behavior. Normally, the hooks redirect control flow to a function that is part of the rootkit, which allows the rootkit to add, modify, or delete data being processed and returned by the original function. These hooks were introduced in Chapter 25, “Userland Rootkits,” however they also apply to code in kernel memory.
The linux_check_inline_kernel
plugin builds on the work of many other plugins described in this chapter. In particular, it leverages the other plugins to locate functions and function pointers that may be targeted by malware. It then checks to see whether any of the functions are victims of inline hooking. It also checks various other functions (shown in the following list) that are not analyzed by other plugins:
dev_get_flags
: Used to hide the promiscuous mode setting of network interfacesia32_sysenter_target
and ia32_syscall
: The handler functions of system call interruptsvfs_readdir
and tcp_sendmsg
: These were hooked by an IFRAME injecting rootkit in late 2012 (see: http://lwn.net/Articles/525977/).The types of inline hooks currently detected are:
JMP
: Detects hooks that transfer control outside the local function by using the JMP
instruction. This normally occurs by first moving an address into a register or directly jumping to the address.CALL
: Detects functions that use the CALL
instruction to transfer control. RET
: Detects functions that use a RET
instruction to transfer control. This is normally done by using PUSH
to copy the destination address onto the stack and then executing a RET
.Although this may not be an exhaustive list, these detections effectively find a majority of hooks that you will encounter in the wild. The following shows how to use the linux_check_inline_kernel
plugin against a sample infected with suterusu
. This rootkit extensively uses inline hooks to control a wide range of system behavior:
$ python vol.py --profile=LinuxDebian-3_2x64 -f susnf.lime
linux_check_inline_kernel
proc_root readdir JMP 0xffffffffa039e06f
/proc readdir JMP 0xffffffffa039e06f
/ readdir JMP 0xffffffffa039e06f
/ readdir JMP 0xffffffffa039e0be
/home readdir JMP 0xffffffffa039e0be
/home/x readdir JMP 0xffffffffa039e0be
/root readdir JMP 0xffffffffa039e0be
<snip>
/var readdir JMP 0xffffffffa039e0be
/var/mail readdir JMP 0xffffffffa039e0be
/var/www readdir JMP 0xffffffffa039e0be
/var/cache readdir JMP 0xffffffffa039e0be
/var/cache/samba readdir JMP 0xffffffffa039e0be
/var/cache/samba/printing readdir JMP 0xffffffffa039e0be
/var/spool readdir JMP 0xffffffffa039e0be
/var/spool/exim4 readdir JMP 0xffffffffa039e0be
/var/spool/exim4/db readdir JMP 0xffffffffa039e0be
/var/spool/exim4/msglog readdir JMP 0xffffffffa039e0be
/var/spool/exim4/input readdir JMP 0xffffffffa039e0be
/var/spool/cron readdir JMP 0xffffffffa039e0be
/var/spool/cron/crontabs readdir JMP 0xffffffffa039e0be
/var/spool/cron/atjobs readdir JMP 0xffffffffa039e0be
/var/spool/rsyslog readdir JMP 0xffffffffa039e0be
/var/lib readdir JMP 0xffffffffa039e0be
<snip>
tcp6_seq_afinfo show JMP 0xffffffffa039e2a1
tcp4_seq_afinfo show JMP 0xffffffffa039e36b
udplite6_seq_afinfo show JMP 0xffffffffa039e10d
udp6_seq_afinfo show JMP 0xffffffffa039e10d
udplite4_seq_afinfo show JMP 0xffffffffa039e1d7
udp4_seq_afinfo show JMP 0xffffffffa039e1d7
TCP ioctl JMP 0xffffffffa039ea84
UDP ioctl JMP 0xffffffffa039ea84
UDP-Lite ioctl JMP 0xffffffffa039ea84
PING ioctl JMP 0xffffffffa039ea84
RAW ioctl JMP 0xffffffffa039ea84
dev_get_flags JMP 0xffffffffa039e02a
The output shows several detected hooks. The first two entries indicate that the readdir
function of the /proc
root directory has been hooked. This means that the handlers in the linux_check_fop
structure would still point to the original location, but the instructions at the location have been overwritten. There are also hundreds of files whose readdir
member is hooked and whose handler address is the same. This occurs because the rootkit hooks the readdir
function of the entire root file system, which subsequently hooks all files and directories inside it.
After the file system entries, you then see the network sequence operation structures that linux_check_afinfo
checks for function pointer replacement. In the case of suterusu
, instead of simply replacing the function pointers for the show
function, it overwrites the first several bytes of the function with a JMP
instruction.
The next set of hooks contains the ioctl
handlers for the IP protocol interfaces associated with sockets. This is the backdoor channel that suterusu
creates for userland binaries to request actions such as hiding processes, ports, and files. The last line tells you that dev_get_flags
is overwritten. Because dev_get_flags
tells you whether a device is in promiscuous mode, it’s often targeted by malware. By filtering the function’s output, malware can hide the fact that it is sniffing network traffic.
Memory forensics provides you with the ability to detect rootkits that typical tools and methodologies miss. In particular, it allows you to identify rootkits that log key strokes, hide files, conceal logged-in users, elevate process privileges, and so on. Recently added capabilities to the Volatility Framework also inspect Netfilter hooks to determine whether any malicious code is sniffing network packets or engaging in covert communications. Although there are endless ways to hook functions within the kernel, the currently implemented detection capabilities cover a majority of what we’ve seen in the wild. As adversaries and offensive researchers continue to develop new rootkit techniques, remember that Volatility is open-source and expandable. If attackers can install it—you can detect it!
3.17.150.89