The Spy Within

By

Jürgen Quade

Rootkits allow attackers to take complete control of a computer. We describe the tricks intruders use to gain access to the Linux kernel and provide guidelines on hardening the kernel against such attacks.

In response to a question asked in parliament, the German Federal Government responded in May 2012 that it was able to decrypt PGP messages or SSH connections, at least partially. Experts were exasperated when they heard this. No one seriously believed that PGP encryption had been cracked. More likely, data is sniffed before encryption or after decryption, or the secret service possesses the private key and, possibly, the associated password. Germany’s “Federal Trojan” can infiltrate the kernel as a rootkit for this purpose.

Classically, the term “rootkit” refers to a piece of software that gives an attacker camouflaged access to, and thus control over, a machine. Userland rootkits tend to modify applications to do this. In comparison, the much more powerful kernel rootkits change kernel data structures and code – for example, through system call hijacking.

Hijacked

Hijacking a system call is simple in theory. A system call, which is a job for the operating system kernel, is identified by a number that is used as an index in a table with function addresses. These addresses point to functions that implement the respective system calls. In the Linux kernel, this table is sys_call_table.

To hijack the system call, the attacker replaces the respective function address with the address for their typically malicious function (Figure 1).

In the scope of this function, the attacker can call parameters, change the code of the original system call, and copy or manipulate the results. To grab all keystrokes, including any passwords entered by the user, the attacker replaces the sys_read() system call with their malicious variant, which I’ve called evil_sys_read(). This function first calls the original sys_read() system call, which reads the requested data via the hardware and copies the data to the application’s memory space (userland). Before evil_sys_read() returns control to the application, it accesses the data in userland and does something evil, like redirecting it to the attacker’s server on the Internet.

Linux is Becoming More Secure

Kernel 2.4 made this kind of hijacking attack comparatively simple. With the help of a Loadable Kernel Module (LKM), the attacker could read the initial address from the sys_call_table and replace its entries. Kernel versions 2.6 and 3.0 made this attack more difficult. The sys_call_table address is no longer defined globally, so it is thus unknown to a kernel module. Additionally, the sys_call_table resides in a segment that cannot be changed.

Working around the write protection is relatively easy because the attacker already has root privileges and runs code in the kernel.

Listing 1 shows a sequence for an x86 architecture that deletes the 16th bit in the control register, CR0. This bit generally enables or disables write protection. The bad news: You have no way of preventing this attack.

Address List

To determine the address of the sys_call_table, however, an intruder has to demonstrate a little more skill. With a bit of luck, they might find (e.g., on Ubuntu) a list of all kernel addresses on the computer. The distribution creates this list while building the kernel and stores it in /boot/System.map-<kernel version>.

Even if this list is not there, the attacker doesn’t need to give up just yet. A trick for the x86 platform lets intruders discover the sys_call_table address in the Linux kernel itself. To do so, you only have follow the code path that Linux processes when a syscall occurs; it is well known that a software interrupt is used to call the system call.

The address of the called function system_call() is located in the Interrupt Descriptor Table (IDT). The address of the IDT, in turn, is located in the IDT register of the CPU and thus is very easy to access. The system_call() function accesses the sys_call_table using the call machine command. This assembler command results in an easy-to-find fingerprint of three bytes after the requested four-byte address of the sys_call_table.

Figure 2 summarizes the approach: The attacker reads the IDTR (1) and obtains the address of the IDT. The entry for index 0x80 (2) within the table points to the system_call() function. Beginning at the start address for the function (3), the attacker just needs to search a few bytes in memory after the fingerprint (4) with the call to the selected system call to discover the address of the sys_call_table (5).

Figure 2: Attackers can walk through data structures to discover the sys_call_table.

In the Data Segment

Other ways to access the address of the sys_call_table are demonstrated by various kernel rootkits, which are easily found on the Internet. For example, the Intoxonia rootkit searches the data segment for the address of the sys_close system call. This address is exported by the kernel and is thus known to a loadable module. Anyone who finds the address in the data segment can compute the start of the sys_call_table.

On x86, rootkits could also infest the system via the debug registers. The CPU provides a total of four debug registers that can each store an address. Once a code sequence accesses one of the stored addresses, an interrupt is triggered, and the Linux kernel jumps via the IDT to the do_debug() function.

If an attacker stores the address of the system_call function in a debug register, while replacing the address of do_debug() with the address of the rootkit (e.g., evil_do_debug()), each system call will enable the rootkit. Of course, the attack vector via debug registers only works on x86; however, these attacks are available on other Linux platforms in modified form – for example, on Android.

Security researchers have published other methods, in combination with proof of concept rootkits, that also support rootkit injection. The idea behind these rootkits has less to do with facilitating the work of secret service agents and more to do with developing preventive defensive measures.

The Good News

Two approaches help defend a system against rootkits. The first approach prevents the installation of a kernel rootkit, and the second takes actions that prevent, or at least detect, kernel injection, such as system call hijacking (Figure 3).

To prevent a kernel rootkit being installed, admins need to configure and compile their own kernel. Prominent barn doors for rootkits include /dev/kmem and /dev/mem, whose drivers need to be removed from the kernel configuration. Baddies also like to use loadable kernel modules (LKM); admins are advised to disable their support, which makes it necessary to build the required drivers into the kernel.

Additionally, you will want to disable the ksplice and kexec mechanisms. ksplice allows a kernel update without a reboot, and kexec enables a reboot that works around the BIOS. As long as the attacker cannot install their own kernel, this keeps the attacker out (for want of a fatal kernel bug), even if they have root privileges.

For the second approach, researchers have developed a number of tools that prevent a rootkit’s central activities and work like an Intrusion Detection System (IDS) by saving hash sums, directly after the install, of essential system tables, including the IDT or the sys_call_table. A tool verifies these checksums at regular intervals, and if it detects any changes to the initially registered comparison values, you can assume a rootkit is at work.

Another method monitors write access to the sys_call_table using, for example, the above-mentioned debug registers. As soon as a write attempt occurs, the alarm bells go off. In many cases, researchers also deploy hypervisor (i.e., virtualization) technologies to locate malicious software regardless of the operating system.

All of these tools have a minor disadvantage: They were created in an academic environment and published, then they disappeared into a drawer – no source code, no maintenance, no package. Currently, it seems that only the samhain IDS is ready for use to detect and prevent kernel rootkits, although you have a bit more choice in userland rootkits (see the “Userland Rootkits” box).

The Author

Jürgen Quade is a professor at the Lower Rhine University (Hochschule Niederrhein) and has been an avid supporter of Open Source since the beginnings of Linux. He co-authored the book Linux-Treiber entwickeln [Developing Linux Drivers, in German].

Related content

Rootkits allow attackers to take complete control of a computer. We describe the tricks intruders use to gain access to the Linux kernel and provide guidelines on hardening the kernel against such attacks.