Einführung
Im ersten Teil haben wir untersucht, wie Linux-Rootkits funktionieren: ihre Entwicklung, Taxonomie und Techniken zur Manipulation des Benutzer- und Kernelbereichs. Im zweiten Teil widmen wir uns der Detektionstechnik. Zunächst zeigen wir auf, warum die statische Erkennung bei Linux-Rootkits oft unzuverlässig ist, selbst wenn Binärdateien nur trivial verändert wurden, und gehen dann auf Verhaltens- und Laufzeitsignale ein, die Verteidiger stattdessen nutzen können. Von Missbrauch gemeinsam genutzter Objekte und LKM-Ladevorgängen bis hin zu eBPF, io_uring, Persistenz und Abwehrumgehung konzentriert sich dieser Artikel auf praktische Methoden zur Erkennung und Untersuchung von Rootkit-Aktivitäten in realen Umgebungen.
Statische Erkennung über VirusTotal
Bevor wir uns mit Verhaltenserkennungstechniken befassen, ist es sinnvoll zu untersuchen, wie gut herkömmliche statische Erkennungsmechanismen Linux-Rootkits identifizieren. Dazu haben wir ein kleines Experiment durchgeführt, bei dem VirusTotal als Ersatz für die herkömmliche signaturbasierte Virenerkennung diente. Aus öffentlich zugänglichen Forschungsarbeiten und Open-Source-Repositories wurde ein Datensatz von zehn Linux-Rootkits zusammengestellt. Jede Probe wurde entweder auf VirusTotal hochgeladen oder aus bereits vorhandenen Einsendungen abgerufen.
Für jedes Rootkit haben wir die Anzahl der Antivirenprogramme erfasst, die die ursprüngliche Binärdatei als fehlerhaft erkannt haben. Anschließend führten wir zwei weitere Tests durch:
- Entstampfte Binärdateien, erstellt mit
strip --strip-all, wobei Symboltabellen und andere nicht essentielle Metadaten entfernt wurden. - Trivial modifizierte Binärdateien, die durch Anhängen eines einzelnen Nullbytes an die Originaldatei erstellt werden: eine absichtlich unraffinierte Änderung.
Ziel war es nicht, durch ausgeklügelte Verschleierungstechniken der Entdeckung zu entgehen, sondern zu beurteilen, wie anfällig statische Signaturen selbst bei einfachsten binären Modifikationen sind.
Tabelle 1: Technischer Überblick über den analysierten Rootkit-Datensatz
| Rootkit | Grundlegende Erkennungen | Entkleidet | Nullbyte hinzugefügt |
|---|---|---|---|
| Azazel | 36/66 | 19/66 | 21/66 |
| Bedevil* | 32/66 | 32/66 | 21/66 |
| BrokePKG | 7/66 | 3/66 | 3/66 |
| Diamorphin | 33/66 | 8/64 | 22/66 |
| Kovid | 27/66 | 1/66 | 15/66 |
| Mobkit | 29/66 | 6/66 | 17/66 |
| Reptil | 32/66 | 3/66 | 20/66 |
| Snapekit | 30/66 | 3/66 | 19/66 |
| Symbiont | 42/66 | 8/66 | 22/66 |
| TripleCross | 31/66 | 17/66 | 19/66 |
Bedevil ist standardmäßig auf wenige Funktionen beschränkt, daher sind die Basis- und die beschränkte Erkennung identisch.
Beobachtungen
Wie erwartet, führte das Entfernen von Binärdateien im Allgemeinen zu einem starken Rückgang der Detektionsraten. In einigen Fällen sanken die Erkennungsraten auf nahezu Null, was darauf hindeutet, dass manche Antiviren-Engines stark auf Symbolinformationen oder andere leicht entfernbare Metadaten angewiesen sind. Noch aussagekräftiger ist der Effekt des Hinzufügens eines einzelnen Nullbytes: eine Modifikation, die weder die Programmlogik, den Ausführungsablauf noch das Verhalten verändert, aber dennoch die Erkennung für viele Beispiele erheblich verschlechtert.
Dies verdeutlicht eine grundlegende Schwäche der statischen, signaturbasierten Erkennung. Wenn bereits eine Änderung von nur einem Byte die Erkennungsergebnisse spürbar beeinflussen kann, benötigen Angreifer keine ausgeklügelte Verschleierung, um statische Scanner zu umgehen.
Verschleierungstechniken in Rootkits
Interessanterweise verwenden die meisten Rootkits in diesem Datensatz kaum oder gar keine fortgeschrittene statische Verschleierung. Wo Verschleierung eingesetzt wird, beschränkt sie sich typischerweise auf einfache XOR-Codierung von Zeichenketten oder Konfigurationsdaten oder auf einfache Packtechniken, die das Binärlayout geringfügig verändern. Diese Methoden sind kostengünstig in der Anwendung und ausreichend, um viele statische Signaturen zu überwinden.
Das Fehlen fortgeschrittenerer Verschleierungstechniken in diesen Proben ist bemerkenswert. Viele sind Open-Source-Proof-of-Concept-Rootkits, die eher dazu dienen, Techniken zu demonstrieren, als die Erkennung aggressiv zu umgehen. Doch selbst bei minimaler oder gar keiner Verschleierung erweist sich die statische Detektion als unzuverlässig.
Warum die statische Erkennung nicht ausreicht
Dieses Experiment unterstreicht einen wichtigen Punkt: Statische Erkennung allein ist grundsätzlich unzureichend für eine zuverlässige Rootkit-Erkennung. Die Fragilität statischer Signaturen (insbesondere bei geringfügigen Änderungen) bedeutet, dass sich die Verteidiger nicht auf dateibasierte Indikatoren oder hashbasierte Erkennung verlassen können, um versteckte Bedrohungen aufzudecken.
Wenn Binärdateien verändert werden können, ohne das Verhalten zu beeinflussen, ist das Verhalten des Rootkits zur Laufzeit das einzig verbleibende konstante Signal. Aus diesem Grund verlagert sich der Fokus im weiteren Verlauf dieses Blogbeitrags von statischen Artefakten hin zur dynamischen Analyse und Verhaltenserkennung. Dabei wird untersucht, wie Rootkits mit dem Betriebssystem interagieren, den Ausführungsablauf manipulieren und während der Ausführung beobachtbare Spuren hinterlassen.
Genau hier wird die Erkennungstechnik sowohl anspruchsvoller als auch weitaus effektiver.
Dynamische Detektionstechnik
Techniken zur Erkennung des Ladens von Rootkits im Benutzerland
Benutzerseitige Rootkits kapern häufig den dynamischen Linkerprozess und injizieren bösartige Shared Objects in Zielprozesse, ohne dafür Zugriff auf Kernel-Ebene zu benötigen. Eine Infektion beginnt mit der Erstellung einer gemeinsam genutzten Objektdatei. Die Erkennung neu erstellter gemeinsam genutzter Objektdateien kann durch eine Erkennungsregel erfolgen, die der unten dargestellten ähnelt:
file where event.action == "creation" and
(file.extension like~ "so" or file.name like~ "*.so.*")
Diese Dateien werden oft in beschreibbare oder temporäre Pfade wie /tmp/, /dev/shm/, oder in versteckte Unterverzeichnisse unterhalb der Benutzerverzeichnisse geschrieben. Angreifer können die Dateien entweder herunterladen, kompilieren oder direkt über einen Loader ablegen. Dieses Wissen kann auf die oben genannte Erkennungsregel angewendet werden, um das Rauschen zu reduzieren.
Beispielsweise können wir in den oben gezeigten Telemetriedaten sehen, wie der Bedrohungsakteur scp verwendet, um eine gemeinsam genutzte Objektdatei in ein verstecktes Unterverzeichnis innerhalb von /tmp herunterzuladen und sie dann in ein Bibliotheksverzeichnis zu verschieben, um sich unauffällig einzufügen. Wir haben diese und ähnliche Bedrohungen durch folgende Maßnahmen erkannt:
- Gemeinsames Objekt, erstellt von einem zuvor unbekannten Prozess
- Creation of Hidden Shared Object File
Sobald die gemeinsam genutzte Objektdatei auf dem System vorhanden ist, hat der Angreifer mehrere Möglichkeiten, sie zu aktivieren. Die am häufigsten missbrauchten Mechanismen sind die Umgebungsvariable LD_PRELOAD , die Datei /etc/ld.so.preload und dynamische Linker-Konfigurationspfade wie /etc/ld.so.conf.
Die Umgebungsvariable LD_PRELOAD ermöglicht es einem Angreifer, ein gemeinsam genutztes Objekt anzugeben, das während der Ausführung einer dynamisch verknüpften Binärdatei vor allen anderen Bibliotheken geladen wird. Dies ermöglicht ein vollständiges Überschreiben von libc -Funktionen, wie z. B. execve(), open(), oder readdir(). Diese Methode funktioniert prozessbezogen und erfordert keinen Root-Zugriff.
Um diese Technik zu erkennen, werden Telemetriedaten für die Umgebungsvariable LD_PRELOAD benötigt. Sobald dies verfügbar ist, kann jede beliebige Erkennungslogik zum Erkennen ungewöhnlicher LD_PRELOAD -Werte geschrieben werden. Zum Beispiel:
process where event.type == "start" and event.action == "exec" and
process.env_vars != null
Wie in Abbildung 1 dargestellt, war dies auch der nächste Schritt für die Angreifer. Die Angreifer haben libz.so.1 von /tmp/.X12-unix/libz.so.1 nach /usr/local/lib/libz.so.1 verschoben.
Um eine höhere Genauigkeit zu erreichen, haben wir diese Logik mithilfe des Regeltyps new_terms implementiert und dabei nur zuvor unbekannte Einträge gemeinsam genutzter Objekte innerhalb der Variable LD_PRELOAD wie folgt markiert:
- Unusual Preload Environment Variable Process Execution
- Ungewöhnliche LD_PRELOAD/LD_LIBRARY_PATH-Befehlszeilenargumente
Wenn neben den Umgebungsvariablen LD_PRELOAD und LD_LIBRARY_PATH noch weitere Variablen erfasst werden, muss die obige Regel entsprechend angepasst werden, um diese beiden Elemente explizit einzubeziehen. Um Störungen zu reduzieren, sollten statistische Analysen und/oder eine Basislinienbestimmung durchgeführt werden.
Eine weitere Aktivierungsmethode besteht darin, die /etc/ld.so.preload -Datei zu nutzen. Falls diese Datei vorhanden ist, zwingt sie den dynamischen Linker dazu, das aufgeführte Shared Object in jede dynamisch verknüpfte Binärdatei auf dem System einzufügen, was zu einer globalen Einfügung führt.
Eine ähnliche Methode besteht darin, die Konfiguration des dynamischen Linkers so zu verändern, dass schädliche Bibliothekspfade priorisiert werden. Dies kann erreicht werden, indem /etc/ld.so.conf geändert oder Einträge zu /etc/ld.so.conf.d/ hinzugefügt werden, gefolgt von der Ausführung von ldconfig , um den Cache zu aktualisieren. Dies ändert den Auflösungspfad kritischer Bibliotheken, wie z libc.so.6.
Diese Szenarien können erkannt werden, indem die Dateien /etc/ld.so.preload und /etc/ld.so.conf sowie das Verzeichnis /etc/ld.so.conf.d/ auf Erstellungs-/Änderungsereignisse überwacht werden. Anhand dieser Rohtelemetriedaten kann eine Erkennungsregel implementiert werden, um diese Ereignisse zu kennzeichnen:
file where event.action in ("creation", "rename") and
file.path like ("/etc/ld.so.preload", "/etc/ld.so.conf", "/etc/ld.so.conf.d/*")
Wir sehen häufig diese Kette, bei der ein gemeinsam genutztes Objekt erstellt und anschließend der dynamische Linker modifiziert wird.
Dies erkennen wir anhand der folgenden Erkennungsregeln:
Das gleichzeitige Auftreten dieser beiden Warnmeldungen auf einem einzigen Host erfordert eine Untersuchung.
Techniken zur Erkennung des Ladens von Rootkits im Kernelbereich
Das manuelle Laden eines LKM erfordert typischerweise die Verwendung von integrierten Befehlszeilenprogrammen wie modprobe, insmod, und kmod. Durch die Erkennung der Ausführung dieser Hilfsprogramme wird die Ladephase (bei manueller Durchführung) erkannt.
process where event.type == "start" and event.action == "exec" and (
(process.name == "kmod" and process.args == "insmod" and
process.args like~ "*.ko*") or
(process.name == "kmod" and process.args == "modprobe" and
not process.args in ("-r", "--remove")) or
(process.name == "insmod" and process.args like~ "*.ko*") or
(process.name == "modprobe" and not process.args in ("-r", "--remove"))
)
Viele Open-Source-Rootkits werden ohne Loader veröffentlicht und sind auf vorinstallierte LKM-Ladeprogramme angewiesen. Ein Beispiel ist Singularity, das ein load_and_persistence.sh -Skript bereitstellt, das mehrere Aktionen ausführt und anschließend schließlich insmod "$MODULE_DIR/$MODULE_NAME.ko" aufruft. Obwohl insmod im Befehl aufgerufen wird, ist insmod intern tatsächlich kmod , wobei insmod ein Prozessargument ist. Ein Beispiel für eine Singularitätslast:
Dies lässt sich leicht anhand der folgenden Erkennungsregeln feststellen:
- Laden von Kernelmodulen über ein integriertes Hilfsprogramm
- Kernelmodul von ungewöhnlichem Speicherort geladen
Dieser Erkennungsansatz ist jedoch alles andere als narrensicher, da viele Rootkits auf einen Loader angewiesen sind, um das LKM zu laden und so die Ausführung dieser Benutzerland-Dienstprogramme zu umgehen.
Beispielsweise ruft der Loader von Reptile den Systemaufruf init_module direkt mit einem im Speicher entschlüsselten Kernel-Blob auf:
#define init_module(module_image, len, param_values) syscall(__NR_init_module, module_image, len, param_values)
int main(void) {
[...]
do_decrypt(reptile_blob, len, DECRYPT_KEY);
module_image = malloc(len);
memcpy(module_image, reptile_blob, len);
init_module(module_image, len, "");
[...]
}
Darüber hinaus fungiert das kmatryoshka-Modul von Reptile als Chainloader im Kernel, der eine weitere versteckte LKM mithilfe eines direkten Funktionszeigers auf sys_init_module entschlüsselt und lädt, der über kallsyms_on_each_symbol() lokalisiert ist. Dies verschleiert den Lademechanismus zusätzlich vor den Augen der Nutzer.
Deshalb ist es unerlässlich zu verstehen, was diese Hilfsprogramme im Detail tun; sie sind lediglich Wrapper um die Systemaufrufe init_module() und finit_module() . Eine effektive Erkennung sollte sich daher auf die direkte Verfolgung dieser Systemaufrufe konzentrieren und nicht auf die Tools, die sie aufrufen.
Um die Verfügbarkeit der zum Laden von LKMs erforderlichen Datenquellen zu gewährleisten, können verschiedene Sicherheitsinstrumente eingesetzt werden. Auditd oder Auditd Manager sind geeignete Optionen. Um die Sammlung der Systemaufrufe init_module() und finit_module zu vereinfachen, kann die folgende Konfiguration implementiert werden.
-a always,exit -F arch=b64 -S finit_module -S init_module
-a always,exit -F arch=b32 -S finit_module -S init_module
Die Kombination dieser Rohdaten aus der Telemetrie mit einer Erkennungsregel, die bei Auftreten dieses Ereignisses einen Alarm auslöst, ermöglicht eine starke Verteidigung.
driver where event.action == "loaded-kernel-module" and
auditd.data.syscall in ("init_module", "finit_module")
Diese Strategie ermöglicht die Erkennung des Ladens von Kernelmodulen, unabhängig davon, welches Hilfsprogramm für das Ladeereignis verwendet wird. Im folgenden Beispiel sehen wir einen korrekt positiven Nachweis des Diamorphine -Rootkits.
Diese vordefinierte Regel ist hier verfügbar:
Zusätzliche Hinweise zur Entwicklung von Linux-Erkennungssystemen mithilfe von Auditd finden Sie in der Forschungsarbeit „Linux detection engineering with Auditd“.
Module außerhalb des Quellcodes und unsignierte Module
Ein weiteres Anzeichen für einen bösartigen LKM ist das Vorhandensein des Kernel-„Taint“-Flags. Wenn der Kernel feststellt, dass ein Modul geladen ist, das entweder nicht Teil des offiziellen Kernelbaums ist, keine gültige Signatur besitzt oder eine nicht-permissive Lizenz verwendet, kennzeichnet er den Kernel als „beschädigt“. Dies ist ein eingebauter Integritätsmechanismus, der anzeigt, dass sich der Kernel in einem potenziell nicht vertrauenswürdigen Zustand befindet. Ein Beispiel hierfür ist unten dargestellt, wo das Modul reveng_rtkit geladen wird:
[ 2853.023215] reveng_rtkit: loading out-of-tree module taints kernel.
[ 2853.023219] reveng_rtkit: module license 'unspecified' taints kernel.
[ 2853.023220] Disabling lock debugging due to kernel taint
[ 2853.023297] reveng_rtkit: module verification failed: signature and/or required key missing - tainting kernel
Der Kernel identifiziert das Modul als nicht zum Kernel gehörend, mit einer nicht spezifizierten Lizenz und fehlender kryptografischer Verifizierung. Dies führt dazu, dass der Kernel als beschädigt markiert wird.
Um dieses Verhalten zu erkennen, müssen System- und Kernelprotokolle analysiert und ausgewertet werden. Sobald Kernel-Log-Telemetriedaten verfügbar sind, können diese Ereignisse durch einfache Mustererkennung oder regelbasierte Erkennung identifiziert werden. Das Laden von Modulen außerhalb des Quellcodes kann wie folgt erkannt werden:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"loading out-of-tree module taints kernel."
Eine ähnliche Erkennungslogik kann implementiert werden, um das Laden vorzeichenloser Module zu erkennen:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"module verification failed: signature and/or required key missing - tainting kernel"
Mithilfe der oben beschriebenen Erkennungslogik haben wir in den Telemetriedaten echte positive Ergebnisse beim Versuch, Singularity zu laden, beobachtet:
Diese Regeln sind standardmäßig verfügbar in:
Der Protokolleintrag zeigt stets den Modulnamen an, der das Ereignis ausgelöst hat, was eine einfache Fehlerbehebung ermöglicht. Wenn der LKM bei einer durch diese Warnung ausgelösten manuellen Überprüfung nicht im System vorhanden ist, kann dies darauf hindeuten, dass sich der LKM versteckt.
Tötungssignale
Viele (Open-Source-)Rootkits nutzen kill -Signale, insbesondere solche in den höheren, nicht zugewiesenen Bereichen (32+), als verdeckte Kommunikationskanäle oder Auslöser für bösartige Aktionen. Beispielsweise könnte ein Rootkit ein bestimmtes Signal mit hoher Nummer kill abfangen (z. B. kill -64 <pid>). Nach Empfang dieses Signals könnte die Nutzlast des Rootkits so konfiguriert werden, dass sie Berechtigungen erhöht, Befehle ausführt, Versteckfunktionen umschaltet oder eine Hintertür einrichtet.
Um dies zu erkennen, können wir Auditd nutzen und eine Regel erstellen, die alle Abbruchsignale erfasst:
-a exit,always -F arch=b64 -S kill -k kill_rule
Die an kill() übergebenen Argumente sind kill(pid, sig). Wir können a1 (das Signal) abfragen, um jedes Abschaltsignal über 32 zu kennzeichnen.
process where event.action == "killed-pid" and
auditd.data.syscall == "kill" and auditd.data.a1 in (
"21", "22", "23", "24", "25", "26", "27", "28", "29", "2a",
"2b", "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34",
"35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e",
"3f", "40", "41", "42", "43", "44", "45", "46", "47"
)
Die Analyse des kill() -Systemaufrufs auf ungewöhnliche Signalwerte mittels Auditd bietet eine gute Möglichkeit zur Erkennung von Rootkits, die diese Signale nutzen, wie es beispielsweise bei Techniken wie denen von Diamorphine der Fall ist. Die vordefinierten Regeln für das Töten von Gegnern finden Sie unter:
Speicherzugriffsfehler
Schließlich ist es unerlässlich zu erkennen, dass Rootkits im Kernel-Bereich von Natur aus anfällig sind. LKMs werden typischerweise für eine bestimmte Kernelversion und -konfiguration kompiliert. Ein falsch aufgelöstes Symbol oder ein falsch ausgerichteter Speicherschreibvorgang kann einen Segmentierungsfehler auslösen. Auch wenn diese Fehler die Funktionalität des Rootkits nicht sofort offenlegen, liefern sie doch starke forensische Hinweise.
Um dies zu erkennen, muss die Rohdatenerfassung von Syslog aktiviert sein. Anschließend kann das Schreiben einer Erkennungsregel zum Kennzeichnen von Speicherzugriffsfehlermeldungen dazu beitragen, entweder bösartiges Verhalten oder Kernel-Instabilität zu identifizieren, die beide eine Untersuchung rechtfertigen:
event.dataset:"system.syslog" and process.name:"kernel" and message:"segfault"
Diese Erkennungsregel ist als Bausteinregel sofort verfügbar:
Die Kombination von Modullade-Transparenz auf Systemaufrufebene mit Kernel-Taint, Out-of-Tree-Meldungen, Kill-Signal-Erkennung und Segmentierungsfehlerwarnungen bildet die Grundlage für eine mehrschichtige Strategie zur Erkennung von LKM-basierten Rootkits.
eBPF-Rootkits
eBPF-Rootkits nutzen die legitime Funktionalität des BPF-Subsystems des Linux-Kernels aus. Programme können dynamisch geladen und angehängt werden, indem Hilfsprogramme wie bpftool oder benutzerdefinierte Lader verwendet werden, die die bpf() -Systemaufrufe missbrauchen.
Die Erkennung von eBPF-basierten Rootkits erfordert Einblick in bpf() -Systemaufrufe und die Verwendung sensibler eBPF-Hilfsfunktionen. Zu den wichtigsten Indikatoren gehören:
bpf(BPF_MAP_CREATE, ...)bpf(BPF_MAP_LOOKUP_ELEM, ...)bpf(BPF_MAP_UPDATE_ELEM, ...)bpf(BPF_PROG_LOAD, ...)bpf(BPF_PROG_ATTACH, ...)
Mithilfe von Auditd kann eine Audit-Regel erstellt werden, bei der a0 verwendet wird, um die spezifischen BPF-Systemaufrufe von Interesse anzugeben:
-a always,exit -F arch=b64 -S bpf -F a0=0 -k bpf_map_create
-a always,exit -F arch=b64 -S bpf -F a0=1 -k bpf_map_lookup_elem
-a always,exit -F arch=b64 -S bpf -F a0=2 -k bpf_map_update_elem
-a always,exit -F arch=b64 -S bpf -F a0=5 -k bpf_prog_load
-a always,exit -F arch=b64 -S bpf -F a0=8 -k bpf_prog_attach
Diese müssen umgebungsspezifisch angepasst werden, um sicherzustellen, dass harmlose Programme (z. B. EDRs oder andere Observability-Tools), die eBPF nutzen, kein Rauschen erzeugen. Ein weiteres wichtiges Signal ist die Verwendung von eBPF-Hilfsfunktionen.
Die Hilfsfunktion bpf_probe_write_user
Der bpf_probe_write_user -Helper ermöglicht es eBPF-Programmen im Kernel-Bereich, direkt in den Benutzerspeicher zu schreiben. Obwohl diese Funktion eigentlich für Debugging-Zwecke gedacht ist, kann sie von Rootkits missbraucht werden.
Die Erkennung bleibt eine Herausforderung, aber Linux-Kernel protokollieren üblicherweise die Verwendung sensibler Hilfsfunktionen wie bpf_probe_write_user. Die Überwachung dieser Einträge bietet eine Erkennungsmöglichkeit, die die Erfassung von Rohdaten aus dem Syslog und spezifische Erkennungsregeln erfordert, wie beispielsweise die folgenden:
event.dataset:"system.syslog" and process.name:"kernel" and
message:"bpf_probe_write_user"
Diese Regel gibt einen Alarm aus, sobald ein Kernel-Logeintrag die Verwendung von bpf_probe_write_user anzeigt. Auch wenn legitime Tools gelegentlich darauf zugreifen, rechtfertigt eine unerwartete oder häufige Nutzung, insbesondere in Verbindung mit verdächtigem Prozessverhalten, eine Untersuchung. Der Kontext, wie beispielsweise der Anbindungspunkt des eBPF-Programms und der damit verbundene Benutzerprozess, hilft bei der Priorisierung. Diese Erkennungsregel ist hier verfügbar:
Nachfolgend einige offensichtliche Beispiele für korrekt positive Ergebnisse, die mit dieser Logik erkannt wurden:
Die Regel greift bei nysm (einem schwer auffindbaren Post-Exploitation-Container) und boopkit (einer Linux eBPF-Backdoor).
io_uring Rootkits
ARMO research (2025) stellte eine neue Verteidigungsumgehungstechnik vor, die io_uring, ein Design für asynchrone E/A, nutzt, um die beobachtbare Systemaufrufaktivität zu reduzieren und die Standardtelemetrie zu umgehen. Diese Technik ist auf Kernelversionen ab 5.1 beschränkt und vermeidet die Verwendung von Hooks. Obwohl die Methode erst kürzlich von Rootkit-Forschern entdeckt wurde, wird sie noch aktiv weiterentwickelt und ist in ihrem Funktionsumfang noch relativ unausgereift. Ein Beispiel für ein Tool, das diese Technik nutzt, ist RingReaper. Rootkits können über io_uring_enter() Batch-Dateien, Netzwerk- und andere E/A-Operationen ausführen. Ein Codebeispiel ist unten dargestellt.
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, size, offset);
io_uring_submit(&ring);
Diese Aufrufe werden in eine Warteschlange gestellt und senden eine Leseanforderung über io_uring, wodurch die üblichen Telemetriepfade von Systemaufrufen umgangen werden.
Anders als beim Hooking der Syscall-Tabelle oder bei LD_PRELOAD-basierten Injektion handelt es sich io_uring selbst nicht um einen Rootkit-Übertragungsmechanismus, sondern um eine unauffälligere Möglichkeit, nach der Kompromittierung mit dem Dateisystem und den Geräten zu interagieren. Während io_uring aufgrund fehlender execve-ähnlicher Fähigkeiten keine Binärdateien direkt ausführen kann, ermöglicht es bösartige Aktionen wie Dateierstellung, Enumeration und Datenexfiltration bei gleichzeitiger Minimierung der Beobachtbarkeit.
Die Erkennung von io_uring-basierten Rootkits erfordert Einblick in die Systemaufrufe, die ihrer Funktionsweise zugrunde liegen, wie z. B. io_uring_setup(), io_uring_enter() und io_uring_register().
Während EDR-Lösungen möglicherweise Schwierigkeiten haben, die indirekten Auswirkungen von io_uring zu erfassen, kann Auditd diese Systemaufrufe direkt verfolgen. Die folgende Prüfregel erfasst relevante Ereignisse zur Analyse:
-a always,exit -F arch=b64 -k io_uring
-S io_uring_setup -S io_uring_enter -S io_uring_register
Dies legt jedoch nur die Verwendung des Systemaufrufs selbst offen, nicht aber die spezifische Datei oder das Objekt, auf das zugegriffen wird. Die eigentliche "Magie" von io_uring tritt innerhalb von Benutzerbibliotheken auf (z. B. liburing), wodurch die Analyse von Systemaufrufargumenten unerlässlich wird.
Beispielsweise zeigt die Überwachung io_uring_enter() mit to_submit > 0 an, dass eine E/A-Operation gebündelt wird, während abwechselnde Aufrufe mit min_complete > 0 das Abrufen des Abschlusses signalisieren. Die Korrelation mit Prozessattributen (z. B. UID=0, ungewöhnliche Pfade wie /dev/shm, /tmp oder tmpfs-gestützte Positionen) erhöht die Erkennungseffizienz.
Eine praktische Methode zum Verfolgen der io_uring -Aktivität ist die Verwendung von eBPF mit Werkzeugen wie BCC, wobei Tracepoints wie sys_enter_io_uring_enter anvisiert werden. Dies ermöglicht es Analysten, das Prozessverhalten und die aktiven Dateideskriptoren während io_uring -Operationen zu überwachen:
tracepoint:syscalls:sys_enter_io_uring_enter
{
printf("\nPID %d (%s) called io_uring_enter with fd=%d, to_submit=%d, min_complete=%d, flags=%d\n",
pid, comm, args->fd, args->to_submit, args->min_complete, args->flags);
printf("Manually inspect with: ls -l /proc/%d/fd\n", pid);
}
Um dies zu veranschaulichen, wurden verschiedene von RingReaper eingeführte Techniken getestet. Live-Tracing zeigt die verwendeten Dateideskriptoren an und hilft so, verdächtige Aktivitäten wie das Lesen von /run/utmp zu identifizieren, um festzustellen, welche Benutzer angemeldet sind:
Die Aktivität des Schreibens in eine Datei, in diesem Beispiel /root/test:
Oder durch Auflistung der Prozessinformationen über ps , indem der Inhalt comm für jede aktive PID gelesen wird:
Während die Überwachung von Systemaufrufen die Verwendung io_uring aufdeckt, gibt sie ohne zusätzliche Korrelation nicht direkt Aufschluss über die Art der E/A. io_uring ist eine relativ neue Technik und daher noch schwer zu entdecken; allerdings hat sie auch einige Einschränkungen. io_uring kann keinen Code direkt ausführen; Angreifer können jedoch Dateischreibvorgänge missbrauchen (z. B. Cronjobs, udev-Regeln), um eine verzögerte oder indirekte Ausführung zu erreichen, wie die Persistenztechniken der Malware-Familien Reptile und Sedexp zeigen.
Rootkit-Persistenztechniken
Rootkits, egal ob im Benutzermodus oder im Kernelmodus, benötigen eine Form der Persistenz, um über Neustarts oder Benutzersitzungen hinweg funktionsfähig zu bleiben. Die Methoden variieren je nach Art und Berechtigungen des Rootkits, beinhalten aber üblicherweise den Missbrauch von Konfigurationsdateien, Dienstverwaltungsskripten oder Systeminitialisierungsskripten.
Benutzerland-Rootkits – Persistenz von Umgebungsvariablen
Bei der Verwendung von LD_PRELOAD zur Aktivierung eines Benutzerland-Rootkits ist das Verhalten standardmäßig nicht persistent. Um Persistenz zu erreichen, können Angreifer Shell-Initialisierungsdateien (z. B. ~/.bashrc, ~/.zshrc, oder /etc/profile) modifizieren, um Umgebungsvariablen wie LD_PRELOAD oder LD_LIBRARY_PATH zu exportieren. Diese Modifikationen gewährleisten, dass jede neue Shell-Sitzung automatisch die Umgebung erbt, die zur Aktivierung des Rootkits erforderlich ist. Bemerkenswerterweise existieren diese Dateien sowohl für Benutzer- als auch für Root-Kontexte. Daher können auch Benutzer ohne Administratorrechte Persistenz einführen, die den Ausführungsablauf auf ihrer Berechtigungsebene kapert.
Um dies zu erkennen, kann eine Regel ähnlich der unten dargestellten verwendet werden:
file where event.action in ("rename", "creation") and file.path like (
// system-wide configurations
"/etc/profile", "/etc/profile.d/*", "/etc/bash.bashrc",
"/etc/bash.bash_logout", "/etc/zsh/*", "/etc/csh.cshrc",
"/etc/csh.login", "/etc/fish/config.fish", "/etc/ksh.kshrc",
// root and user configurations
"/home/*/.profile", "/home/*/.bashrc", "/home/*/.bash_login",
"/home/*/.bash_logout", "/home/*/.bash_profile", "/root/.profile",
"/root/.bashrc", "/root/.bash_login", "/root/.bash_logout",
"/root/.bash_profile", "/root/.bash_aliases", "/home/*/.bash_aliases",
"/home/*/.zprofile", "/home/*/.zshrc", "/root/.zprofile", "/root/.zshrc",
"/home/*/.cshrc", "/home/*/.login", "/home/*/.logout", "/root/.cshrc",
"/root/.login", "/root/.logout", "/home/*/.config/fish/config.fish",
"/root/.config/fish/config.fish", "/home/*/.kshrc", "/root/.kshrc"
)
Je nach Umgebung werden möglicherweise einige dieser Shells nicht verwendet, und es kann eine gezieltere Erkennungsregel erstellt werden, die sich beispielsweise nur auf bash oder zsh konzentriert. Die vollständige Erkennungslogik unter Verwendung von Elastic Defend und der File Integrity Monitoring-Integration von Elastic finden Sie hier:
Weitere Informationen, eine detaillierte Beschreibung dieser Persistenztechnik sowie verschiedene andere Möglichkeiten zur Erkennung ihres Missbrauchs, finden Sie in Linux Detection Engineering - A primer on persistence mechanisms.
Benutzerland-Rootkits – konfigurationsbasierte Persistenz
Durch die Modifizierung der Konfigurationsdateien /etc/ld.so.preload, /etc/ld.so.conf oder /etc/ld.so.conf.d/ können Rootkits global über Benutzer und Sitzungen hinweg persistieren (weitere Informationen zu diesem Persistenzvektor finden Sie in Linux Detection Engineering - A Continuation on Persistence Mechanisms). Sobald die Konfigurationen geschrieben sind, wird der dynamische Linker das schädliche Shared Object weiterhin einfügen, es sei denn, diese Konfigurationen werden explizit rückgängig gemacht. Diese Methoden sind von Grund auf persistent. Die Erkennungsstrategien ähneln denen im vorherigen Abschnitt und basieren auf der Überwachung von Ereignissen bei der Erstellung oder Änderung von Dateien in diesen Pfaden.
Kernelspace-Rootkits – LKM-Persistenz
Ähnlich wie Benutzerland-Rootkits sind LKMs standardmäßig nicht persistent. Ein Angreifer muss das System explizit so konfigurieren, dass das schädliche Modul beim Systemstart neu geladen wird. Dies wird typischerweise durch die Nutzung legitimer Kernelmodul-Lademechanismen erreicht:
Moduldatei: modules
Diese Datei listet Kernelmodule auf, die beim Systemstart automatisch geladen werden sollen. Durch das Hinzufügen eines bösartigen .ko -Dateinamens an dieser Stelle wird sichergestellt, dass modprobe diese beim Systemstart lädt. Diese Datei befindet sich unter /etc/modules.
Konfigurationsverzeichnis für modprobe
Dieses Verzeichnis enthält Konfigurationsdateien für das Dienstprogramm modprobe . Angreifer können Aliase verwenden, um ihr Rootkit zu verschleiern oder es automatisch zu laden, wenn ein bestimmtes Kernel-Ereignis eintritt (z. B. wenn ein Gerät untersucht wird). Diese modprobe-Konfigurationsdateien befinden sich unter /etc/modprobe.d/, /run/modprobe.d/, /usr/local/lib/modprobe.d/, /usr/lib/modprobe.d/, und /lib/modprobe.d/.
Kernelmodule konfigurieren, die beim Systemstart geladen werden sollen: modules-load.d
Diese Konfigurationsdateien legen fest, welche Module zu Beginn des Bootvorgangs geladen werden sollen und befinden sich unter /etc/modules-load.d/, /run/modules-load.d/, /usr/local/lib/modules-load.d/, und /usr/lib/modules-load.d/.
Um alle oben aufgeführten Persistenztechniken zu erkennen, kann eine Erkennungsregel ähnlich der folgenden erstellt werden:
file where event.action in ("rename", "creation") and file.path like (
"/etc/modules",
"/etc/modprobe.d/*",
"/run/modprobe.d/*",
"/usr/local/lib/modprobe.d/*",
"/usr/lib/modprobe.d/*",
"/lib/modprobe.d/*",
"/etc/modules-load.d/*",
"/run/modules-load.d/*",
"/usr/local/lib/modules-load.d/*",
"/usr/lib/modules-load.d/*"
)
Diese vorkonfigurierte Regel, die alle oben aufgeführten Pfade zu einer einzigen Erkennungsregel kombiniert, ist hier verfügbar:
Ein Beispiel für ein Rootkit, das Persistenz automatisch mit dieser Methode bereitstellt, ist Singularity. Im Rahmen der Bereitstellung werden die folgenden Befehle ausgeführt:
read -p "Enter the module name (without .ko): " MODULE_NAME
CONF_DIR="/etc/modules-load.d"
mkdir -p "$CONF_DIR"
echo "[*] Setting up persistence..."
echo "$MODULE_NAME" > "$CONF_DIR/$MODULE_NAME.conf"
Standardmäßig bedeutet dies, dass singularity.conf als neuer Eintrag unter /etc/modules-load.d/ erstellt wird. Anhand der Telemetriedaten erkennen wir diese Technik einfach durch die Überwachung der Erstellung neuer Dateien:
Diese Verzeichnisse werden auch für gutartige LKMs verwendet und sind daher anfällig für falsch positive Ergebnisse. Eine weitere Persistenzmethode besteht darin, eine trigger- oder zeitplanbasierte Technik zu verwenden, um das Kernelmodul durch Ausführen des Loaders zu laden.
Udev-basierte Persistenz – Reptile-Beispiel
Eine weniger verbreitete, aber wirkungsvolle Methode zur Persistenz besteht im Missbrauch von udev, dem Linux-Gerätemanager, der dynamische Geräteereignisse verarbeitet. Udev führt regelbasierte Skripte aus, wenn bestimmte Bedingungen erfüllt sind. Eine detaillierte Beschreibung dieser Technik findet sich in Linux Detection Engineering - A Sequel on Persistence Mechanisms. Das Reptile-Rootkit demonstriert diese Technik, indem es eine bösartige udev-Regel unter /etc/udev/rules.d/ installiert:
ACTION=="add", ENV{MAJOR}=="1", ENV{MINOR}=="8", RUN+="/lib/udev/reptile"
Diese Regel diente wahrscheinlich als Inspiration für die von Levelblue entdeckte Sedexp- Malware. So funktioniert die Regel:
ACTION=="add"Wird ausgelöst, wenn ein neues Gerät zum System hinzugefügt wird.ENV{MAJOR}=="1": Entspricht Geräten mit der Hauptnummer „1“, typischerweise Speichergeräten wie/dev/mem,/dev/null,/dev/zeround/dev/random.ENV{MINOR}=="8": Schränkt die Bedingung weiter auf/dev/randomein.RUN+="/lib/udev/reptile": Führt die Reptile-Loader-Binärdatei aus, wenn das oben genannte Gerät erkannt wird.
Diese Regel stellt die Persistenz sicher, indem sie die Ausführung einer Loader-Binärdatei auslöst, sobald das Gerät /dev/random geladen wird. Als weit verbreiteter Zufallszahlengenerator, der für zahlreiche Systemanwendungen und den Bootvorgang unerlässlich ist, ist diese Methode effektiv. Die Aktivierung erfolgt nur bei bestimmten Geräteereignissen, und die Ausführung erfolgt mit Root-Rechten über udev daemon. Um diese Technik zu erkennen, kann eine Erkennungsregel ähnlich der folgenden erstellt werden:
file where event.action in ("rename", "creation") and file.extension == "rules" and file.path like (
"/lib/udev/*",
"/etc/udev/rules.d/*",
"/usr/lib/udev/rules.d/*",
"/run/udev/rules.d/*",
"/usr/local/lib/udev/rules.d/*"
)
Die Erstellung und Änderung dieser Dateien wird anhand der folgenden vordefinierten Regeln beschrieben:
Allgemeine Persistenzmechanismen
Zusätzlich zu den Pfaden zum Laden von Kernelmodulen können Angreifer auch auf allgemeinere Linux-Persistenzmethoden zurückgreifen, um Rootkits im Benutzermodus oder im Kernelbereich über den Loader neu zu laden:
Systemd: Erstellt oder erweitert einen Dienst/Timer in einem beliebigen Verzeichnis (z. B. /etc/systemd/system/), das den Loader beim Systemstart unterstützt.
file where event.action in ("rename", "creation") and file.path like (
"/etc/systemd/system/*", "/etc/systemd/user/*",
"/usr/local/lib/systemd/system/*", "/lib/systemd/system/*",
"/usr/lib/systemd/system/*", "/usr/lib/systemd/user/*",
"/home/*.config/systemd/user/*", "/home/*.local/share/systemd/user/*",
"/root/.config/systemd/user/*", "/root/.local/share/systemd/user/*"
) and file.extension in ("service", "timer")
Initialisierungsskripte: Erstellen oder Anhängen an ein bösartiges Run-Control-Skript (/etc/rc.local), SysVinit -Skript (/etc/init.d/) oder Upstart- Skript (/etc/init/).
file where event.action in ("creation", "rename") and
file.path like (
"/etc/init.d/*", "/etc/init/*", "/etc/rc.local", "/etc/rc.common"
)
Cron-Jobs: Erstellen oder erweitern Sie einen Cron-Job , der die wiederholte Ausführung eines Loaders ermöglicht.
file where event.action in ("rename", "creation") and
file.path like (
"/etc/cron.allow", "/etc/cron.deny", "/etc/cron.d/*",
"/etc/cron.hourly/*", "/etc/cron.daily/*", "/etc/cron.weekly/*",
"/etc/cron.monthly/*", "/etc/crontab", "/var/spool/cron/crontabs/*",
"/var/spool/anacron/*"
)
Sudoers: Erstellen oder Anhängen einer bösartigen sudoers-Konfiguration als Hintertür.
file where event.type in ("creation", "change") and
file.path like "/etc/sudoers*"
Diese Methoden sind weit verbreitet, flexibel und oft einfacher zu erkennen, wenn man die Prozessherkunft oder die Dateimodifikations-Telemetrie nutzt.
Die Liste der vordefinierten Erkennungsregeln zum Aufspüren dieser Persistenztechniken ist unten aufgeführt:
- Systemd Service Created
- Systemd Timer Created
- System V Init Script Created
- rc.local/rc.common File Creation
- Cron Job Created or Modified
- Sudoers-Dateiaktivität
- Mögliche Persistenz durch Dateiänderung
Rootkit-Abwehrumgehungstechniken
Obwohl Rootkits per Definition Werkzeuge zur Umgehung von Verteidigungsmechanismen sind, setzen viele zusätzliche Techniken ein, um während und nach der Installation unentdeckt zu bleiben. Diese Methoden sind darauf ausgelegt, die Sichtbarkeit in Protokollen zu vermeiden, Endpoint-Detection-Agenten zu umgehen und gängige Untersuchungsabläufe zu stören. Im folgenden Abschnitt werden die wichtigsten Umgehungstechniken moderner Linux-Rootkits beschrieben, kategorisiert nach ihren operativen Zielen.
Versuche, bei der Stationierung unentdeckt zu bleiben.
Bedrohungsakteure konzentrieren sich aus forensischer Sicht häufig auf unauffällige Ausführungstaktiken. Ein Angreifer könnte beispielsweise seine Payloads im Shared-Memory-Verzeichnis /dev/shm speichern und ausführen, da es sich hierbei um ein vollständig virtuelles Dateisystem handelt und die Payloads daher niemals auf die Festplatte gelangen. Aus forensischer Sicht ist das großartig, aber für uns als Ingenieure für Verhaltenserkennung ist dieses Verhalten sehr verdächtig und ungewöhnlich.
Als Beispiel schlägt der Autor von Singularity, obwohl er kein tatsächlicher Bedrohungsakteur ist, die folgende Bereitstellungsmethode vor:
cd /dev/shm
git clone https://github.com/MatheuZSecurity/Singularity
cd Singularity
sudo bash setup.sh
sudo bash scripts/x.sh
Um dieses Verhalten mit einer nahezu Null-Falsch-Positiv-Rate zu erkennen, müssen mehrere Auslösemechanismen installiert werden, beginnend mit dem Klonen eines GitHub-Repositorys in das Verzeichnis /dev/shm .
sequence by process.entity_id, host.id with maxspan=10s
[process where event.type == "start" and event.action == "exec" and (
(process.name == "git" and process.args == "clone") or
(
process.name in ("wget", "curl") and
process.command_line like~ "*github*"
)
)]
[file where event.type == "creation" and
file.path like ("/tmp/*", "/var/tmp/*", "/dev/shm/*")]
Das Klonen von Verzeichnissen in /tmp und /var/tmp ist üblich, daher könnten diese in Umgebungen, in denen das Klonen von Repositories üblich ist, aus dieser Regel entfernt werden. Die gleiche Aktivität in /dev/shm ist jedoch sehr selten.
Das vom Loader aufgerufene Skript setup.sh fährt fort, indem es das LKM in einem Unterverzeichnis /dev/shm/ kompiliert. Echte Bedrohungsakteure kompilieren in der Regel nicht auf dem Host selbst, es kommt jedoch auch vor, dass dies geschieht.
sequence with maxspan=10s
[process where event.type == "start" and event.action == "exec" and
process.name like (
"*gcc*", "*g++*", "c++", "cc", "c99", "c89", "cc1*", "clang*",
"musl-clang", "tcc", "zig", "ccache", "distcc"
)] as event0
[file where event.action == "creation" and file.path like "/dev/shm/*" and
process.name like (
"ld", "ld.*", "lld", "ld.lld", "mold", "collect2", "*-linux-gnu-ld*",
"*-pc-linux-gnu-ld*"
) and
stringcontains~(event0.process.command_line, file.name)]
Diese Endpunktlogik erkennt die Ausführung eines Compilers, woraufhin der Linker eine Datei in /dev/shm (oder einem Unterverzeichnis) erstellt.
Und schließlich, da das gesamte Repository in /dev/shm geklont und setup.sh und x.sh ausgeführt wurden, werden wir die Prozessausführung aus dem gemeinsam genutzten Speicherverzeichnis beobachten, was in den meisten Umgebungen ungewöhnlich ist:
process where event.type == "start" and event.action == "exec" and
process.executable like ("/dev/shm/*", "/run/shm/*")
Diese Regeln sind in den Repositories „Erkennungsregeln“ und „Schutzartefakte“ verfügbar:
- Git-Repository oder Datei-Download in verdächtiges Verzeichnis
- Linux-Kompilierung in verdächtigem Verzeichnis
- Binary Executed from Shared Memory Directory
Als legitime Prozesse getarnt
Um bei der Prozessaufzählung oder Systemüberwachung nicht aufzufallen, benennen Rootkits ihre Prozesse und Threads oft um, sodass sie harmlosen Systemkomponenten ähneln. Gängige Verkleidungen sind:
kworker,migrationoderrcu_sched(Kernel-Threads)sshd,systemd,dbus-daemon, oderbash(Benutzerland-Daemons)
Diese Namen wurden so gewählt, dass sie sich in die Ausgabe von Tools wie ps, top oder htop einfügen und somit die manuelle Erkennung erschweren. Beispiele für Rootkits, die diese Technik nutzen, sind Reptile und PUMAKIT. Reptile erzeugt bei der Initialisierung über kworker ungewöhnliche Netzwerkereignisse:
network where event.type == "start" and event.action == "connection_attempted"
and process.name like~ ("kworker*", "kthreadd") and not (
destination.ip == null or
destination.ip == "0.0.0.0" or
cidrmatch(
destination.ip,
"10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12",
"192.0.0.0/24", "192.0.0.0/29", "192.0.0.8/32", "192.0.0.9/32",
"192.0.0.10/32", "192.0.0.170/32", "192.0.0.171/32", "192.0.2.0/24",
"192.31.196.0/24", "192.52.193.0/24", "192.168.0.0/16", "192.88.99.0/24",
"224.0.0.0/4", "100.64.0.0/10", "192.175.48.0/24","198.18.0.0/15",
"198.51.100.0/24", "203.0.113.0/24", "240.0.0.0/4", "::1",
"FE80::/10", "FF00::/8"
)
)
Das folgende Beispiel zeigt die Port-Knocking-Funktionalität von Reptile, bei der der Kernel-Thread einen neuen Prozess startet, seine Sitzungs-ID auf 0 ändert und die Netzwerkverbindung herstellt:
Es scheint, dass Reptile denselben kworker -Prozess nutzt, um Dateien zu erstellen:
file where event.type == "creation" and
process.name like~ ("kworker*", "kthreadd")
PUMAKIT erzeugt Kernel-Threads, um Benutzerbefehle über kthreadd auszuführen, aber eine ähnliche Aktivität wurde auch über einen kworker -Prozess in anderen Rootkits beobachtet:
process where event.type == "start" and event.action == "exec" and
process.parent.name like~ ("kworker*", "kthreadd") and
process.name in ("bash", "dash", "sh", "tcsh", "csh", "zsh", "ksh", "fish") and
process.args == "-c"
Diese kworker und kthreadd -Regeln können aufgrund interner Operationen des Linux-Kernels zu falsch positiven Ergebnissen führen. Diese können je nach Umgebung problemlos ausgeschlossen oder der Logik zusätzliche Befehlszeilenargumente hinzugefügt werden.
Diese Regeln sind in den Repositories „Erkennungsregeln“ und „Schutzartefakte“ verfügbar:
- Netzwerkaktivität wurde über Kworker erkannt.
- Verdächtige Dateierstellung über Kworker
- Verdächtige Erhöhung der Mitarbeiter-UID
- Shell-Befehlsausführung über Kworker
Darüber hinaus können bösartige Prozesse, wie beispielsweise ein initialer Dropper oder ein Persistenzmechanismus, sich als Kernel-Threads ausgeben und dazu eine eingebaute Shell-Funktion ausnutzen. Mithilfe des Befehls exec -a kann jeder beliebige Prozess mit einem vom Angreifer frei wählbaren Namen gestartet werden. Die Maskierung von Kernelprozessen kann durch die folgende Erkennungsabfrage erkannt werden:
process where event.type == "start" and event.action == "exec" and
process.command_line like "[*]" and process.args_count == 1
Dieses Verhalten wird im Folgenden dargestellt, wo mehrere Malware-Programme versuchten, sich als Kernel-Worker-Prozess oder Webdienstprozess auszugeben.
Diese Technik wird auch häufig von Bedrohungsakteuren missbraucht, die das Toolkit The Hacker's Choice (THC) einsetzen, insbesondere beim Einsatz von gsocket.
Regeln im Zusammenhang mit Kernel-Masquerading und Masquerading über exec -a im Allgemeinen sind im Repository protections-artifacts verfügbar:
Eine weitere Technik, die sowohl in freier Wildbahn als auch in Horse Pill zu beobachten ist, ist die Verwendung von prctl um den Prozessnamen zu unterdrücken. Um sicherzustellen, dass diese Telemetriedaten verfügbar sind, kann eine benutzerdefinierte Auditd-Regel erstellt werden:
-a exit,always -F arch=b64 -S prctl -k prctl_detection
Und begleitet von folgender Erkennungslogik:
process where host.os.type == "linux" and auditd.data.syscall == "prctl" and
auditd.data.a0 == "f"
Ermöglicht die Erkennung dieser Technik. Im folgenden Screenshot sehen wir Telemetriebeispiele für die Anwendung dieser Technik. Dabei ist process.executable unverständlich, und prctl wird verwendet, um sich im System als legitimer Prozess auszugeben.
Diese Regel, einschließlich der zugehörigen Installationsanleitung, finden Sie hier:
Obwohl es viele Arten der Verkleidung gibt, sind dies die am häufigsten beobachteten.
Protokoll- und Auditbereinigung
Viele Rootkits enthalten Routinen, die Spuren ihrer Installation oder Aktivität aus den Protokollen löschen. Eine dieser Techniken besteht darin, die Vergangenheit des Opfers zu löschen. Dies kann auf zwei Arten festgestellt werden. Eine Methode besteht darin, das Löschen der Shell-Verlaufsdatei zu erkennen:
file where event.type == "deletion" and file.name in (
".bash_history", ".zsh_history", ".sh_history", ".ksh_history",
".history", ".csh_history", ".tcsh_history", "fish_history"
)
Die zweite Methode besteht darin, Prozessausführungen mit Befehlszeilenargumenten zu erkennen, die mit dem Löschen des Shell-Verlaufs zusammenhängen:
process where event.type == "start" and event.action == "exec" and (
(
process.args in ("rm", "echo") or
(
process.args == "ln" and process.args == "-sf" and
process.args == "/dev/null"
) or
(process.args == "truncate" and process.args == "-s0")
)
and process.command_line like~ (
"*.bash_history*", "*.zsh_history*", "*.sh_history*", "*.ksh_history*",
"*.history*", "*.csh_history*", "*.tcsh_history*", "*fish_history*"
)
) or
(process.name == "history" and process.args == "-c") or
(
process.args == "export" and
process.args like~ ("HISTFILE=/dev/null", "HISTFILESIZE=0")
) or
(process.args == "unset" and process.args like~ "HISTFILE") or
(process.args == "set" and process.args == "history" and process.args == "+o")
Die Aktivierung beider Erkennungsregeln (Prozess- und Dateierkennung) ermöglicht eine robustere Verteidigungsstrategie mit mehrschichtigen Sicherheitsmechanismen.
Beim Laden können Rootkits den Kernel verunreinigen oder Out-of-Tree-Meldungen erzeugen, die beim Parsen von Syslog- und Kernel-Logs identifiziert werden können. Um ihre Spuren zu verwischen, löschen Rootkits möglicherweise diese Protokolldateien:
file where event.type == "deletion" and file.path in (
"/var/log/syslog", "/var/log/messages", "/var/log/secure",
"/var/log/auth.log", "/var/log/boot.log", "/var/log/kern.log",
"/var/log/dmesg"
)
Oder löschen Sie den Kernel-Nachrichtenpuffer über dmesg:
process where event.type == "start" and event.action == "exec" and
process.name == "dmesg" and process.args in ("-c", "--clear")
Ein Beispiel für ein Rootkit, das dmesg automatisch bereinigt, ist das bds-Rootkit, das durch Ausführen von /opt/bds_elf/bds_start.sh geladen wird:
Eine weitere Möglichkeit, diese Protokolle zu löschen, besteht in der Verwendung von journalctl:
process where event.type == "start" and event.action == "exec" and
process.name == "journalctl" and
process.args like ("--vacuum-time=*", "--vacuum-size=*", "--vacuum-files=*")
Dies ist eine Technik, die von Singularity verwendet wurde:
Eine weitere Technik, die vom Loader-Skript von Singularity angewendet wird, ist das Löschen aller mit dem Rootkit verbundenen Dateien, falls es nicht geladen werden kann oder sobald der Ladevorgang abgeschlossen ist. Für eine gründlichere Löschung wählte der Autor die Verwendung von shred anstelle von rm. rm (remove) löscht einfach den Dateizeiger, was zwar schnell ist, aber die Datenwiederherstellung ermöglicht. shred überschreibt die Dateidaten mehrfach mit zufälligen Daten, sodass sie nicht wiederhergestellt werden können. Dadurch wird die Löschung zwar dauerhafter, gleichzeitig aber auch unübersichtlicher aus Sicht der Verhaltenserkennung, da shred auf den meisten Linux-Systemen nicht üblicherweise verwendet wird.
process where event.type == "start" and event.action == "exec" and
process.name == "shred" and (
// Any short-flag cluster containing at least one of u/z,
// and containing no extra "-" after the first one
process.args regex~ "-[^-]*[uz][^-]*" or
process.args in ("--remove", "--zero")
) and
not process.parent.name == "logrotate"
Der obige reguläre Ausdruck stellt sicher, dass Versuche, die Erkennung durch Kombinieren oder Modifizieren von Flags zu umgehen, schwieriger werden. Nachfolgend ein Beispiel dafür, wie Singularity nach Dateien sucht, die mit seiner Bereitstellung in Zusammenhang stehen, und diese anschließend vernichtet:
Diese Techniken zum Entfernen von Dateien und Protokollen können mithilfe mehrerer vordefinierter Erkennungsregeln erkannt werden:
- System Log File Deletion
- Attempt to Clear Kernel Ring Buffer
- Versuch, die Protokolle über Journalctl zu löschen
- Dateilöschung über Shred
Sobald ein Rootkit seine Spuren beseitigt hat, kann es die geänderten Dateien mit einem Zeitstempel versehen, um sicherzustellen, dass keine Spuren von Dateimodifikationen zurückbleiben:
process where event.type == "start" and event.action == "exec" and
process.name == "touch" and
process.args like (
"-t*", "-d*", "-a*", "-m*", "-r*", "--date=*", "--reference=*", "--time=*"
)
Ein Beispiel hierfür wird hier gezeigt, wo ein Angreifer den Zeitstempel der Datei /etc/ld.so.conf als Referenzzeit für die Dateien auf dem Laufwerk /dev/shm verwendet, um sich unauffällig zu verhalten:
Dies ist eine Technik, deren Abdeckung wir sowohl durch Erkennungsregeln als auch durch Schutzartefakte erweitert haben:
Obwohl es immer noch weitere Techniken gibt, die wir in dieser Studie nicht behandelt haben, sind wir zuversichtlich, dass diese Studie dazu beitragen wird, das Verständnis der Linux-Rootkit-Landschaft und ihrer Erkennungstechniken zu vertiefen.
Rootkit-Präventionstechniken
Die Verhinderung von Linux-Rootkits erfordert eine mehrschichtige Verteidigungsstrategie, die Kernel- und Benutzerbereichshärtung, strenge Zugriffskontrolle und kontinuierliche Überwachung kombiniert. Obligatorische Zugriffskontrollframeworks wie SELinux und AppArmor schränken das Prozessverhalten und die Möglichkeiten zur Persistenz im Benutzermodus ein. Gleichzeitig mindern Kernel-Härtungstechniken wie der Lockdown-Modus, KASLR, SMEP/SMAP und Tools wie LKRG das Risiko einer Kompromittierung auf Kernel-Ebene. Durch die Einschränkung der Kernelmodulnutzung mittels Deaktivierung des dynamischen Ladens oder Erzwingung der Modulsignierung werden gängige Vektoren für die Rootkit-Bereitstellung weiter reduziert.
Die Transparenz hinsichtlich schädlichen Verhaltens wird durch Auditd und die Überwachung der Dateiintegrität hinsichtlich Systemaufrufen und Dateiaktivitäten sowie durch EDR-Lösungen, die verdächtiges Laufzeitverhalten erkennen und verhindern, verbessert. Die Sicherheit wird zusätzlich gestärkt, indem die Prozessprivilegien durch seccomp-bpf, Linux-Funktionen und den Landlock LSM minimiert werden, wodurch der Zugriff auf Systemaufrufe und die Interaktion mit dem Dateisystem eingeschränkt werden.
Rechtzeitige Kernel- und Software-Updates, bei Bedarf unterstützt durch Live-Patching, schließen bekannte Sicherheitslücken, bevor sie ausgenutzt werden können. Darüber hinaus sollten Dateisystem- und Gerätekonfigurationen durch das erneute Einbinden sensibler Dateisysteme mit restriktiven Flags und das Deaktivieren des Zugriffs auf Kernel-Speicherschnittstellen wie /dev/mem und /proc/kallsyms abgesichert werden.
Keine einzelne Kontrollmaßnahme kann Rootkits gänzlich verhindern. Eine mehrschichtige Verteidigung, die Konfigurationshärtung, statische und dynamische Erkennung sowie forensische Bereitschaft kombiniert, bleibt unerlässlich.
Fazit
Im ersten Teil dieser Reihe haben wir untersucht, wie Linux-Rootkits intern funktionieren, und ihre Entwicklung, Taxonomie und Techniken zur Manipulation des Benutzer- und Kernelbereichs erforscht. Im zweiten Teil haben wir dieses Wissen in praktische Erkennungsstrategien umgesetzt und uns dabei auf die Verhaltenssignale und die Laufzeittelemetrie konzentriert, die die Rootkit-Aktivität aufdecken.
Während Windows-Malware weiterhin im Mittelpunkt des Interesses kommerzieller Sicherheitsanbieter und der Bedrohungsforschungsgemeinschaften steht, bleibt Linux vergleichsweise wenig erforscht, obwohl es den Großteil der weltweiten Cloud-Infrastruktur, Hochleistungsrechnerumgebungen und Internetdienste antreibt.
Unsere Analyse zeigt, dass sich Linux-Rootkits weiterentwickeln. Die zunehmende Verbreitung von Technologien wie eBPF, io_uring und containerisierten Linux-Workloads führt zu neuen Angriffsflächen, die noch nicht gut verstanden oder umfassend geschützt sind.
Wir ermutigen die Sicherheitscommunity zu Folgendem:
- Investieren Sie in Linux-spezifische Erkennungsentwicklung aus statischer und dynamischer Sicht.
- Teilen Sie Forschungsergebnisse, Machbarkeitsnachweise und Erkennungsstrategien offen, um den kollektiven Wissensaufbau unter den Verteidigern zu beschleunigen.
- Zusammenarbeit mit Anbietern, Hochschulen und der Industrie, um die Rootkit-Abwehr unter Linux auf das gleiche Reifeniveau wie unter Windows zu bringen.
Nur durch die gemeinsame Verbesserung von Transparenz, Erkennung und Reaktionsfähigkeit können die Verteidiger dieser schwer fassbaren und sich schnell entwickelnden Bedrohungslandschaft einen Schritt voraus sein.