LKM

Important files

  • /usr/src/linux-`uname -r`/Documentation/kbuild/modules.txt
  • /usr/src/linux-`uname -r`/include/asm/unistd.h
  • /usr/src/linux-`uname -r`/include/asm-i386/unistd.h
  • /usr/src/linux-`uname -r`/arch/i386/kernel/*_ksyms.c
  • /usr/src/linux-`uname -r`/arch/i386/kernel/entry.S
  • /proc/kallsyms

Setup

Makefile

obj-m += moduleX.o moduleY.o

# EXTRA_CFLAGS := -I/usr/include

all:
   make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

modules_install:
   make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install

clean:
   make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Make a directory lkm where you place your kernel module sources and the Makefile. Replace moduleX.o moduleY.o with the appropiate module names. To build the modules use

% make

If you don't execute

# make modules_install

as root you won't be able to use modprobe but only insmod and rmmod. These latter tools will need the path to the module and don't resolve dependencies.

sys_call_table

As of kernel-2.6 the sys_call_table is no longer exported. There are two ways to get the pointer to the sys_call_table. Keep in mind that you need to do only 'one' of the following:

A. System.map

Along with the kernel a System.map file is created, residing in /usr/src/linux/System.map. Therein we find the address of the sys_call_table:

grep ' sys_call_table' /usr/src/linux/System.map | sed “s/^\(.*\) \(.* sys_call_table\)/0x\1/”

To get the LKMs below running which use the sys_call_table replace 0x00000000

  1. define SYS_CALL_TABLE_ADDRESS 0x00000000;

with the address you get from the System.map file.

B. Patching the kernel

Export the sys_call_table like it was done before kernel-2.5-41 (see Changelog-2.5.41)

Open ksyms.c:

  1. vi /usr/src/linux-`uname -r`/arch/`uname -m`/kernel/*_ksyms.c

and add

// +++ manually added
extern void * sys_call_table;
EXPORT_SYMBOL( sys_call_table );

Rebuild and boot your new kernel. To get the LKMs below running which use the sys_call_table comment the SYS_CALL_TABLE_ADDRESS line:

// #define SYS_CALL_TABLE_ADDRESS 0x00000000;

Highjacking the kernel

My first loadable kernel module

The following module will write an entry to your kernel logfile upon loading an unloading:

#include <linux/kernel.h>     /* We're doing kernel work     */
#include <linux/module.h>     /* Specifically, a module      */

#define MODULE_NAME "module0"

MODULE_LICENSE( "GPL" );
MODULE_AUTHOR( "Reto Glauser" );

int init_module( void )
{
   printk( KERN_INFO "Loading kernel module '%s'\n", MODULE_NAME );
   return 0;
}

void cleanup_module( void )
{
   printk( KERN_INFO "Unloading kernel module '%s'\n", MODULE_NAME );
}

Load it with

  1. insmod module_name.ko

and watch your logfiles.

Intercepting system calls 1

Now, let's do some real work. The following module will intercept the system call __NR_chdir which is used to change a directory and write an entry to the logfile:

#include <linux/kernel.h>     /* We're doing kernel work     */
#include <linux/module.h>     /* Specifically, a module      */
#include <linux/unistd.h>     /* The list of system calls    */

#define MODULE_NAME "module1"

/*
 * To get the sys_call_table address, execute:
 *
 * # grep ' sys_call_table' /usr/src/linux/System.map | sed "s/^\(.*\) \(.* sys_call_table\)/0x\1/"
 *
 * and replace 0x000000 from below with that address
 */
#define SYS_CALL_TABLE_ADDRESS 0x00000000;

#ifdef SYS_CALL_TABLE_ADDRESS
   void **sys_call_table = ( void * )SYS_CALL_TABLE_ADDRESS;
#else
   extern void *sys_call_table[];
#endif
   
MODULE_LICENSE( "GPL"          );
MODULE_AUTHOR ( "Reto Glauser" );
MODULE_VERSION( "1.0"          );

asmlinkage int ( *original_sys_call )( const char * );

/*
 * This function is a wrapper arround the original __NR_chdir system call
 * and writes the directory a user changed into to the kernel log
 */
asmlinkage int custom_sys_call( char *path )
{
   /* Print message every time we are called */
   printk( KERN_INFO "'%s': someone is changing the directory (%s)\n", MODULE_NAME, path );

   /* Call the original system call to maintain functionality */
   return original_sys_call( path );
}

/* This function is called when the module is loaded */
int init_module()
{
   if( sys_call_table == 0x00000000 )
{
      printk( KERN_INFO "'%s': You need to set the address of sys_call_table to a valid value\n", MODULE_NAME );
      return -1;
}
   
   printk( KERN_INFO "'%s': Loading kernel module with sys_call_table @ 0x%x\n", MODULE_NAME, ( int )sys_call_table );

   /* Store reference to the original system call */
   original_sys_call = sys_call_table[ __NR_chdir ];

   /* Manipulate sys_call_table to call custom_sys_call instead of original_sys_call */
   sys_call_table[ __NR_chdir ] = custom_sys_call;

   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_chdir ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )original_sys_call, *( int * )sys_call_table[ __NR_chdir ] );

   return 0;
}

/* This function is called when the module is unloaded */
void cleanup_module()
{
   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_chdir ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )sys_call_table[ __NR_chdir ], *( int * )original_sys_call );

   /* Restore original_sys_call */
   sys_call_table[ __NR_chdir ] = original_sys_call;

   printk( KERN_INFO "'%s': Unloading kernel module with sys_call_table @ 0x%x\n", MODULE_NAME, ( int )sys_call_table );
}

Load it with

  1. insmod module_name.ko

and watch your logfiles.

Intercepting system calls 2

Goint a step further this module will accept a parameter list of UIDs to spy on and log __NR_chdir system calls of those specified users:

#include <linux/kernel.h>     /* We're doing kernel work     */
#include <linux/module.h>     /* Specifically, a module      */
#include <linux/moduleparam.h>   /* which will have params      */
#include <linux/unistd.h>     /* The list of system calls    */
#include <linux/sched.h>      /* Current (process) structure */

#define MODULE_NAME "module2"
#define NUMBER_OF_USERS_TO_SPY_ON 5

/*
 * To get the sys_call_table address, execute:
 *
 * # grep ' sys_call_table' /usr/src/linux/System.map | sed "s/^\(.*\) \(.* sys_call_table\)/0x\1/"
 *
 * and replace 0x000000 from below with that address
 */
#define SYS_CALL_TABLE_ADDRESS 0x00000000;

#ifdef SYS_CALL_TABLE_ADDRESS
   void **sys_call_table = ( void * )SYS_CALL_TABLE_ADDRESS;
#else
   extern void *sys_call_table[];
#endif

MODULE_LICENSE  ( "GPL"                       );
MODULE_AUTHOR   ( "Reto Glauser"              );
MODULE_VERSION  ( "1.0"                       );
MODULE_PARM_DESC( array_of_uid, "UIDs to spy on" );

int array_of_uid[ NUMBER_OF_USERS_TO_SPY_ON ];
int argc = 0;
module_param_array( array_of_uid, int, &argc, 0 );

asmlinkage int ( *original_sys_call )( const char * );

/*
 * This function is a wrapper arround the original __NR_chdir system call
 * and writes the directory a user changed into to the kernel log
 */
asmlinkage int custom_sys_call( const char *path )
{
   int i;
   /* Print message every time we are called */
   for( i = 0; i < argc; i++ )
{
      if( array_of_uid[ i ] == current->uid )
{
         printk( KERN_INFO "'%s': UID %i is changing the directory (%s)\n", MODULE_NAME, current->uid, path );
         break;
}
}

   /* Call the original system call to maintain functionality */
   return original_sys_call( path );
}

/* This function is called when the module is loaded */
int init_module()
{
   if( sys_call_table == 0x00000000 )
{
      printk( KERN_INFO "'%s': You need to set the address of sys_call_table to a valid value\n", MODULE_NAME );
      return -1;
}
   
   printk( KERN_INFO "'%s': Loading kernel module with sys_call_table @ 0x%x\n", MODULE_NAME, ( int )sys_call_table );

   /* Store reference to the original system call */
   original_sys_call = sys_call_table[ __NR_chdir ];

   /* Manipulate sys_call_table to call custom_sys_call instead of original_sys_call */
   sys_call_table[ __NR_chdir ] = custom_sys_call;
   
   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_chdir ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )original_sys_call, *( int * )sys_call_table[ __NR_chdir ] );

   return 0;
}

/* This function is called when the module is unloaded */
void cleanup_module()
{
   if( sys_call_table[__NR_chdir] != custom_sys_call )
      printk( KERN_ALERT "'%s': Somebody else played with the sys_call_table\n", MODULE_NAME );

   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_chdir ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )sys_call_table[ __NR_chdir ], *( int * )original_sys_call );

   /* Restore original_sys_call */
   sys_call_table[ __NR_chdir ] = original_sys_call;

   printk( KERN_INFO "'%s': Unloading kernel module with sys_call_table @ 0x%x\n", MODULE_NAME, ( int )sys_call_table );
}

Load it with

  1. insmod module_name.ko uid_array=0,1000

to spy on root and the user with UID 1000 and watch your logfiles.

Intercepting system calls 3

The following module will intercept the system call __NR_unlinkat and __NR_rmdir which are used to delete files or a directories and write an entry to the kernel logfile:

#include <linux/kernel.h>     /* We're doing kernel work     */
#include <linux/module.h>     /* Specifically, a module      */
#include <linux/unistd.h>     /* The list of system calls    */

#define MODULE_NAME "module3"

/*
 * To get the sys_call_table address, execute:
 *
 * # grep ' sys_call_table' /usr/src/linux/System.map | sed "s/^\(.*\) \(.* sys_call_table\)/0x\1/"
 *
 * and replace 0x000000 from below with that address
 */
#define SYS_CALL_TABLE_ADDRESS 0x00000000;

#ifdef SYS_CALL_TABLE_ADDRESS
   void **sys_call_table = ( void * )SYS_CALL_TABLE_ADDRESS;
#else
   extern void *sys_call_table[];
#endif

MODULE_LICENSE( "GPL"          );
MODULE_AUTHOR ( "Reto Glauser" );
MODULE_VERSION( "1.0"          );

asmlinkage int ( *original_sys_call_unlinkat )( int dirfd, const char *pathname, int flags );
asmlinkage int ( *original_sys_call_rmdir    )( const char *pathname );

/*
 * This function is a wrapper arround the original __NR_unlinkat system call
 * and writes the directory a user changed into to the kernel log
 */
asmlinkage int custom_sys_call_unlinkat( int dirfd, const char __user *pathname, int flags )
{
   /* Print message every time we are called */
   printk( KERN_INFO "'%s': someone is deleting a file (%s)\n", MODULE_NAME, pathname );

   /* Call the original system call to maintain functionality */
   return original_sys_call_unlinkat( dirfd, pathname, flags );
}

/*
 * This function is a wrapper arround the original __NR_rmdir system call
 * and writes the directory a user changed into to the kernel log
 */
asmlinkage int custom_sys_call_rmdir( const char __user *pathname )
{
   /* Print message every time we are called */
   printk( KERN_INFO "'%s': someone is deleting a directory (%s)\n", MODULE_NAME, pathname );

   /* Call the original system call to maintain functionality */
   return original_sys_call_rmdir( pathname );
}

/* This function is called when the module is loaded */
int init_module()
{
   if( sys_call_table == 0x00000000 )
{
      printk( KERN_INFO "'%s': You need to set the address of sys_call_table to a valid value\n", MODULE_NAME );
      return -1;
}

   printk( KERN_INFO "'%s': Loading kernel module with sys_call_table @ 0x%x\n", MODULE_NAME, ( int )sys_call_table );
   
   /* Store reference to the original system call */
   original_sys_call_unlinkat = sys_call_table[ __NR_unlinkat ];
   original_sys_call_rmdir    = sys_call_table[ __NR_rmdir    ];

   /* Manipulate sys_call_table to call custom_sys_call_unlinkat instead of original_sys_call_unlinkat */
   sys_call_table[ __NR_unlinkat ] = custom_sys_call_unlinkat;

   /* Manipulate sys_call_table to call custom_sys_call_rmdir instead of original_sys_call_rmdir */
   sys_call_table[ __NR_rmdir    ] = custom_sys_call_rmdir;

   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_unlinkat ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )original_sys_call_unlinkat, *( int * )sys_call_table[ __NR_unlinkat ] );
   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_rmdir    ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )original_sys_call_rmdir   , *( int * )sys_call_table[ __NR_rmdir    ] );

   return 0;
}

/* This function is called when the module is unloaded */
void cleanup_module()
{
   if( sys_call_table[ __NR_unlinkat ] != custom_sys_call_unlinkat || sys_call_table[ __NR_rmdir ] != custom_sys_call_rmdir )
      printk( KERN_ALERT "'%s': Somebody else played with the sys_call_table\n", MODULE_NAME );

   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_unlinkat ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )sys_call_table[ __NR_unlinkat ], *( int * )original_sys_call_unlinkat );
   printk( KERN_INFO "'%s': changing sys_call_table[ __NR_rmdir    ] from 0x%x to 0x%x\n", MODULE_NAME, *( int * )sys_call_table[ __NR_rmdir    ], *( int * )original_sys_call_rmdir    );
   
   /* Restore original_sys_call_rm */
   sys_call_table[ __NR_unlinkat ] = original_sys_call_unlinkat;
   sys_call_table[ __NR_rmdir    ] = original_sys_call_rmdir;

   printk( KERN_INFO "'%s': Unloading kernel module with sys_call_table @ 0x%x\n", MODULE_NAME, ( int )sys_call_table );
}

Load the module:

# insmod module_name.ko

and watch your logfile.

Sources

Firewall

 
projects/lkm.txt · Last modified: 2009-04-13 13:18 by blinkeye
 
Recent changes RSS feed Creative Commons License Powered by GNU/Linux Powered by Gentoo Powered by Apache Powered by XCache Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki