Sunday, June 10, 2012

Hooking the Linux System Call Table in 2012

Greetings,

This post is going to walk through the process of creating a linux kernel module (lkm) to hook the syscall table. There is a lot of documentation on hooking the syscall table available, however most of this is out of date.

History

Original syscall hooking implementations simply updated the pointer in the syscall table. From 2.6.24, the syscall table had write permissions removed. The most common workaround for this was to make the syscall table writable using the change_page_attr function.
int set_page_rw(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ | VM_WRITE;
    return change_page_attr(pg, 1, prot);
}

int set_page_ro(long unsigned int _addr)
{
    struct page *pg;
    pgprot_t prot;
    pg = virt_to_page(_addr);
    prot.pgprot = VM_READ;
    return change_page_attr(pg, 1, prot);
}

Unfortunately shortly after, the change_page_attr was depreciated. There have been some workarounds such as disabling page protection (http://stackoverflow.com/a/4000943), however until recently, there have not been any clean solutions.

Mid 2011, there was a post by Corey Henderson (http://stackoverflow.com/a/6742086), that demonstrated setting the permissions on a memory page manually. Later, Alexey Lyashko, released the first of 3 excellent blog entries (http://syprog.blogspot.com.au/2011/10/hijack-linux-system-calls-part-i.html).
int make_rw(unsigned long address)
{
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    if(pte->pte &~ _PAGE_RW)
       pte->pte |= _PAGE_RW;
    return 0;
}

int make_ro(unsinged long address)
{
    unsigned int level;
    pte_t *pte = lookup_address(address, &level);
    pte->pte = pte->pte &~ _PAGE_RW;
    return 0;
} 
http://syprog.blogspot.com.au/2011/10/hijack-linux-system-calls-part-iii.html

The rest of this post will run through how to create a simple kernel module to hook system calls.

Creating the Kernel Module

The kernel module will be developed on a Backtrack 5r2 32bit image. (http://www.backtrack-linux.org/):

root@bt:~# uname -a
Linux bt 3.2.6 #1 SMP Fri Feb 17 10:40:05 EST 2012 i686 GNU/Linux


Firstly, lets create our makefile. In a clean directory, create the file "Makefile", with contents below:
obj-m := hello.o
KDIR := /lib/modules/`uname -r`/build
PWD := `pwd`
default:
    make -C $(KDIR) M=$(PWD) modules
Ensure the spacing before the make command is a single tab.

Now lets create a sample kernel hook. Create the file "hello.c", with contents below:
#include <linux/module.h>
#include <asm/unistd.h>
#include <linux/highmem.h>
 
unsigned long *sys_call_table = (unsigned long*) 0xc1538160;
 
asmlinkage int (*real_open)(const char* __user, int, int);
 
asmlinkage int custom_open(const char* __user file_name, int flags, int mode)
{
      printk("hooked: open(\"%s\", %X, %X)\n", file_name,
                                                    flags,
                                                    mode);
  return real_open(file_name, flags, mode);
}  
 
int make_rw(unsigned long address)
{  
   unsigned int level;
   pte_t *pte = lookup_address(address,&level);
   if(pte->pte &~ _PAGE_RW)
      pte->pte |= _PAGE_RW;
   return 0;
}
 
int make_ro(unsigned long address)
{
   unsigned int level;
   pte_t *pte = lookup_address(address, &level);
   pte->pte = pte->pte &~ _PAGE_RW;
   return 0;
}
 
static int hello_init(void){
  make_rw((unsigned long)sys_call_table);
  real_open = (void*)*(sys_call_table + __NR_open);
  *(sys_call_table + __NR_open) = (unsigned long)custom_open;
  make_ro((unsigned long)sys_call_table);
  return 0;
}
 
static void hello_exit(void){
  make_rw((unsigned long)sys_call_table);
  *(sys_call_table + __NR_open) = (unsigned long)real_open;
  make_ro((unsigned long)sys_call_table);
}
 
module_init(hello_init);
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");
Lets test this compiles. Running make should give output similar to below:

root@bt:~/lkm# make
make -C /lib/modules/`uname -r`/build M=`pwd` modules
make[1]: Entering directory `/usr/src/linux-source-3.2.6'
  CC [M] /root/lkm/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC /root/lkm/hello.mod.o
  LD [M] /root/lkm/hello.ko
make[1]: Leaving directory `/usr/src/linux-source-3.2.6'
root@bt:~/lkm# ls
hello.c hello.mod.c hello.o modules.order
hello.ko hello.mod.o Makefile Module.symvers


The final step is to update the line:
unsigned long *sys_call_table = (unsigned long*) 0xc1538160;
The address to the syscall table is not exported in recent linux builds. There are various ways to determine the address. For our purposes, grepping /boot/System.map-* should do:

root@bt:~/lkm# grep sys_call_table /boot/System.map-3.2.6
c1562120 R sys_call_table


Update the address of sys_call_table in the code to the the value from /boot/System.map*

Run make again to ensure the binaries are updated. We should be good to go now. Lets test it!

Open a new terminal and tail /var/log/syslog:

root@bt:~# tail -f /var/log/syslog 

In another terminal, lets load the module:
insmod hello.ko

Go back to the first terminal and check the syslog. You should see output similar to below:
Jun 10 23:51:09 bt kernel: [ 2829.772978] hooked: open("/tmp/vteAU7IFW", 80C2, 180)

If you see the above, you have successfully hooked the syscall table.

To stop hooking and remove the kernel module:
root@bt:~/lkm# rmmod hello