Rootkit

aus HaBo WiKi, der freien Wissensdatenbank von http://www.hackerboard.de
Wechseln zu: Navigation, Suche

Rootkit (engl. etwa „Administratorenausrüstung“) ist eine Bezeichnung für Softwarewerkzeuge, die das Betriebssystem in seiner Arbeit beeinflussen, indem sie systeminterne Abläufe manipulieren. Abhängig von der Art und Weise wie das Rootkit in das System eingreift, unterscheidet man zwischen Userland- und Kernelland-Rootkits.

Ihrem Ursprung nach wurden Rootkits von Administratoren eingesetzt, um ihr System abzusichern. Auch heute noch gibt es entsprechende Tools wie kstat[1], wobei gutartige Module wie z.B. St.Jude[2] und St.Michael [3] dafür sorgen, den Kernel zu überwachen. Zudem ermöglichen Module wie sebek [4] der Sicherheitsindustrie, gutartige Rootkits einzusetzen.

Demgegenüber werden Rootkits auch von Eindringlingen und zahlreicher Malware benutzt, um ihre Anwesenheit und Arbeitsweise zu verbergen. Um dies zu realisieren wird meist eine Reihe von unterschiedlichen Rootkits installiert, welche dabei helfen, Prozesse, Netzwerkverbindungen und Dateien zu verstecken oder einem Anwender erweiterte Rechte zu verleihen. Der Artikel behandelt diese Art der Rootkits. Er soll helfen, deren Arbeitsweise zu verstehen.

Userland Rootkit

Userland Rootkits tauschen bestehende Programme auf dem System aus, wie z.B. unter Unix das ps-Kommando, um einen Prozess zu verstecken, ls, um eine Datei zu verbergen und netstat, um eine Netzwerkverbindung geheim zu halten. Da es eine ganze Reihe von Programmen auszutauschen galt, entstanden frühzeitig spezielle Userland Rootkits, die eine Reihe systemspezifischer Programme zusammenfassten und für einen Angreifer leicht einzurichten waren. Meistens beinhalteten diese Rootkits auch noch andere Tools wie z.B. Backdoors oder IRC Bouncer.

Das Überschreiben der Standardprogramme durch eigene Versionen machte Userland Rootkits jedoch anfällig gegen eine Überprüfung ihrer (MD5-) Checksummen. Zudem beeinflußten sie lediglich die veränderten Programme in ihrer Arbeit, wobei nachträglich installierte Softwarealternativen wie z.B lsof damit nicht ausgehebelt werden konnten.

Kernelland Rootkit

Der Code von Kernelland-Rootkits wird im Systemkern ausgeführt. Auf diese Weise können sie vom System angeforderte Informationen bereits im Kernel manipulieren, womit sämtlichen Programme nur noch gefilterte Informationen zur Verfügung stehen, ohne dass ihnen dieser Zustand bewusst wird. So kann ein Objekt (z.B. ein Prozess oder eine Datei) aus der angeforderten Systeminformation einfach herausgenommen werden, wodurch die Programme nicht mehr in der Lage sind, das Objekt aufzulisten. Diese Objekte sind demnach vorhanden, bleiben aber vor dem Auge des Anwenders versteckt.

Im Kernel gibt es unzählige Stellen, an denen die zu versteckende Information herausgefiltert werden kann. Je nach Geschick des Entwicklers sind Kernelland Rootkits daher nahezu unauffindbar. Unter Umständen können so weder normale, noch forensische Tools erkennen, dass etwas manipuliert wurde.

Die Möglichkeiten solcher Rootkits

  • Prozesshiding: Jede gewünschte PID kann gefiltert werden. Die Kinder von versteckten Prozessen werden automatisch auch versteckt.
  • Porthidding: Jeder beliebige Port kann versteckt werden.
  • Kernel-Backdoor: Ein Kernel-Backdoor, welches z.B. auf ein speziell präperiertes Packet auf einem Port wartet. Sobald es eintrifft, wird ein Backdoor ausgeführt. Für den Eindringling hat dies den Vorteil, dass das Backdoor nicht permanent auf einem Port lauschen muss und vor allem, dass dieses "magische Packet" von einem interenen Sniffer nicht gesehen wird, da der Kernel es gleich verwirft.
  • Root-Privilegien: Jeder User mit einer bestimmten UserID bekommt direkt vom Kernel Root-Privilegien für alle Prozesse die er ausführt.
  • Dateihidding: Alle Dateien und Verzeichnisse, welche z.B. ein bestimmtes Präfix enthalten, werden vom Kernel unterdrück und tauchen damit nicht mehr in den Verzeichnissen auf.

Kernelland Rootkits unter Linux

Ab der Version 2.2 des Linux Kernels gibt es dynamisch ladbare Kernelmodule, mit deren Hilfe man Code zur Laufzeit des Kernels hinzufügen kann. Dadurch wurde es möglich, Treiber ohne Neucompilierung des Kernels und ohne Neustart zu laden. Seither haben auch Angreifer die Möglichkeit, ihren Code im laufenden Betrieb direkt im Kernel auszuführen, was ihnen neue Wege zur Manipulation des Systems eröffnet [5].

Manipulationsmöglichkeiten im Kernel

Die folgende Liste liefert eine kleine Auswahl der Methoden, mit denen Kernelland Rootkits in das System eingreifen:

  • Syscalltable patching: Direktes austauschen der Systemcalls.
  • VFS-Patching [6]: Austauschen verschiedener Pointer in den Strukturen für das root- und proc Filesystem.
  • Austauschen des Systemcalltable selber.
  • Direktes Ändern von Kernel-Code Strukturen.

Die neue Herausforderung für Rootkits ab dem Linux 2.6 Kernel

Mit der Veröffentlichung des Linux 2.6.x Kernels änderte sich die Entwicklungsgrundlage der bisherigen Rootkittechnik entscheidend. Bis zum Linux 2.6 Kernel war der System-Call Table, welcher für das Patchen der Systemcalls benötigt wird, ein exportiertes Symbol. Damit war die Addresse aller System-Calls bekannt. Doch seit dem 2.6er Kernel wurde er nicht mehr exportiert, mit dem Ziel, bisherige Rootkits wirkungslos zu machen und die Entwicklung neuer Rootkits zu unterbinden bzw. erheblich zu erschweren. Zunächst gab es tatsächlich kein gut- oder bösartiges Rootkit für den Linux 2.6er Kernel, abgesehen von adore-ng [7], welches auf vfs Technik basiert.

Das Schreiben von Rootkits wurde durch das neue Verfahren jedoch nicht verhindert. Der Weg dorthin hat sich lediglich verändert. Der folgende Text soll verdeutlichen, wie sich unter diesen Bedingungen ein Rootkit erstellen lässt.

Systemcalltable

Der Systemcalltable ist die Schnittstelle zwischen den Benutzern und dem Kernel und enthält die Adressen sämtlicher Systemcalls die (in unserem Fall) Linux unterstützt. Man kann sich die Systemcalls, die Linux anbietet, in der Datei /usr/src/linux/include/asm/unistd.h ansehen. Dort findet man aktuell 293 verschiedene Systemcalls und ihre Positionen im Systemcalltable. So steht z.B. der Systemcall für read an der Position 3.

Wichtige Systemcalls:

  • int sys getuid und int sys setuid: Dient dazu die UserID zu lesen bzw. zu setzen.
  • int sys chdir: Für das Wechseln von Verzeichnissen.
  • int sys rmdir: Verzeichniss löschen.
  • int sys mkdir: Verzeichniss erstellen.
  • int sys open: Öffnen von File-Descriptoren.
  • int sys close: Schließen von File-Descriptoren.
  • int sys read: Lesen von einem File-Descriptor.
  • int sys write: Schreiben in einen File-Descriptor
  • int sys getdents bzw. getdents64: Auflisten von Dateien in einem Verzeichnis.

Wie findet man als Kernel-Patcher den Systemcall? Eigentlich ist die Idee simpel, man durchsucht einfach das gesamte Datensegement ab und prüft an jeder Addresse ob es sich um den Systemcalltable handelt. Diese überprüfung ist dank exportierter Systemcalls möglich.

int get_sct () {
  unsigned long *ptr;
  ptr=(init_mm.end_code + 4) & 0xfffffffc;
  while((unsigned long )ptr < (unsigned long)init_mm.end_data) {
      if ((unsigned long *)*ptr == (unsigned long *)sys_close) {
        if (*((ptr-__NR_close)+__NR_read) ==(unsigned long *) sys_read && *((ptr-__NR_close)+__NR_open) == (unsigned long) sys_open ) {
          sys_call_table = (ptr-__NR_close));
          break;
        }
      }
    ptr++;
  }
  #ifdef DEBUG
    printk ("sys_call_table found at: %p\n", sys_call_table);
  #endif
}
eine Beispielanwendung

Für geübte Rootkitautoren sind die folgenden Informationen in wesentlich ausführlicherer Form innerhalb der Community verfügbar, weshalb hier keine spektakulären neuen Erkenntnisse zu erwarten sind. Demgegenüber werden jedoch Systementwickler und Administratoren anhand des Beispiels in die Lage versetzt, diese Technik im Ansatz zu verstehen, ohne dass hier Code veröffentlicht wird, der es einem Skriptkiddie erlaubt, damit Schaden anzurichten.

Patchen eines Systemcalls: Die UserID, unter dem der aktuelle Prozess läuft, erhält man durch den Systemcall getuid32. In unserem Beispiel ist es das Ziel, den Systemcall so zu kompromittieren, dass jeder User mit der UserID 6666 z.B. direkt vom Kernel die Userid 0 und damit Root-Privilegien zugewiesen bekommt. Aus Sicht eines Eindringlings hat dies den Vorteil, dass ein ihm bekanntes Benutzerkonto auf dem System Rootrechte erlangen kann, ohne in der shadow-Datei herumspielen zu müssen, da dort ein Account mit einer uid 0 doch stark auffallen würde.

Zunächst gilt es, den originalen Systemcall zu sichern …

org_getuid = sys_call_table[__NR_getuid32];

… und danach seine Adresse im Systemcall durch den gepatchten Sytemcall auszutauschen:

sys_call_table[__NR_getuid32] = (void *) my_getuid;

Patch der UID und EUID:

int my_getuid () {
  int ret;
  ret = org_getuid();
  if (ret == MAGIC_UID) {
    current->uid = 0;
    return 0;
  }
  return ret;
}
int my_geteuid () {
  int ret;
  ret = org_geteuid();
  if (ret == MAGIC_UID) {
    current->euid = 0;
    return 0;
  }
  return ret;
}

Mithilfe dieses Codes werden die UserID und die effektive UserID kompromittiert. Dazu ruft die Funktion zunächst den orginalen Systemcall auf und prüft, ob die UserID der MAGIC-UID entspricht. Ist dies der Fall, so wird die UserID für den aktuellen Prozess auf 0 gesetzt und die UserID 0 zurückgegeben. In allen anderen Fällen reicht die Funktion den Rückgabewert des orginalen Systemcalls durch. Für die effektive UserID wird analog dazu vorgegangen..

Referenz

Die Basis für diesen Artikel lieferten die Vortragsunterlagen „LKM Rootkits auf Linux 2.6“ des 22C3, welche uns mit freundlicher Genehmigung des Antisec-Teams zur Verfügung gestellt wurden (thx@newroot:).