
eBPF-basierter Linux-Rootkit-Detektor mit Multi-Channel-Cross-View-Analyse (sched_switch, NMI, /proc) zur Erkennung von DKOM, Tracepoint-Manipulation und Prozessversteckung mit hardwarenaher Integritätsüberprüfung.
System-Prozess-Integrität & Cross-View-Analyse
„Ich werde singen, also leuchte hell, SPiCa…“
SPiCa ist ein eBPF-basierter Linux-Rootkit-Erkenner, geschrieben in Rust. Der Name stammt vom Hatsune-Miku-Song SPiCa und dem darin referenzierten Stern — Spica (Alpha Virginis), dem hellsten Punkt in der Jungfrau. Was dem bloßen Auge wie ein einzelner Stern erscheint, ist eigentlich ein spektroskopischer Doppelstern: zwei Sterne in gegenseitigem Orbit, ohne Messung ihrer Spektren nicht als separate Objekte unterscheidbar. SPiCa wendet dasselbe Prinzip auf die Kernel-Beobachtung an: Mehrere unabhängige Kanäle messen denselben Kernelzustand aus physikalisch unterschiedlichen Mechanismen, und ein Rootkit, der einen unterdrückt, wird von den anderen aufgedeckt.
Haftungsausschluss: Signifikante Teile dieser Codebasis wurden mit GLM-Unterstützung generiert oder refaktorisiert. Strenge Tests und iterative Gestaltung wurden angewandt, aber überprüfen Sie den Code vor dem produktiven Einsatz auf Sicherheit und Leistung.
SPiCa ist darauf ausgelegt, den eBPF-eingeschränkten Gegner zu besiegen — einen Angreifer mit erhöhten Privilegien (CAP_BPF oder CAP_SYS_ADMIN), der ein privilegiertes eBPF-Programm in den Kernel lädt. Dieser Gegner ist grundlegend schwächer als ein LKM-Rootkit, da der BPF-Verifier harte Einschränkungen auferlegt:
| Einschränkung | Auswirkung auf den Gegner |
|---|---|
| Begrenzte Schleifen (~1M Anweisungen pro Programm) | Kann nicht den gesamten Kernelspeicher durchsuchen |
| Zeigersicherheit (direktes Dereferenzieren erfordert nachweisbar gültiges, begrenztes Ziel) | Kann nicht beliebigen Zeigern folgen |
| Eingeschränkte Helfer (pro Programmtyp genehmigte Liste) | Kann kmalloc, vmalloc oder jede Kernel-Allokationsfunktion nicht aufrufen |
Kein beliebiger Kernel-Schreibzugriff (bpf_probe_write_kernel stark eingeschränkt) | Kann task_struct, Tracepoint-Funktionsarrays oder BPF-Programm-Bytecode nicht modifizieren |
| Kartenzugriff auf geladene Referenzen beschränkt | Kann nicht auf Karten anderer Programme zugreifen (außer wenn über ID aufgezählt — siehe §7) |
Da LKMs nach dem Booten durch das LSM-Gate blockiert werden, ist dieser eingeschränkte Gegner die realistische verbleibende Bedrohung. SPiCas Anti-Evasion-Mechanik ist auf diese Bedrohung abgestimmt — jede Verteidigung ist ehrlich darüber, was sie abdeckt und was nicht.
init_module ermöglicht. SPiCa hebt die untere Grenze an, indem es den einfacheren LKM-Vektor blockiert, setzt aber keine obere Grenze für den Gegner.SPiCa ist die letzte Verteidigungsschicht in einem Defense-in-Depth-Stack, kein Ersatz für die darüberliegenden Schichten.
SPiCa führt vier eBPF-Programme aus, die an Kernel-Hooks angehängt sind, plus eine Userspace-Erkennungs-Engine, die deren Ausgabe mit der Systemsicht auf sich selbst (/proc) kreuzkorreliert.
| Kanal | Mechanismus | Was ihn unterdrückt | Kosten der Unterdrückung |
|---|---|---|---|
/proc | procfs-Verzeichnisaufzählung | DKOM (Prozess aus Aufgabenliste ausklinken) | Niedrig — gut verstandene Rootkit-Technik |
sched_switch | eBPF-Tracepoint auf dem Scheduler | Programm vom Funcs-Array trennen; Kontext filtern | Mittel — erfordert Kernel-Schreiben in das Funcs-Array |
| NMI | Hardware-Perf-Counter-Interrupt → eBPF-Perf-Event | Den IDT/NMI-Handler hooken | Nahezu sicherer Kernel-Panic — Hardware-Interrupt, nicht maskierbar |
Die zentrale architektonische Eigenschaft: Ein Rootkit kann nicht alle drei Kanäle gleichzeitig unterdrücken, ohne dass die Unterdrückung selbst erkennbar oder destabilisierend wird. Das Unterdrücken von NMI erfordert das Patchen der IDT (Interrupt Descriptor Table), was auf den meisten Kernels zu einem Panic führt. Dies ist die „scharfe Bombe“ — der einzige Weg des Angreifers zur totalen Blindheit ist einer, der das System wahrscheinlich zum Absturz bringt.
sched sees it, /proc doesn't → DKOM NMI finds sched's heartbeat stopped → TAMPER sched goes globally silent, /proc != empty → SILENT
Jede Erkennungsklasse ist ein differentielles Urteil: eine Diskrepanz zwischen zwei oder mehr Kanälen. Die Erkennungs-Engine ist eine reine Funktion über das Registry + /proc-Snapshot + Kanal-Zeitstempel – keine E/A, keine Seiteneffekte, vollständig unit-testbar.
### Das NMI-Redesign: Von der Beobachtung zur Integrität
Im ursprünglichen Design war NMI ein zweiter Prozessbeobachtungskanal, der die CPU abtastete und meldete, welche Aufgabe gerade lief. Dies war redundant: sched\_switch beobachtet bereits die Planung, und NMI tastete dieselben Daten über einen anderen Mechanismus ab. Die Redundanz kostete ~1000+ Ringpuffer-Ereignisse/s/CPU an Prozessdaten, die zu 99,999 % der Zeit bestätigten: „Ja, der Scheduler macht das, was der Scheduler tut."
In der neu gestalteten Architektur wird **NMI von der Prozessbeobachtung zur Tracepoint-Integritätsprüfung umfunktioniert.** Es meldet nicht mehr, welcher Prozess auf der CPU ist. Stattdessen überprüft es, ob `sched_switch` tatsächlich läuft, indem es einen gemeinsam genutzten `.bss`-Heartbeat liest. Dies:
1. Eliminiert ~99 % des NMI-Ringpuffer-Verkehrs (Ereignisse nahe Null im stationären Zustand)
2. Erkennt direkt Tracepoint-Trennung, Unterdrückung und BTF/Attach-Fehler (den ursprünglichen BTF-Fehler – siehe [§11](#11-der-btf-bug-vorfall))
3. Läuft von einem Hardware-Interrupt aus, außerhalb des Tracepoint-Dispatch-Pfads – immun gegen `bpf_override_return`, Kprobe-Interception und funcs-array-Manipulation
4. Liest `.bss`-Globale über direkten Speicherzugriff, nicht über BPF-Helfer – immun gegen `fmod_ret` auf Hilfsfunktionen
---
## 3. Der sched\_switch-Beobachtungskanal