Recently I set myself the task of adding code to the Linux Kernel by writing a loadable kernel module, since I’m trying to get back into C programming.
There are two main types of kernel module: static and loadable. The static modules are added into the kernel as its compiled. Loadable modules, on the other hand, are dynamic, and they’re loaded/unloaded during runtime. When loaded, they pretty much become part of the kernel.
There are two locations in the file system to note:
- /lib/modules is where the kernel modules are stored. There are subdirectories for the different types.
- /etc/modules lists the modules to load during system boot.
A standard Linux installation also ships with something called modutils, that is a group of programs for manipulating loadable kernel modules (LKMs):
- insmod: Load module
- rmmod: Unload module
- depmod: Find dependencies
- kerneld: Kernel daemon
- ksyms: Show exported symbols
- lsmod: List loaded modules
- modinfo: Show module information
- modprobe: Load module and resolve dependencies
But these can only be used with whatever’s already in the file system.
Structure of a Kernel Module
A module is created a little differently from the typical C program. Whereas the typical program has a main() function, these have entry and exit functions, module_init() and module_exit(), as they must receive and hand over kernel execution. These correspond to the loading and unloading from kernel’s memory space, and they probably invoke whatever system calls for sorting out memory allocation.
Writing the Module
The concept is simple: we merely write a C program, compile it, then use the insmod command. A simple ‘Hello World’ program is all we need to get things going, as the learning curve here is in getting our own module successfully loaded.
First we might need to install kernel headers, or kernel-dev, from the distribution’s repository. Other tutorials recommend compiling and running from the most recent kernel.
The following is an edited ‘Hello World’ example from The Linux Documentation Project:
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
printk(KERN_INFO "Hello world 1.\n");
*/ Non-0 return if init_module failed;*/
printk(KERN_INFO "Goodbye world 1.\n");
We might think the next step is a matter of putting the code in an IDE, clicking compile/build, then loading the resulting object file. Unfortunately it’s not that simple. If we try, the compiler will throw up an error because it can’t find the headers to include. Also, the compiler links code to whatever objects are available in the standard library, and the objects for this module aren’t available until it’s loaded into kernel space.
Instead, we use a ‘makefile’. Create a new directory (here I’ve called it ‘MyModule‘). This will contain mymodule.c and the makefile. Next, open a new file in the IDE or text editor, then add the following:
obj-m += mymodule.o
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Save this as ‘Makefile‘, in the directory that was just created. Now that directory will contain mymodule.c and Makefile. The next step is to build this using ‘make‘. The output should be something along the lines of:
The directory should now be populated by the newly-created module and several other files (.zip archive here). The module here is mymodule.ko.
The next step is to load mymodule.ko into the kernel’s memory space using insmod() as root.
Since I used ‘printk‘ and not ‘printf‘, nothing was printed to the screen. How can we determine whether it was successfully loaded? Use lsmod, of course:
And by checking syslog, we should find that the module has indeed printed a kernel message:
The entry looking something like:
Sep 29 23:12:05 BEATRIX kernel: [19161.607371] Hello world 1.
And that’s it. The module doesn’t do much, but the important thing is we managed to create a module and load it.
Practical Uses and Security Implications
By writing our own kernel modules, we can extend the functionality of the kernel, add our own drivers, interfaces and features, and as we’ve seen, a kernel module can be any C program created to perform any conceivable function.
On a standard Linux installation there could be hundreds of them, even if only a fraction are ever used. A malicious program, for example a kernel-mode rootkit, could easily be hidden anywhere here. Next we could look in /etc/modules to see which of them load when the system boots. This seems the perfect way to hide a rootkit, short of compiling it into the kernel itself.