Loadable Kernel Module Exploits

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

Now we graduate to a more advanced example. Listing 1 presents a module that logs a message every time someone other than uid 0 (root) or uid 500 (me on my workstation) invokes the write system call with the word “Linux” somewhere in the buffer. You may have to stretch a little to find a use for this module by itself, but I assure you it demonstrates several useful concepts. We are able to do this all by replacing the write system call with our own function that performs the checking and logging, and then calls write. Let's go through this example step by step.

Listing 1. Checking and Logging Function

Notice all of the include files. There sure are a lot of them, but don't despair, the ones we are going to worry about are linux/sched.h and asm/uaccess.h. The sched.h include allows you to access the current task_struct structure via the current macro, providing a great deal of useful information about the current process (see Table 1 for a list of some useful fields in task_struct), while uaccess.h provides useful macros for accessing user-space memory (more on this later).

Table 1. Useful task_struct Fields

Even these few fields in task_struct are enough to enable some really interesting modules. Should arbitrary users be allowed to su to root? You can prevent them from doing so by wrapping setuid and checking for one of several prespecified UIDs before allowing the “real” setuid. This will allow you to develop, at the kernel level, an equivalent to the wheel group, or group of users that are allowed to su root. As an aside, the FSF has long held that the wheel group is a tool of fascist administrators (see the documentation for GNU su for more information).

Being able to audit or alter the behavior of system calls, simply on the basis of which uid invokes them, is obviously a powerful ability. It can make for good security policy to control and audit the actions of the “nobody” user and its friends, the uucp, mail and postgres users carefully. However, an even more powerful technique is to alter behavior based on an argument. We will ignore sys_call_table and origwrite for now and proceed directly to wrapped_write, which examines both the uid of the invoking process and its buffer argument.

The first thing you should notice is that wrapped_write begins with a call to kmalloc. Why not malloc, you may ask? Remember, we're still in kernel space, and we don't have access to malloc and other standard library functions. Even if we did, calling malloc, which returns a pointer to user-space memory, would be worthless. We need to allocate some memory in kernel space to copy data into from the buf argument. This is an important point: the same memory visibility barrier between kernel and user space that keeps your programs from crashing the kernel also adds a little bit of complexity to your kernel programming. When you call write from a C program, you pass a pointer to a user-space memory block that is inaccessible from the kernel. Therefore, if you want to do any operations on data pointed to by a user-space pointer, you will have to first copy that memory area into kernel space. The copy_from_user macro does this for you. copy_from_user takes three arguments, a “to” pointer, a “from” pointer and a count.

The remainder of wrapped_write is fairly straightforward, given what we know about current and task_struct. Perhaps a more interesting module would use strstr to check for the string “Linux sucks”, and if it existed, alter write_buf at that point to contain “Linux rule”, then transfer write_buf back to user space (with the copy_to_user macro) before calling the original write. Then, if unsuspecting users wrote “Linux sucks”, it would be replaced with “Linux rules”. kfree is important here. Leaking memory in the kernel is a bad thing, so be sure to kfree everything you kmalloc.

It is in init_module that we actually make the switch so that our function is called instead of the original write. Recall that syss_call_table is an array of pointers to functions. By altering the value at index SYS_write (a constant representing the system call number for write), we are able to cause another function to replace write. Be sure to save the original function, so you can replace it when the module is unloaded! You can test this module out by compiling and installing it with insmod; then su to some user other than 0 or 500, and type

% echo "I like Linux"

on a virtual console. You should get a message from the kernel that you're talking about Linux again. Congratulations! You are now ready for a module that does something useful.



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