Loadable Kernel Module Exploits

Beat potential invaders at their own game by learning how to use cracker tools and improve your own security.

Many useful computer security tool ideas have a common genesis: the cracker world. Tools, like port scanners and password crackers, originally designed to aid black-hats in their attempts to compromise systems, have been profitably applied by systems administrators to audit the security of their own servers and user accounts. This article presents a cracker idea—the kernel module exploit—and shows how you can improve your system's security by using some of the same ideas and techniques. First, I will discuss the origin of my idea and how it works, then I will attempt to demystify the art of kernel module programming with a few short examples. Finally, we will walk through a substantial, useful example that will help prevent a class of attacks from compromising your system.

Before we get started, I need to mention the standard disclaimer. Be aware that a bug in kernel space is liable to crash your machine, and an endless loop in kernel space will hang your machine. Do not develop and test new modules on a production machine, and test modules thoroughly to ensure they do not destabilize your system or corrupt your data. To minimize data loss due to system crashes in the debugging cycle, I recommend that you either use a virtual machine or emulator (like bochs, plex86, the User-Mode Linux port or VMware) for testing, or install a journaling filesystem (like SGI's xfs) on your development workstation. Furthermore, none of the code examples in this article have been tested on an SMP machine, and most of it is likely not multiprocessor safe. Now that we have that out of the way, let's talk about modules.

A few months ago, I was developing a system called audit trail generator for Linux. For every process on a system, I wanted to keep track of all system calls and their arguments. To this end, I experimented with several approaches, but none was as successful as I would have liked. Wrapping the libc function for write(), for example, only enabled me to log write() invocations that originated from C programs, and dynamic binary instrumentation was limited by the sorts of executables the instrumentation library could parse (C, C++ and Fortran). Being limited to auditing executables produced by one of a few languages was only a small practical limitation, since virtually every program on a GNU/Linux system is written in C, C++ or some language that has a C- or C++-based runtime library, like Perl or Python. However, the incompleteness of these solutions really bothered me on a theoretical level. I knew how straightforward it would be to bypass this system by invoking a system call from a little-known language that didn't rely on C or C++, or even by handcrafting a system call in assembly language. It was clear that it would be impossible to write an insubversible user-space auditing tool, and it would be tough to write a really useful tool without hacking into the kernel. Since I didn't want to maintain a patch or deal with a lengthy recompile-reboot-debug cycle, I didn't think doing this in kernel space was feasible.

No sooner had I put these concerns on the back burner and started work on this project than I saw a message to my local LUG's mailing list that gave me an idea. This message was a forwarded advisory about a kernel module exploit. This particular module was a nasty one: it modified the behavior of certain system calls to hide itself from the lsmod command and to hide the presence of scanners, crackers, sniffer logs and other such files. I almost screamed “Eureka!” in my office. I didn't have to deal with maintaining a kernel patch, recompiling or rebooting; I could develop my tool as a loadable module. I recognized that the general technique behind module exploits could be adapted to add many types of useful behavior to system calls, including a different security policy, finer-grained security than the UNIX model allows and, of course, my audit trail generator.

Hello, Kernel!

I will discuss some of the fun things you can do by altering and wrapping system calls a little later, but let us first get our hands dirty with an example kernel module. This is a simple example, akin to everyone's favorite first program, but it demonstrates the most basic parts of a loadable kernel module, the init_module and cleanup_module functions:

#include <linux/kernel.h>
#include <linux/module.h>
int init_module() {
   printk("<1> Hello, kernel!\n");
   return 0;
void cleanup_module() {
   printk("<1>I'm not offended that you"
          "unloaded me.  Have a pleasant day!\n");

You may have to use #define for the symbol MODVERSIONS and #include for the file linux/modversions.h from the Linux source tree, depending on how your system is set up. Call this short module hello.c and compile it with:

gcc -c -DMODULE -D__KERNEL__ hello.c
You should now have a file called hello.o in your current directory. If you're currently in X, switch over to a virtual console and (as root) type insmod hello.o. You should see “Hello, kernel!” on your screen. If you would like to check that your module is loaded, use the lsmod command; it should show that your hello module is loaded and taking up memory. You can now rmmod this module; it will politely inform you that you have unloaded it.

The linux/kernel.h and linux/module.h header files are the two most basic for any module development, and you are likely to need them for any module you write. It is best if these headers (unlike modversions.h) come from /usr/include/linux rather than a Linux source tree. (If your distribution vendor has made /usr/include/linux a link to the Linux source tree, complain—that practice is liable to cause major breakage and headaches for you.) You will use quite a few more of the kernel headers for any substantial module, and you will find that

grep -l /usr/include/linux

is a good friend while developing modules.

Think of init_module as an “object constructor” for your module. init_module should allocate storage, initialize data and alter the kernel state so that your module can do its work. In this case, init_module is merely announcing its presence and returning 0 to signify success, as in many C functions. Therefore, our initialization for the hello module consists solely of calling the printk function, a particularly handy function to have at your disposal. Essentially, it functions like the standard C printf function, but for two differences. First, and most obviously, printk allows you to specify a priority for a given message (the “1” in angle brackets). Second, printk sends its output to a circular buffer that is consumed by the kernel logger and (possibly) sent to syslogd. Since the output of syslog is flushed frequently, calling printk with judiciously placed, high-priority messages can greatly aid debugging—especially since any bug in kernel-space code is liable to crash your machine or at least cause a “kernel oops”.

Why not just use printf, you ask? Simple: to do so would be impossible. The Linux kernel is not linked to the C library, so old friends like printf are unavailable in kernel-space code. However, there are many useful routines in the kernel that give you functionality similar to library routines, including workalikes for most of the str family of functions from the C library. To use these in your modules, merely include linux/string.h (be careful not to include the C library version).

If init_module is a constructor, remove_module is the destructor. Be sure to tidy up after your module as carefully as possible; if you don't free some memory or restore a data structure, you'll have to reboot to return your system to normal.



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

LKM Exploits

Anonymous's picture

I currently working on a monitoring tool in a form of LKM, which I want to use to monitor the system calls made and aruguments passed during the calls.

I can access into the system call table and replace system calls but this is only for one system call.

Any idea how to capture parameters passed and monitor the system calls made by applications?

When security is really

elf's picture

When security is really important, kernel modules should be disabled and kernel compiled static. Otherwise, once someone gets root access, it will be easy to takeover the kernel space area and hide all kind of intrusion evidences.

Re: Kernel Korner: Loadable Kernel Module Exploits

Anonymous's picture

When I try to insmod the module : "Listing 1. Checking and Logging Function ", i get the error : unresolved symbol sys_call_table, though the compilation gives no warning.

(kernel 2.4-18, red hat)

why ?

Re: Kernel Korner: Loadable Kernel Module Exploits

Anonymous's picture

sys_call_table is no longer supported, as it is dangerous to replace system calls . If you want to add explicitly, add the following to kyms.c and recompile the kernel.

Get address of un-EXPORT kernel symbol from /boot/System.map

Biswajit Paul's picture

sys_call_table is no longer EXPORT_ed by kernel(=>2.4.20) may be due to security reason.
# more /proc/ksyms|egrep sys_call_table : Shows nothing.

But i guess for that you do not required to EXPORT the symbol in kyms.c and recompile the kernel.Here is a nobel trick that can come handy.

All the kernel symbols( irrespective of whether it is EXPORT_ed or not) are saved along with their address in /boot/System.map during the kernel compilation for the purpose of future debugging.This file comes handy in the situation where symbols are not exported !!!

# more /boot/System.map|egrep sys_call_table : Display the address of sys_call_table.

Use that address in your code instead of defining sys_call_table as external symbol and ask the insmod to resolve it during the module insersion.


yudi dudulz's picture

Firstly i want to appologies. I have read your paper. I have try your command. But there is still some unresolve symbol. I have make external symbol there is nr_free_pages and buffermem_pages.
when i finish make external symbol. I try to remake modul but there still unresove.

extern int nr_free_pages(void) in the source code
then remake. Then insmod -f ip.o