In a previous article we introduced the concept of rootkits and their simpler version: rootkits that operate in user-mode. Following, we are going to see how rootkits operate in kernel mode and how they are capable of integrating and interacting with the execution of the kernel itself.
Linux kernel and dynamic modules
Linux kernel is monolithic, in other words, it is responsible for the entire management of resources, organization/planning of processes and access to devices. The "modules" function, known as Loadable Kernel Modules (LKM), a mechanism that allows the kernel to load/download additional code to the memory, appears from the 2.2 version. The kernel becomes much more flexible thanks to the modules as it is able to load the code in runtime in order to perform new tasks without having to recompile the kernel. This function is very useful for example to load drivers on devices, and it also opens an important path for the development of malicious software such as rootkits.
Kernel-mode rootkit operation
It’s normal for a kernel-mode rootkit to use the dynamic modules function. There are other operation modes that directly manipulate the nucleus kernel that is running instead of adding modules. This technique has been historically exploited by directly accessing the kernel’s memory image through the /dev/kmem file. However, the evolution of Linux kernel has brought with it greater security measures and this interface has been deactivated since the 2.6 version. These and other more complex reasons have consolidated the use of LKM as the most frequently used technique by kernel-mode rootkits.
In an operating system, the user mode and kernel mode interact and communicate with each other through an intermediate mechanism. In the case of Linux, the interface that allows the exchange between user space and Kernel space is Glibc, the system’s main library that is responsible for mediating in system calls. Therefore, any task in the user space that requires access to privileged resources will use the communication interface provided by Glibc.
- Syscalls and Glibc. Communication interface between user space and kernel space -
When a system call takes place (e.g. open() to access a file), the following flow of events occurs:
1. The execution of user mode is interrupted (a 0x80 instruction in Linux or sysenter in modern processers).
2. The interrupt controller consults the event that has occurred in the interrupt table and determines that it is a system call or syscall. The execution enters kernel space via the system call. The syscall consults the call table (sys_call_table) and checks the memory address of the requested call and jumps to that address. The interface between the user space and kernel space that manages the Linux system call is gclibc.
3. The requested function is executed and the control of the programme is returned to the user.
- System calls: Interrupt (sysenter, 0x80) and user <-> kernel flow of execution -
Syscalls interceptions with dynamic modules
While the objective in a user-space rootkit is to intercept calls made by binaries to libraries, the objective of a kernel-mode rootkit is directly a system call, in other words, a kernel module function call.
To intercept the execution of a system call, a rootkit can:
a. Modify the original table call so that every call points towards other locations where the modified functions have been disposed (syscall hooking).
b. Or, manipulate the interrupt descriptor table (IDT), specifically the one corresponding to the syscall (0x80) to lead to a modified syscalls table (Interrupt hooking).
The rootkit must take control of the flow of execution in all those system calls that it is interested in intercepting. The most frequent option is syscall hooking and modifying the entries in the original system calls table (sys_call_table) switching the corresponding original function call entry for another address where the malicious code has been disposed.
Kernel modules and syscall hooking
Kernel modules constitute an important function that can be used to perform system call hooking and modify its behaviour. To do so, the module must perform at least the following tasks:
1. Locate the system call table address (sys_call_table).
2. Enable the writing of the memory zone where the call table is located, since it is set to read-only mode.
3. Modify the corresponding entries in the call table, which are selected to achieve the sought-after objective (conceal processes, files, etc) so that they point out the malicious functions.
4. Define the tasks that the functions have to perform to achieve the objective.
5. In addition, it must conceal its own presence so that it isn’t visible in /proc/modules, /sys/module or on the list provided by Ismod.
Linux kernel modules
According to GNU/Linux’s documentation, a kernel module must include the following minimum structure: necessary libraries, module information and initialization and destruction functions with the desired operations.
-Basic structure of a Kernel module -
Once the module is compiled it can be loaded in the memory and integrated with the kernel through the insmod command. Other interesting commands for the management of modules are Ismod to list the memory modules and rmmod to download modules.
Further information regarding module programming can be found in the following link: http://www.tldp.org/LDP/lkmpg/2.6/html/index.html
Kernel mode syscall hooking: An example.
Using kernel mode to intercept and manipulate system call becomes one of the most used resources by rootkits, given its relative simplicity and power. That is why it is important to know the usual operation modes of modules. We will prove how a syscall is intercepted with an example. A module has been created in this example to intercept a getcwd system call performed by /bin/pwd. This binary returns the route where we are:
- Strace. System calls performed by /bin/pwd. Observe the getcwd syscall -
Effectively, amongst the calls made by the /bin/pwd binary we can see that getcwd is targeted to be substituted. The exemplary module intercepts the calls made to getcwd to manipulate /bin/pwd’s result so that, instead of displaying the actual route, it displays the chain "/home/pepito". The steps taken by the module are:
1. Locate the address of the sys_call_table.
2. Given that the memory zone where the sys_call_table resides is protected (read only) it has to put the memory page in writing-mode, modifying a bit of the processor’s 0 register.
3. Once the protection against the writing is eliminated, it modifies the corresponding entry to getcwd (sys_call_table[__NR_getcwd]).
4. It defines an alternative function that will be called instead of the original (getcwd). This function will modify the chain that contains the actual route for "/home/pepito"
We can check the result in the following illustration:
- Interception of a getcwd syscall that is manipulated so that /bin/pwd always returns"/home/pepito" -
Effectively it can be verified in the system call how the interception has modified the values in getcwd:
- Interception by the getcwd function module -
The programming of kernel modules is without a doubt a very efficient mechanism for the development of rootkits. However, it must be accompanied by the necessary measures that assure that its detection and elimination are as difficult as possible. A carefully designed rootkit in kernel space can be practically undetectable even for users or advanced tools and given its power it must be considered as a very serious threat.