9

I need to replace a standard system call (e.g. SYS_mkdir) with my own implementation.

As I read in some sources, including this question on Stackoverflow, the sys_call_table is not exported symbol since kernel version 2.6.

I tried the following code:

    #include <linux/module.h> 
    #include <linux/kernel.h> 
    #include <linux/unistd.h> 
    #include <asm/syscall.h> 

    int (*orig_mkdir)(const char *path); 

    ....

    int init_module(void) 
    { 
            orig_mkdir=sys_call_table[__NR_mkdir]; 
            sys_call_table[__NR_mkdir]=own_mkdir;  
            printk("sys_mkdir replaced\n"); 
            return(0); 
    } 

    ....

Unfortunately I receive compiler error:

 error: assignment of read-only location ‘sys_call_table[83]’

How can I replace the system call?

EDIT: Is there any solution without kernel patching?

Community
  • 1
  • 1
Alex
  • 9,891
  • 11
  • 53
  • 87

5 Answers5

8

this works for me.

See Linux Kernel: System call hooking example and https://bbs.archlinux.org/viewtopic.php?id=139406

asmlinkage long (*ref_sys_open)(const char __user *filename, int flags, umode_t mode);
asmlinkage long new_sys_open(const char __user *filename, int flags, umode_t mode)
{
  return ref_sys_open(filename, flags, mode);
}

static unsigned long **aquire_sys_call_table(void)
{
  unsigned long int offset = PAGE_OFFSET;
  unsigned long **sct;

  while (offset < ULLONG_MAX) {
    sct = (unsigned long **)offset;

    if (sct[__NR_close] == (unsigned long *) sys_close) 
      return sct;

    offset += sizeof(void *);
  }
  print("Getting syscall table failed. :(");
  return NULL;
}


// Crazy copypasted asm stuff. Could use linux function as well...
// but this works and will work in the future they say.
static void disable_page_protection(void) 
{
  unsigned long value;
  asm volatile("mov %%cr0, %0" : "=r" (value));

  if(!(value & 0x00010000))
    return;

  asm volatile("mov %0, %%cr0" : : "r" (value & ~0x00010000));
}

static void enable_page_protection(void) 
{
  unsigned long value;
  asm volatile("mov %%cr0, %0" : "=r" (value));

  if((value & 0x00010000))
    return;

  asm volatile("mov %0, %%cr0" : : "r" (value | 0x00010000));
}


static int __init rootkit_start(void) 
{

  //Hide me

  print("loaded");

  if(!(sys_call_table = aquire_sys_call_table()))
    return -1;

  disable_page_protection(); 
  {
    ref_sys_open = (void *)sys_call_table[__NR_open];
    sys_call_table[__NR_open] = (unsigned long *)new_sys_open;
  }
  enable_page_protection();
  return 0;
}

static void __exit rootkit_end(void) 
{
  print("exiting");

  if(!sys_call_table) {
    return;
  }

  disable_page_protection();
  {
    sys_call_table[__NR_open] = (unsigned long *)ref_sys_open;
  }
  enable_page_protection();
}
Community
  • 1
  • 1
domenukk
  • 953
  • 15
  • 28
7

Yes there is a solution without patching/rebuilding the kernel. Use the Kprobes infrastructure (or SystemTap).

This will allow you to place "probes" (functions) at any point(s) within the kernel, using a kernel module.

Doing similar stuff by modifying the sys_call_table is now prevented (it's read-only) & is considered a dirty hack! Kprobes/Jprobes/etc are a "clean" way to do so..Also, the documentation and samples provided in the kernel source tree is excellent (look under the kernel src tree- Documentation/kprobes.txt).

kaiwan
  • 2,114
  • 1
  • 18
  • 23
  • 1
    kprobes/systemtap won't let you *replace* the system call handler, but can supplement it / precede it. – fche Dec 16 '12 at 20:48
  • Hey, kprobes uses patching :) – Ilya Matveychikov Dec 17 '12 at 19:00
  • @fche: yes, i agree. The point was that the effect is similar.. @ IlyaMatveychikov: AFAIK, no, kprobes is an in-kernel feature; you don't need to apply any patch. Also, most distros have kprobes enabled.. – kaiwan Dec 19 '12 at 08:45
1

The problem is caused due to the fact that sys_call_table is read only. In order to avoid the error, before manipulating the sys_call_table, you have to make it writable as well. The kernel provides a function to achieve it. And that function is given as set_mem_rw().

Just add the below code snippet before manipulating the sys_call_table

set_mem_rw((long unsigned int)sys_call_table,1);

In the exit function of the kernel module,please do not forget to revert back the sys_call_table back to read only.It can be achieved as below.

set_mem_ro((long unsigned int)sys_call_table,1);    
PaulDaviesC
  • 1,161
  • 3
  • 16
  • 31
0

First, you need to determine the location of sys_call_table. See here.

Before writing into the just located system table, you have to make its memory pages writable. For that check here and if that doesn't work, try this.

Community
  • 1
  • 1
LubosD
  • 781
  • 6
  • 18
0

Use LSM infrustructure.

Look at LSM hooks path_mkdir or inode_mkdir for details. One question that needs to be solved is how to register your own LSM module while the system don't allow it explicitly. See the answer for details here:

How can I implement my own hook function with LSM?

Community
  • 1
  • 1
Ilya Matveychikov
  • 3,936
  • 2
  • 27
  • 42