Have you ever tried to google for "patching Linux system call table"? There are hundreds, if not thousands, of posts regarding this problem. Most of them are outdated, as they refer to older kernels (those, that still exported sys_call_table), others are about adding custom system call and recompiling the kernel. There are a few covering modern kernels, but those are brief and, mostly, only give you a general idea of how it works. I decided to make an in-depth description of the procedure and provide a working example.
This description consists of three parts: Modules, Miscellaneous Character Drivers and System Call Table. The second part is optional, but it would make your kernel space code more flexible and usable from user space perspective.
Modules - this part immediately follows this preamble and covers the basics of Loadable Kernel Modules.
Miscellaneous Character Drivers - here I try to provide you with in-depth explanation of what it is, how it works and, the most important - how it may be used.
System Call Table - This is going to be the shortest part as it only covers the structure of the system call table.
So, let us dive into the business.
Loadable Kernel Modules
Loadable Kernel Modules (aka LKMs) are simply extensions to the basic kernel in your operating system that may be loaded/unloaded on the fly, without a need to recompile the kernel or reboot the system. This feature exists in all major operating systems (Windows, Linux, MacOS) but we will concentrate on Linux only, preferably having kernel 2.6.38 (as this is the one I tested examples on) and above.
Modules may be utilized for different purposes, like adding support for new hardware, adding new system call or extending the kernel functionality in any other way. We are going to use kernel module for kernel patching. Why kernel module? The answer is simple - we cannot modify anything inside kernel space from a user process and we will have to perform a decent set of modifications. But first of all, we need to write our module.
Go on, open your favorite source editor and start with adding the needed include files:
Basically, these two are all you need in order to build a simple kernel module that may be loaded and unloaded. We will, however, add some mode includes later on.
There are two more things that we unconditionally need to implement - initialization and cleanup routines. Here we go:
/* your initialization code goes here */
/* Once you are done, return 0 to tell the OS that your module
has been loaded successfully or return relevant error code
(which must be a negative integer) */
printk(KERN_INFO "We are in kernel space\n");
This is our initialization routine. If you have to set up any variables or make another arrangements which are crucial for your module, you should do that here.
printk(KERN_INFO "Elvis has left the building\n");
On the other hand, the routine above is used to clean the mess we produced with our module. It is called before the module is unloaded.
As you have noticed, we used printk function here. One of the most robust functions exported by Linux kernel, generally used to output log/diagnostic messages. You may obtain its output by issuing the dmesg command.
Use module_init and module_exit macros to outline these routines:
And the last thing - add some more information to the module using the following macros:
to your code if you use license other then GPL */
/* Your name and email goes here */
MODULE_AUTHOR("your name goes here");
/* Version of your module */
MODULE_VERSION("this string is up to you");
/* Write a line about what this module is */
MODULE_DESCRIPTION("describe this module here");
That's it. We have just built a skeleton kernel module. Now we have to compile it. But the problem is that you cannot compile kernel modules as you would do with your applications. Simply because they are not simple applications. You have to create a special makefile, pointing out that it is a makefile for a kernel module:
#m stands for module/driver
#this is the list of modules that the kernel building system
#needs to build
obj-m := name_of_the_module.o
#Kernel building system (include files mostly)
#uname -r gives the version of the running kernel
KDIR := /lib/modules/`uname -r`/build
#current working directory - where to store the output
PWD := `pwd`
#default build rule
make -C $(KDIR) M=$(PWD) modules
Run make and you will have the compiled module ready to be plugged in. All you need to do now is attempt to load the module into the kernel and we have the insmod command for that purpose. Use one of the following, depending on whether your distro is debian or red hat based, respectively:
sudo insmod ./your_module_name.ko
su -c "insmod ./your_module_name.ko"
You will be prompted for your or root's password (depends on the issued command), after that you should get the shell prompt. If you get no error notifications, that means that your module has been successfully loaded, otherwise, as usual, check your code and verify it against your kernel sources. Use the following to unload your module:
sudo rmmod your_module_name
su -c "rmmod your_module_name"
you have no need for the ".ko" extension here. If you get no error notification - all's good. But if you do, that would mean, that, for some reasons, the system is unable to unload your module. The most frequent message that I used to get is about my module being busy. Sometimes, even rmmod -f cannot resolve this issue and you have to reboot your machine in order to get your module out of kernel's hands. Of course, you have to check your code after that for possible reasons.
Now type "dmesg | tail" to get the end of the kernel log. The last two lines should contain the strings we passed to printk function or the reason for not being able to unload the module. It is important to run this command before you reboot in case of an error in order to see the reason, otherwise, you will not find that entry.
So, we've just built our simplest module. Actually, this is not true, as the simplest module should not contain printk :). We are (hopefully) able to load and unload it.
In the next article, we will add a virtual device, that would be responsible for interaction with a user process and will perform all the patching/fixing operations.
Hope this post was helpful. See you in the next one!