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

3 comments:

  1. Nice post. Could you tell what is the purpose to put hook in system call?

    how can i deny the user to perform set of system call operation?

    ReplyDelete
  2. Very nice post. Resolved my problem.
    Thanks

    ReplyDelete
  3. Hi, I am trying to hook sys_open, sys_creat, sys_exeve, sys_write and some more syscalls to intercept file system activity, on kernel 4.17/4.18 on x86_64 & 32 bit.

    I am looking for a sample kernel module code or a tutorial that explains how to hook syscall on kernel 4.17/4.18 onward, where syscall wrappers are added.

    On my Ubuntu 18 kernel 4.17, /proc/kallsyms has __x64_sys_open, but not ksys_open.

    If I were to hook syscalls in a conventional way of finding a specific syscall table address and hook it, which specific syscall API I have to hook?

    sys_open or __x64_sys_open or ksys_open
    sys_open or __x64_sys_creat or ksys_creat
    sys_openat or __x64_sys_openat or ksys_openat
    sys_execve or __x64_sys_exeeve or ksys_execve
    etc ...

    and in which header files __x64_sys_creat or __x64_sys_openat are declared in?

    It will be great if someone can share a kernel module sample with what is needed to hook syscalls on kernels from 4.17 onward.

    In syscalls.h some syscalls like sys_creat are commented as /* __ARCH_WANT_SYSCALL_DEPRECATED */

    Please help, thank you,

    Kumar T

    ReplyDelete