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.
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:
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
with the address you get from the System.map file.
Export the sys_call_table like it was done before kernel-2.5-41 (see Changelog-2.5.41)
Open 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;
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
and watch your logfiles.
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
and watch your logfiles.
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
to spy on root and the user with UID 1000 and watch your logfiles.
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.
http://www.win.tue.nl/~aeb/linux/lk/lk-8.html The Linux Virtual File System
http://www.oopweb.com/OS/Documents/LKMPG/Volume/index.html
http://www.oopweb.com/OS/Documents/LKMPG/Volume/x958.html
http://kerneltrap.org/node/6416
http://www.linux-magazin.de/content/view/full/1251/month/4/year/2007
http://mail.nl.linux.org/kernelnewbies/2005-06/msg00018.html
http://www.linuxforums.org/programming/introducing_lkm_programming_part_ii.html
http://www.tldp.org/LDP/lkmpg/2.6/html/lkmpg.html#AEN121
http://www.linuxjournal.com/article/4378
http://nd.edu/~ablaich/sci_linux_2_6.txt
http://www.w00w00.org/files/articles/lkmhack.txt
http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html
http://caml.inria.fr/pub/docs/manual-caml-light/node17.html
Roll Your Own Firewall with Netfilter
Building Into The Linux Network Layer
Linux-Magazin: Firewalling bei IPsec-Einsatz unter Kernel 2.6
Linux-Magazin: Unsichtbarer Schutz mit Linux: Firewall auf Bridge-Ebene
Linux-Magazin: Authentifizierung an der Firewall dank Netfilter-Modul
nf-HiPAC: High performance packet classification