
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:
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.
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
Ein eBPF-Programm, das an den `sched_switch`-Tracepoint angehängt ist, feuert jedes Mal, wenn der Kernel einen Prozess auf eine CPU einplant. Es liest die PID und den comm des eingehenden Tasks direkt aus den Tracepoint-Argumenten mittels traditioneller (nicht-BTF) Lesevorgänge mit festem Offset:```
ctx.read_at::<u32>(56) → next_pid
ctx.read_at::<[u8;16]>(40) → next_comm
Absichtlich nicht BTF/CO-RE. Das Layout der Tracepoint-Argumente ist über Kernel-Versionen hinweg stabil (es ist Teil der Tracepoint-ABI). Die Verwendung hartcodierter Offsets vermeidet die Kernel-Versionen-Anfälligkeit der BTF-aufgelösten Strukturnavigation. Dies ist eine bewusste Designentscheidung, die in §11 dokumentiert ist.
Bei jedem Aufruf führt das Programm Folgendes aus:
next_pid und next_comm aus dem Tracepoint-KontextProcessInfo-Struktur per XOR mit BASE_KEYsc_sched Ringpufferbpf_ktime_get_ns() in die .bss-Globale SCHED_HEARTBEAT — den Herzschlag, den der NMI-Integritätsprüfer überwachtDas Programm vermeidet bewusst bpf_get_current_pid_tgid() in diesem Kontext. Zum Zeitpunkt von sched_switch ist "current" die ausgehende Aufgabe, nicht die eingehende. Die Tracepoint-Argumente liefern die korrekte (eingehende) Prozessidentität.
Die Erkennungsmaschine verwendet eine einzige monotone Zeitbasis: Nanosekunden seit dem Start des SPiCa-Prozesses (Instant::now() seit dem Eintritt von run_detection()). Wenn ein Sched-Ereignis eintrifft, speichert der Ereignis-Handler die aufruferseitig bereitgestellte now (prozesslokale Nanosekunden), nicht den im Ereignis eingebetteten eBPF-Wert bpf_ktime_get_ns(). Dies vermeidet die Zeitbasen-Diskrepanz, die auftreten würde, wenn die Kernel-Boot-Zeit mit der prozesslokalen Zeit gemischt würde – ein Fehler, der in früheren Versionen vorhanden war und dazu führte, dass alle Liveness-Prädikate stillschweigend fehlschlugen.
Das NMI-Programm ist an einen Hardware-Perf-Counter (HW_CPU_CYCLES) gebunden, der auf Hardware-Interrupt-Ebene auslöst. NMIs können nicht mit cli deaktiviert werden – ihre Unterdrückung erfordert eine Neuprogrammierung der PMU oder das Hooking der IDT, beides ist auffällig und destabilisierend.
Der Integritätsprüfmechanismus ist denkbar einfach:``` .bss (shared within SPiCa's ELF object — all programs see the same memory): SCHED_HEARTBEAT ← written by sched_switch on every invocation NMI_LAST_HB ← NMI's record of the last heartbeat it observed NMI_FIRST_TICK ← first NMI invocation timestamp (grace period reference) NMI_LAST_EMIT ← throttle: last event emission timestamp
sched_switch runs → SCHED_HEARTBEAT = bpf_ktime_get_ns()
NMI fires (once per second, throttled): if SCHED_HEARTBEAT == NMI_LAST_HB: if past grace period (5 seconds since first NMI tick): → sched_switch stopped running → emit event_type=1 (TAMPER) else: → sched_switch is alive → emit event_type=0 (heartbeat OK) NMI_LAST_HB = SCHED_HEARTBEAT
### Warum dies jeden Angriff im Scope erkennt
| Angriff | Was passiert | Was das NMI sieht |
|---|---|---|
| Tracepoint vom funcs-Array gelöst | sched\_switch stoppt die Ausführung | `SCHED_HEARTBEAT` friert ein → TAMPER |
| BTF-Fehler / Attach-Fehler (still) | sched\_switch wird nie ausgeführt | `SCHED_HEARTBEAT` bleibt 0 → TAMPER (nach Grace) |
| `bpf_override_return` auf eine Scheduler-Funktion | Falls verhindert, dass der Tracepoint feuert | `SCHED_HEARTBEAT` friert ein → TAMPER |
| Bytecode in-place gepatcht | Erfordert willkürlichen Kernel-Write (LKM-Ebene) | Außerhalb des eBPF-Bedrohungsmodells |
| Ringbuffer-Consumer-Zeiger manipuliert | Sched-Events erreichen Userspace nicht | `SCHED_HEARTBEAT` steigt trotzdem (das Programm läuft) → kein falscher TAMPER; Userspace erkennt über `max(sched_last)` veraltet → SILENT |
### Warum speziell `.bss`
`.bss`-Globale sind im internen Datensegment des BPF-Programms gespeichert, das von einem internen Array-Map verwaltet wird, das der Loader verwaltet. Sie sind:
- **Nicht separat pinnbar** — sie erscheinen nicht als benannte Maps in `/sys/fs/bpf/`
- **Nicht abfangbar über `bpf_map_update_elem`-Hooks** — `.bss`-Schreibvorgänge sind direkte Speicherzugriffe, keine Map-Update-Syscalls. Der alte `sc_canary`-Mechanismus (Vergleich einer `.bss`-Kopie mit einer benannten Map-Kopie zur Erkennung von `bpf_map_update_elem`-Abfangen) wird nicht mehr benötigt.
- **Zwischen Programmen im selben ELF-Objekt gemeinsam genutzt** — sched\_switch und NMI kommunizieren über `.bss` ohne externe Schnittstelle
### Immunität gegen BPF-basierte Abfangversuche
Der NMI-Integritätsprüfer ist strukturell immun gegen BPF-Override-Angriffe, da eine fundamentale Eigenschaft gilt: `bpf_override_return` fängt Funktions**aufrufe** ab, aber der NMI-Prüfer *ruft* die Dinge, die er überprüft, nicht auf – er *liest direkt `.bss`-Speicher*. Man kann den Rückgabewert eines Speicherlesevorgangs nicht überschreiben, denn ein Speicherlesevorgang ist kein Funktionsaufruf.
Zusätzlich:
- `bpf_probe_read_kernel` (wird in alternativen Designs zum Lesen von Kernel-Strukturen verwendet) ist ein fehlersicherer Helfer, der jede Adresse akzeptiert – aber SPiCas `.bss`-Heartbeat-Design benötigt ihn nicht einmal. Der Prüfer liest `.bss`-Globale über direkte Ladebefehle.
- Das NMI-Programm läuft im NMI-Kontext, in dem Kprobes strukturell unzuverlässig sind (der Kernel verzögert oder unterdrückt sie). Ein Kprobe-basierter Angriff auf die Ausführung des Prüfers kämpft gegen die Hardware.
### NMI-Ereignissemantik
Der NMI-Ringbuffer (`sc_nmi`) überträgt leichte Ereignisse:
| `event_type` | Bedeutung | Aktion im Userspace |
|---|---|---|
| 0 | Heartbeat – NMI aktiv, sched\_switch aktiv | `last_nmi_heartbeat`-Zeitstempel aktualisieren |
| 1 | TAMPER – NMI aktiv, sched\_switch-Heartbeat eingefroren | `[TAMPER]` sofort ausgeben |
Ereignisse werden höchstens einmal pro Sekunde gesendet (gedrosselt durch `NMI_LAST_EMIT`). Wenn der NMI-Ringbuffer länger als 5 Sekunden still bleibt, löst Userspace `[SILENT]` aus – der NMI-Kanal selbst ist tot.
---
## 5. Erkennungslogik```mermaid
graph TD
subgraph RING0["Kernel Space: Four eBPF Programs"]
direction TB
SCHED_P["TracePoint, sched_switch<br/>read next_pid, next_comm<br/>write SCHED_HEARTBEAT (.bss)<br/>XOR obfuscate → sc_sched"]
NMI_P["PerfEvent hardware NMI<br/>read SCHED_HEARTBEAT (.bss)<br/>compare to NMI_LAST_HB<br/>frozen → event_type=1 (TAMPER)<br/>alive → event_type=0 (heartbeat)<br/>→ sc_nmi"]
LSM_P["BPF LSM, kernel_read_file<br/>READING_MODULE<br/>gate=0: allow + log<br/>gate=1: EPERM + log → sc_lsm"]
WATCH_P["TracePoint, sched_process_exit<br/>current == SPICA_PID (.bss)? → sc_wd flag"]
end
subgraph RING3["User Space: Differential Engine"]
ENGINE["SPiCa (Tokio async)"] -->|XOR deobfuscate| RB_S[(sc_sched RingBuf)]
ENGINE -->|event_type=0: heartbeat<br/>event_type=1: TAMPER| RB_N[(sc_nmi RingBuf)]
ENGINE -->|raw read| RB_L[(sc_lsm RingBuf)]
ENGINE -->|read_dir| PROC[" /proc"]
RB_S --> FSM{Detection FSM}
PROC --> FSM
FSM -->|scheduled, absent /proc, >2s| D1["[DKOM]"]
FSM -->|in /proc, never seen by sched, >5s| D2["[GHOST]"]
RB_N -->|event_type = 1| D3["[TAMPER]"]
FSM -->|sched channel silent, /proc ≠∅| D4["[SILENT]"]
FSM -->|raw getdents64 ≠ libc readdir| D9["[HOOK]"]
RB_S -->|same TGID, different start_time_ns| D5["[DUPE]"]
RB_L -->|allowed = 1| D7["[LKM-ALLOW]"]
RB_L -->|allowed = 0| D8["[LKM-DENY]"]
WATCH_P -.->|pin exists at startup| D6["[WATCHDOG]"]
end
classDef kernbox fill:#fdf2f8,stroke:#9333ea,stroke-width:2px,color:#581c87;
classDef userbox fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1e3a8a;
classDef alertbox fill:#fff7ed,stroke:#ea580c,stroke-width:1px,color:#7c2d12;
classDef storage fill:#f8fafc,stroke:#64748b,stroke-width:1px;
class RING0 kernbox;
class RING3 userbox;
class D1,D2,D3,D4,D5,D6,D7,D8,D9 alertbox;
class RB_S,RB_N,RB_L,PROC storage;
SILENT ist ein Urteil auf Kanalebene, nicht pro Prozess. Es wird ausgelöst, wenn:
sched-Kanal tot: max(sched_last) über alle Registry-Einträge ist länger als CHANNEL_DEAD_NANOS veraltet, während /proc nicht leer ist. Der Nicht-leer-/proc-Wächter ist die Lebendigkeitsgarantie: Auf jedem laufenden Linux-System ist /proc nicht leer (init, Kernel-Threads, Kworker), und der Scheduler ist niemals wirklich im Leerlauf (Timer-Ticks, I/O-Waiter, RCU-Callbacks). Ein veraltetes max(sched_last) auf einem nicht leeren System bedeutet, dass der Kanal gestorben ist – nicht, dass das System im Leerlauf ist.
NMI-Kanal tot: Kein Heartbeat-Ereignis (event_type=0) ist seit mehr als 5 Sekunden aus dem NMI-Ringpuffer eingetroffen. Das bedeutet, dass das NMI-Perf-Event aufgehört hat zu feuern – entweder wurde das Perf-Event getrennt oder die PMU wurde umprogrammiert.
Beide Prüfungen haben eine 30-Sekunden-Abklingzeit, um Alarmfluten zu vermeiden.
[HOOK])SPiCa liest /proc über zwei unabhängige Mechanismen bei jedem Tick:
Roh-Syscall getdents64 (read_tgids()) – Rusts std::fs::read_dir ruft dies direkt über libc::syscall(SYS_getdents64, ...) auf, wobei alle libc-Funktions-Hooks umgangen werden. LD_PRELOAD-Rootkits können diesen Pfad nicht abfangen.
libc readdir (read_tgids_libc()) – ruft libc::opendir / libc::readdir über FFI auf, was durch den dynamischen Linker geht und vollständig durch /etc/ld.so.preload-Hooks abfangbar ist.
Wenn getdents64 PIDs sieht, die libc readdir nicht sieht, hängt ein Userspace-Rootkit readdir ein, um Prozesse zu verstecken. SPiCa löst [HOOK] aus:```
[HOOK] 3 PID(s) hidden from libc readdir — LD_PRELOAD rootkit suspected
Diese Technik verwandelt SPiCa's Kernel-Level-Privileg in eine aktive Erkennung von Userspace-Rootkits. Der rohe Syscall-Pfad ist die Grundwahrheit – kein Userspace-Hook kann ihn blenden. Der libc-Pfad ist die "wahrgenommene" Sicht, die ein normales Werkzeug (ps, ls) sehen würde. Die Diskrepanz ist ein eindeutiger Beweis für Library-Interception.
**Von `[HOOK]` erkannte Rootkits:**
| Rootkit | Versteckmechanismus | Erkannt |
|---------|---------------------|---------|
| Symbiote | Parasitäres LD_PRELOAD, hookt `readdir` | Ja (wenn aktiv versteckt) |
| JynxKit | LD_PRELOAD, `MAGIC_GID`-Verstecken via `readdir` | Ja |
| Azazel | LD_PRELOAD, `readdir` + `stat`-Hooks | Ja |
| Medusa/OrBit | LD_PRELOAD, `readdir` + Credential-Diebstahl | Ja |
**Getestet gegen echten Symbiote:** Eine lebende Symbiote-Probe (SHA256 `f55af21f...`, MalwareBazaar) wurde via `/etc/ld.so.preload` auf einer Ubuntu 24.04 VM bereitgestellt. Die Probe hookt `readdir`, `readdir64`, `stat`, `fstatat`, `pam_authenticate`, `pcap_loop`, `recvmsg`, `fopen`, `read`, `execve`. Wenn der LD_PRELOAD-Hook aktiv eine PID vor `readdir` versteckt, feuert SPiCa `[HOOK]` innerhalb eines Tick-Zyklus (<1 s). Wenn Symbiote geladen, aber passiv ist (nicht aktiv versteckt), erzeugt SPiCa null Fehlalarme.
### Gnadenfrist
Ein 50 ms Gnadenfenster verhindert Fehlalarme bei neu gegabelten Prozessen. Kanalweite SILENT-Prüfungen haben eine 5-Sekunden-Gnadenfrist (die `NMI_FIRST_TICK`-Referenz des NMI-Programms), damit Startlatenz keine falschen TAMPER-Alarme erzeugt.
---
## 6. Durch den Verifier begrenzte Adressgeheimhaltung
### Definition
**Durch den Verifier begrenzte Adressgeheimhaltung** ist eine rechnerische Sicherheitseigenschaft, die unter dem eBPF-eingeschränkten Gegnermodell gilt. Sie besagt:
> Gegeben einen Gegner, der eBPF-Programme laden kann (CAP\_BPF), aber durch den BPF-Verifier eingeschränkt ist (beschränkte Schleifen, eingeschränkte Helfer, kein beliebiges Kernel-Schreiben), ist die Kernel-Adresse des `.bss`-Datenabschnitts eines BPF-Programms rechnerisch nicht auffindbar.
### Die vier Mechanismen
Die Eigenschaft ist eine Zusammensetzung aus vier unabhängigen Barrieren:
1. **kASLR** — Kernel Address Space Layout Randomization randomisiert den Kernel-Heap-Bereich, in dem BPF-Map-Speicher (einschließlich `.bss`-Backing-Maps) zugewiesen wird. Die genaue Adresse ändert sich bei jedem Neustart.
2. **Verifier-Anweisungsgrenze** — Der BPF-Verifier begrenzt die Gesamtzahl der Anweisungen pro Programm auf ~1 Million und erfordert nachweislich terminierende Schleifen. Ein scanning-Programm eines Gegners kann pro Ladevorgang höchstens N ≈ 10^6 Adressen über `bpf_probe_read_kernel` abfragen. Angesichts eines kASLR-randomisierten Suchraums ist die Erfolgswahrscheinlichkeit pro Programm vernachlässigbar.
3. **LSM-Map-Zugangstor** (siehe [§7](#7-lsm-map-access-gate)) — Ohne das Tor könnte ein Gegner BPF-Maps nach ID aufzählen (`BPF_MAP_GET_NEXT_ID` → `BPF_MAP_GET_FD_BY_ID` → `BPF_MAP_LOOKUP_ELEM`) und `.bss`-Inhalte direkt lesen. Das Tor entfernt diesen einfachen Pfad und zwingt den Gegner in den verifier-begrenzten Memory-Scan-Pfad.
4. **`.bss`-Platzierung** — `.bss`-Globals erscheinen nicht in benannten Map-Enumerationsschnittstellen mit menschenlesbaren Namen. Die interne `.bss`-Backing-Map wird von libbpf/aya als `.bss` benannt, aber ihre Inhalte (die eigentlichen globalen Variablen) sind nur zugänglich, wenn man den Map-Descriptor (fd) hat. Der Map-fd erfordert entweder die fd-Tabelle des ladenden Prozesses oder die Map-ID (durch das Tor blockiert).
### Formale Argumentationsstruktur
---```
Adversary's goal: locate SPiCa's .bss in kernel virtual memory
Search space: kernel heap region, kASLR-randomized (~2^30 entropy)
Path A — map enumeration (blocked):
BPF_MAP_GET_NEXT_ID → BPF_MAP_GET_FD_BY_ID → BPF_MAP_LOOKUP_ELEM
→ blocked by LSM gate (§7) for non-SPiCa processes
Path B — memory scan (bounded):
bpf_probe_read_kernel(addr, buf, len) — fault-safe, accepts any addr
Per-program capacity: N ≈ 10^6 probes (instruction limit)
Success probability per load: N / |search space| ≈ 10^6 / 2^30 ≈ 10^-24
Can reload programs, but: each load is a syscall (observable), and
the target address doesn't change between loads (same boot session)
Path C — timing side channel (theoretical):
bpf_probe_read_kernel timing varies on fault vs success
Binary search over the heap could reduce scan complexity to O(log |A|)
→ O(30) probes, well within instruction budget
LIMITATION: must be acknowledged. Mitigated by rate-limiting BPF_PROG_LOAD
via the LSM gate (future work).
Die meiste BPF-Sicherheitsforschung betrachtet den Verifier als Angriffsfläche (Verifier-Fehler, Unzuverlässigkeit, JIT-Spray). Diese Arbeit kehrt den Rahmen um: Die Einschränkungen des Verifiers sind ein defensives Primitiv, das den Suchraum des Angreifers begrenzt. Die Geheimhaltung ist nicht kryptografisch (es ist kein AES); sie ist rechnerisch — begrenzt durch das, was der Verifier dem Angreifer zu berechnen erlaubt.
bpf_probe_read_kernel ist timing-beobachtbar (Fehler vs. Erfolg benötigt unterschiedliche Zyklen). Eine timing-basierte binäre Suche über den Heap ist theoretisch effizienter als linearer Scan. Die Anweisungsgrenze begrenzt die Sonden pro Programm, aber binäre Suche könnte die Sondenanzahl auf O(log |A|) ≈ 30 reduzieren. Dies ist eine reale Einschränkung. Abhilfe: Ratenbegrenzung von BPF_PROG_LOAD über das LSM-Gate..bss-Globale werden durch eine interne BPF-Map unterstützt. Jeder Prozess mit CAP_BPF kann alle Maps auf dem System aufzählen:```
bpf(BPF_MAP_GET_NEXT_ID, …) → iterate all map IDs
bpf(BPF_MAP_GET_FD_BY_ID, id) → get fd to any map
bpf(BPF_MAP_LOOKUP_ELEM, fd, key, value) → read map contents
Gegen den eBPF-Angreifer (der keine Syscalls aufrufen kann) ist `.bss` versteckt. Gegen Userspace-Root ist es eine Standard-Map – vollständig lesbar. Das LSM-Tor schließt diese Lücke.
### Die Verteidigung
Ein BPF-LSM-Hook auf dem `bpf`-Syscall überprüft Map-Zugriffsbefehle:```
hook = "bpf"
read cmd (arg 0)
if cmd == BPF_MAP_GET_FD_BY_ID:
read map_id from userspace bpf_attr (bpf_probe_read_user)
if map_id matches any of SPiCa's stored IDs (.bss):
if caller_tgid != SPICA_PID:
return -EPERM
Map-IDs werden vom Userspace-Loader direkt nach dem Laden des Programms in .bss geschrieben, bevor der LSM-Hook aktiviert wird (gleiches Timing-Muster wie der vorhandene sc_gate für die LKM-Sperrung). Kein BTF erforderlich — cmd und map_id stammen aus Syscall-Argumenten, nicht aus Kernel-Strukturen.
Chirurgisch: blockiert nur den Zugriff auf die spezifischen Map-IDs von SPiCa. Die BPF-Tools anderer Prozesse (tcpdump, bpftrace, bcc) greifen auf ihre eigenen Maps zu und sind nicht betroffen.
/proc/pid/mem — Ein anderer Root-Prozess könnte den Prozessspeicher von SPiCa direkt über ptrace auslesen und so den BPF-Syscall vollständig umgehen. Dies ist eine grundlegende Einschränkung: Man kann sich nicht gegen einen Prozess mit derselben Berechtigungsstufe schützen, der den Speicher über /proc/pid/mem ausliest. Abwehr: Off-Host-Log-Versand (wenn der Angreifer lokalen Speicher lesen kann, kann er auch lokale Warnungen unterdrücken – nur Fernprotokollierung hilft).Auf Hosts mit TPM wird der 64-Bit-XOR-Verschlüsselungsschlüssel über TPM2_GetRandom mittels der tss-esapi-Crate bezogen (direkter Bibliotheksaufruf an libtss2-esys). Der Schlüssel durchläuft keine Pipe, keine Unterprozess-Befehlszeile und keine Festplatte. Er existiert nur in der TPM-Hardware und im flüchtigen Kernel-Speicher (.bss als zugehörige Map) für die Dauer des SPiCa-Prozesses.
Auf Hosts ohne TPM (VMs ohne vTPM, Container, Rechner ohne TPM-Chip) greift SPiCa auf einen pro Build generierten Kompilierzeitschlüssel zurück, der vom Build-Skript aus /dev/urandom erzeugt wird. Der Fallback ist beim Start deutlich sichtbar mit einem Warnbanner.
.bss-PlatzierungDie folgenden Werte befinden sich im .bss-Bereich des eBPF-Programms und werden vom Userspace über set_global() geschrieben, bevor ein Programm geladen wird:
Benannte BPF-Maps sind über die Map-Iterationsschnittstelle des bpf()-Syscalls aufzählbar (blockiert durch das LSM-Gate — siehe §7); .bss-Globals werden über dieselbe interne Map zugegriffen, sind aber nicht separat benannt oder pinnbar.
Alle ProcessInfo-Felder werden mit dem 64-Bit-BASE_KEY XOR-gefaltet, bevor sie in Ringpuffer geschrieben werden. Das event_type-Feld wird absichtlich unverschleiert gelassen, damit der Userspace es als Sentinel lesen kann, bevor der Rest entschleiert wird.
Dies ist Verschleierung gegen Lese-Lecks, keine Verschlüsselung gegen einen fähigen Angreifer. XOR mit einem 8-Byte-sich-wiederholenden Schlüssel ist strukturell anfällig für Known-Plaintext: Vorhersagbare comm-Werte ("bash", "systemd", "kthreadd") XOR mit dem Chiffretext stellen Schlüsselbytes direkt wieder her. Die Verteidigung ist angemessen für den eBPF-Angreifer (der den Ringpuffer nicht einfach lesen kann – dies erfordert dieselbe Fähigkeitslücke, die .bss schützt), nicht für einen Angreifer mit Binärzugriff und Ringpuffer-Abfangen.
Status: Designphase. Noch nicht implementiert. Dieser Abschnitt dokumentiert die Zielarchitektur für das Forschungspapier.
PCR-gebundenes TPM-Sealing verschiebt die Schlüsselverteilungsgrenze von der Softwareebene ins Silizium. Der Schlüssel wird zur Installationszeit gegen erwartete Platform Configuration Register (PCR)-Werte gesiegelt und kann nur entsiegelt werden, wenn der Startzustand des Systems mit den erwarteten Messungen übereinstimmt.
Während des Starts messen Firmware, Bootloader und Kernel kritische Komponenten in PCRs:
Hinweis: Die PCR-Zuordnung ist startkettenabhängig. GRUB misst den Kernel in PCR 4; systemd-stub misst in PCR 4 und die Befehlszeile in PCR 8. Die Sealing-Richtlinie muss mit der Ziel-Startkette übereinstimmen.
Siegeln (Installationszeitpunkt): Der 64-Bit-Schlüssel wird unter Verwendung von TPM2_Create mit einer TPM2_PolicyPCR-Sitzung gegen erwartete PCR-Werte gesiegelt. Der gesiegelte Blob wird auf der Festplatte gespeichert. Er ist mit dem internen Schlüssel des TPM verschlüsselt und kann nur entschlüsselt werden, wenn die angegebenen PCRs übereinstimmen.
Entsiegeln (frühes Booten, Initramfs-Phase): Bevor irgendein nicht vertrauenswürdiger Userspace-Code ausgeführt wird, fordert der Initramfs-Hook von SPiCa TPM2_Unseal an. Wenn die aktuellen PCR-Werte mit der Siegelrichtlinie übereinstimmen, gibt das TPM den Schlüssel frei.
Injizieren: Der Schlüssel wird vor dem Laden der Programme über set_global() in .bss geschrieben.
Die Falle: Wenn ein Angreifer den Kernel (PCR 4-Fehlanpassung) modifiziert, das Initramfs (PCR 9-Fehlanpassung) ausgetauscht oder die Secure Boot-Richtlinie (PCR 7-Fehlanpassung) geändert hat, weichen die PCR-Hashes voneinander ab. Das TPM verweigert die Entsiegelung und SPiCa verhält sich ausfallsicher — es weigert sich zu starten, anstatt blind mit einem kompromittierten Schlüssel zu laufen.
Für das Forschungspapier die starke Richtlinie präsentieren und den Neuversiegelungs-Kompromiss in den Einschränkungen diskutieren.
Die beiden Mechanismen ergänzen sich:
.bss, in dem der Schlüssel lebt, nicht lokalisieren oder lesen kann.Keiner der beiden Mechaniken ist allein ausreichend. PCR-Siegeln hilft nicht, wenn der Schlüssel zur Laufzeit kompromittiert wird (Map-Aufzählung). Adressgeheimhaltung hilft nicht, wenn das System kompromittiert wurde, bevor SPiCa gestartet ist (feindliches Initramfs).
spica_lsm_modblock, das Map-Zugangsgate) werden zur Laufzeit geladen und werden NICHT in einem PCR gemessen. Ein Rootkit, das sie nach dem Booten ablöst, wird vom PCR-Siegeln nicht erfasst. Was das erfasst, ist der NMI-Heartbeat (der erkennt, dass das Erkennungssystem selbst aufgehört hat zu laufen).SPiCa ist die letzte Durchsetzungsschicht. Es ergänzt ein ordnungsgemäß konfiguriertes System, ersetzt jedoch nicht die darüber liegenden Schichten.```mermaid
flowchart TD
SB["UEFI Secure Boot
─────────────────────────────
Verifies bootloader signature against
the UEFI db certificate store
Measured to PCR 7"]
MS["Kernel Module Signing
─────────────────────────────
CONFIG_MODULE_SIG_FORCE=y
Kernel rejects unsigned .ko at load time"]
IMA["IMA, Integrity Measurement Architecture
─────────────────────────────
Measures file hashes to TPM PCR10
Appraise policy blocks non-matching signatures"]
SPICA["SPiCa
─────────────────────────────
LSM gate: blocks LKM loads + map enumeration
sched_switch + NMI integrity channels
Differential detection: DKOM · GHOST · TAMPER · SILENT · DUPE
PCR-bound key sealing (design phase)"]
SB -->|"boot chain verified"| MS
MS -->|"signed modules only"| IMA
IMA -->|"measured + appraised"| SPICA
classDef firmware fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f
classDef kernel fill:#fdf2f8,stroke:#9333ea,stroke-width:2px,color:#581c87
classDef spica fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1e3a8a
class SB firmware
class MS,IMA kernel
class SPICA spica
SPiCa überprüft beim Start alle vier Schichten und gibt deren Status aus.
---
## 11. Der BTF-Bug-Vorfall
### Was passiert ist
Während Tests auf Ubuntu (neuester Kernel) verursachte eine BTF-Inkompatibilität, dass das `sched_switch`-Tracepoint-Programm zwar erfolgreich angebunden wurde, aber **niemals ein einziges Ereignis auslöste.** Der `attach()`-Systemaufruf gab `Ok(())` zurück, also arbeitete SPiCa normal weiter – aber der Sched-Ringbuffer blieb leer. Es wurden keine Erkennungsalarme ausgelöst, da der Erkennungs-Engine keine Scheduling-Daten vorlagen. **SPiCa lief blind, ohne jeglichen Hinweis auf einen Fehler.**
Dies ist der schlimmste Fehlermodus für ein Sicherheitstool: stille Blindheit.
### Warum es nicht erkannt wurde
Die ursprüngliche Erkennungs-Engine argumentierte **prozessbezogen**: Jeder Prozessdatensatz hatte `sched_last`- und `nmi_last`-Zeitstempel, und die Lebendigkeit wurde pro Datensatz berechnet. Als sched\_switch global ausfiel:
1. `sched_live` wurde für jeden Datensatz auf false gesetzt (keine neuen Sched-Ereignisse → alle `sched_last`-Werte altern aus)
2. Das prozessbezogene `TAMPER`-Prädikat (`in_proc && nmi_live && !sched_live`) konnte ausgelöst werden, erforderte jedoch, dass `nmi_live` *kontinuierlich* für 2 Sekunden gehalten wurde. NMI sampelt spärlich (10M Zyklusperiode), sodass `suspect_since` aufgrund von Sampling-Jitter ständig zurückgesetzt wurde und nie reifte.
3. Das prozessbezogene `SILENT`-Prädikat erforderte `sched_live`, das jetzt für alles false war – das Prädikat invertierte unter genau der Bedingung, die es erkennen sollte.
4. Es gab **keine kanalweite Lebendigkeitsprüfung** – keinen Mechanismus wie „Hat sched ÜBERHAUPT jemals gefeuert?" oder „Ist max(sched_last) veraltet?".
Zusätzlich wurde eine Zeitbasen-Inkonsistenz entdeckt: `sched_last` speicherte `bpf_ktime_get_ns()` (Kernel-Boot-Nanosekunden), während `evaluate()` gegen `nanos_since_startup()` (prozesslokale Nanosekunden) verglich. Das `wrapping_sub` dieser unterschiedlichen Zeitbasen erzeugte riesige Werte, sodass alle Lebendigkeitsprädikate trivial false waren. Die Erkennungslogik hatte in der Produktion nie korrekt funktioniert – der BTF-Bug maskierte dies nur, indem er verhinderte, dass Ereignisse überhaupt ankamen.
### Wie das Redesign das Problem behebt
| Problem | Lösung |
|---|---|
| Keine kanalweite Lebendigkeitsprüfung | `evaluate()` berechnet jetzt `max(sched_last)` und löst `[SILENT]` aus, wenn veraltet, während `/proc` nicht leer ist |
| Prozessbezogenes TAMPER ist nie gereift (Jitter-Reset) | TAMPER ist jetzt ein direktes Signal vom NMI-Programm (`.bss`-Heartbeat-Vergleich), kein prozessbezogener FSM mit Schwellwerten |
| Prozessbezogenes SILENT invertierte bei sched-Ausfall | SILENT ist jetzt kanalweit, berechnet aus Aggregaten, nicht aus pro-Datensatz-Prädikaten |
| Zeitbasen-Inkonsistenz | `sched_last` speichert jetzt prozesslokale Zeit (`now`-Parameter), nicht `bpf_ktime_get_ns()` |
| NMI war redundante Beobachtung | NMI ist jetzt ein Integritätsprüfer – er erkennt sched-Tod direkt über `.bss`-Heartbeat |
---
## 12. Bekannte Einschränkungen & Angriffsfläche
### Innerhalb des eBPF-Bedrohungsmodells
| Angriff | Status | Anmerkungen |
|---|---|---|
| **Manipulation des Ringbuffer-Consumer-Zeigers** | **Erkannt** (SILENT) | `max(sched_last)` wird veraltet → SILENT. `SCHED_HEARTBEAT` läuft weiter (Programm läuft) → kein falsches TAMPER. |
| **Perf-Event-Struct-DKOM** (Feld `state` auf Null setzen) | **Erkannt** (SILENT) | NMI hört auf zu feuern → keine Heartbeat-Ereignisse → SILENT auf NMI-Kanal |
| **`task_struct`-Feld-Spoofing** (pid/tgid überschreiben) | **Teilweise** (DUPE) | Wenn die gespoofte TGID mit einem vorhandenen Datensatz mit einer anderen `start_time_ns` übereinstimmt, löst DUPE aus. Wenn `start_time_ns` ebenfalls gespooft wird, schlüpft dies durch. |
| **eBPF-Programm-Abhängen** (aus funcs-Array entfernen) | **Erkannt** (TAMPER) | NMI-Heartbeat friert ein → TAMPER |
| **Bytecode direkt gepatcht** | **Nicht erkannt** | Erfordert beliebigen Kernel-Write (LKM-Ebene) – außerhalb des eBPF-Bedrohungsmodells |
| **Timing-Seitenkanal auf `bpf_probe_read_kernel`** | **Theoretisches Risiko** | Binäre Suche über den Heap könnte `.bss` in ~30 Sondierungen finden. Siehe [§6](#6-verifier-bounded-address-secrecy) Einschränkungen. |
### Außerhalb des eBPF-Bedrohungsmodells (anerkannt, nicht gelöst)
| Angriff | Status | Anmerkungen |
|---|---|---|
| **IDT/NMI-Handler-Hook** | **Die lebende Bombe** | Architektonisch vollständiger Umgehungsweg. Fast sicherer Kernel-Panic. |
| **Dual-Kanal-Unterdrückung** | **Die lebende Bombe** | Gleichzeitige Unterdrückung von sched + NMI. Erbt die Instabilität des NMI-Hookings. |
| **Verifier-Exploit** | **Nicht-Ziel** | Eskaliert zu beliebiger Kernel-Code-Ausführung. Separate Bedrohungsklasse. |
| **ptrace / `/proc/pid/mem`** | **Grundlegende Einschränkung** | Speicherlesen mit gleichen Rechten. Nur Off-Host-Log-Versand hilft. |
| **Consumer-Zeiger + Heartbeat-Patch** | **LKM-Level** | Wenn der Angreifer beliebigen Kernel-Write hat, kann er sowohl den Consumer-Zeiger vorrücken als auch gefälschte Heartbeats schreiben. Aber beliebiger Kernel-Write = LKM-Ebene = außerhalb des Bedrohungsmodells. |
---
## 13. Build & Ausführung
### Voraussetzungen
- Linux-Kernel >= 5.15 mit `CONFIG_DEBUG_INFO_BTF=y` (nur für den LSM-Hook)
- Für Modulblockierung: `CONFIG_BPF_LSM=y` und `lsm=bpf` in Kernel-Boot-Parametern
- TPM 2.0-Chip + `tpm2-tss`-Bibliothek (optional; fällt mit sichtbarer Warnung zurück)
- Nightly Rust-Toolchain
> **Hinweis:** Der `generate-vmlinux`-Schritt ist **nicht mehr erforderlich**. Die eBPF-Programme verwenden traditionelle Tracepoint-Offsets und `.bss`-Globale – keine CO-RE/BTF-Strukturnavigation ist nötig. Der Befehl `generate-vmlinux` aus xtask wird für zukünftige Verwendung beibehalten, ist aber nicht Teil der Build-Pipeline.
Stellen Sie sicher, dass BPF LSM aktiv ist: `cat /sys/kernel/security/lsm` sollte `bpf` enthalten.
### Setup```shell
make install-deps # system packages + nightly Rust (pacman/apt/dnf auto-detected)
make install-tools # bpf-linker
make build # compiles eBPF + userspace (no vmlinux generation needed)
make run # sudo ./target/release/spica
### Installation in initramfs (Schutz beim frühen Systemstart)```shell
sudo make install # spica install — auto-detects Debian (initramfs-tools) or Fedora (dracut)
sudo insmod some_module.ko # should fail with EPERM (gate locked) cat /sys/kernel/security/lsm # should contain 'bpf' ls /sys/fs/bpf/spica_watchdog # exists if previous run was killed ungracefully
### Entwicklung (macOS oder Linux)
Die eBPF-Programme können nicht unter macOS ausgeführt werden. `cargo check` und Unit-Tests für die Erkennungslogik funktionieren; eine vollständige Laufzeitüberprüfung erfordert Linux.```shell
make check # cargo check for workspace
make check-ebpf # cargo check for the eBPF crate (bpfel-unknown-none)
make test # unit tests for detection FSM, key derivation, obfuscation
GetRandom-TPM-Nutzung durch vollständige Hardware-Vertrauenswurzel. Siehe §9.bpf-Syscall, der die Map-Aufzählung des internen Zustands von SPiCa durch Nicht-SPiCa-Prozesse blockiert. Siehe §7.BPF_PROG_LOAD-Ratenbegrenzung — Das LSM-Gate erweitern, um BPF-Programmladungen von Nicht-SPiCa-Prozessen zu drosseln und das Timing-Seitenkanal-Risiko bei der .bss-Ermittlung zu mindern. Siehe §6 Einschränkungen.siphasher-Abhängigkeit befindet sich bereits im Baum für das Integritätstoken).SPiCa Engine Lizenz: MIT OR Apache-2.0 (Workspace). Das eBPF-Programm exportiert eine GPL-Lizenz über das _license-Static – dies ist eine Kernel-Anforderung für eBPF-Programme, die GPL-lizenzierte Helper verwenden, und gilt nur für das geladene eBPF-Bytecode, nicht für das Userspace-Binary.
Character Attribution: „Hatsune Miku“ und die zugehörigen Charaktergrafiken sind urheberrechtlich geschütztes Eigentum der Crypton Future Media, INC. (www.piapro.net). Dieses Projekt ist ein unabhängiges, nicht-kommerzielles Forschungswerkzeug, nicht verbunden mit Crypton Future Media. Charakter verwendet unter der Piapro Character License (PCL).
Der Projektname SPiCa ist inspiriert von dem Originalsong von Toku-P.
| 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) |
| 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 |
| Warnung | Bedingung | Was erkannt wird |
|---|
[DKOM] | Von Kernel geplante TGID fehlt in /proc für >2 s | Direct Kernel Object Manipulation – Prozess vor Userspace-Aufzählung versteckt |
[GHOST] | TGID in /proc vorhanden, aber nie von sched_switch gesehen für >5 s | /proc-Spoofing – gefälschter Eintrag eingefügt, um DKOM-Prüfungen zu umgehen |
[TAMPER] | NMI erkennt eingefrorenen sched_switch-Heartbeat (event_type=1) | Tracepoint getrennt, unterdrückt oder BTF/Attach-Fehler |
[SILENT] | max(sched_last) über gesamte Registry veraltet, während /proc nicht leer ist; oder NMI-Heartbeat fehlt für >5 s | Kanalverlust auf Kanalebene – sched- oder NMI-Kanal ist tot |
[DUPE] | Gleiche TGID, unterschiedliche start_time_ns zwischen Ereignissen | task_struct-Feld-Spoofing – Rootkit patcht tgid, um sich als legitimer Prozess auszugeben |
[HOOK] | PIDs sichtbar über rohes getdents64, aber fehlend in libc readdir | LD_PRELOAD-Rootkit – Benutzerspace-Bibliotheksabfang, das Prozesse vor ps, ls und anderen Tools verbirgt (Symbiote, JynxKit, Azazel, Medusa/OrBit) |
[WATCHDOG] | /sys/fs/bpf/spica_watchdog-Pin existiert beim Start | Vorherige Instanz wurde unfreiwillig beendet (SIGKILL, OOM, Absturz) |
[LKM-ALLOW] | READING_MODULE abgefangen, während das Tor offen ist (Boot-Fenster) | Audit-Eintrag: Modul geladen, bevor das Tor geschlossen wurde |
[LKM-DENY] | READING_MODULE abgefangen, während das Tor geschlossen ist | insmod/modprobe nach Initialisierung blockiert |
| Global | Zweck | Geschrieben von |
|---|
BASE_KEY | XOR-Verschlüsselungsschlüssel | Userspace zur Ladezeit |
SPICA_PID | Eigene TGID von SPiCa (Watchdog) | Userspace zur Ladezeit |
SCHED_HEARTBEAT | sched_switch-Lebendigkeitszeitstempel | sched_switch-Programm bei jedem Aufruf |
NMI_LAST_HB | NMI-Aufzeichnung des letzten sched-Heartbeats | NMI-Programm bei jeder Prüfung |
NMI_FIRST_TICK | Erster NMI-Aufruf ktime (Schonfrist) | NMI-Programm beim ersten Aufruf |
NMI_LAST_EMIT | Drosselung: letzter Ereignisausgabe-ktime | NMI-Programm bei jeder Ausgabe |
| PCR | Was gemessen wird | Stabilität |
|---|
| PCR 4 | Bootloader-Code + Kernel-Image (GRUB misst beides) | Ändert sich bei Kernel-Update |
| PCR 5 | GPT/MBR-Partitionstabelle, Startkonfiguration | Stabil über Updates hinweg |
| PCR 7 | Secure Boot-Richtlinie (SI-Richtlinie, MOK, db/dbx) | Stabil über Kernel-Updates hinweg |
| PCR 8 | Kernel-Befehlszeile (systemd-stub-Messungen) | Stabil, sofern sich die Befehlszeile nicht ändert |
| PCR 9 | Initramfs (GRUB misst initrd hier) | Ändert sich bei Initramfs-Update |
| PCR 10 | IMA-Messliste | Ändert sich, wenn ausführbare Dateien gemessen werden |
| Richtlinie | Gesiegelt gegen | Stärke | Betriebskosten |
|---|
| Stark | PCR 4 + 7 + 9 + 10 | Erkennt Kernel-, Initramfs-, Secure Boot- und IMA-Änderungen | Erneutes Siegeln nach jedem Kernel-/Initramfs-Update |
| Ausgewogen | PCR 7 + 10 | Erkennt Secure Boot- und IMA-Bewertungsänderungen; stabil über Kernel-Updates hinweg | Erneutes Siegeln nur bei Änderungen der Secure Boot- oder IMA-Richtlinie |
| Minimal | Nur PCR 7 | Erkennt nur Änderungen des Secure Boot-Status | Sehr stabil; schwächste Bindung |
| Begriff | Definition |
|---|
| BPF | Berkeley Packet Filter – In-Kernel-Ausführungsengine für sandboxierte Programme. Modernes BPF (eBPF) geht über Pakete hinaus und umfasst Tracing, Sicherheit und Netzwerk. |
| BTF | BPF Type Format – Kernel-Debug-Informationen, die CO-RE-Programmen (Compile Once, Run Everywhere) erlauben, Kernel-Strukturen portabel zu navigieren. |
| CO-RE | Compile Once, Run Everywhere – BPF-Technik, die BTF nutzt, um portable Programme zu schreiben, die sich an verschiedene Kernelversionen anpassen. |
| DKOM | Direct Kernel Object Manipulation – Rootkit-Technik, bei der ein Prozess aus der Kernel-Liste entfernt wird, um ihn vor /proc zu verstecken. |
| fmod_ret | BPF-Programmtyp, der den Rückgabewert einer Kernel-Funktion über den BPF-Trampolin modifiziert. |
| freplace | BPF-Programmerweiterung – hängt an eine bestimmte (Unter-)Funktion eines anderen BPF-Programms an und fängt dessen Ausführung ab. |
| funcs array | Das Funktionszeiger-Array in einer Kernel-Tracepoint-Struktur, das Callback-Funktionen (einschließlich BPF-Programme) enthält, die beim Auslösen des Tracepoints aufgerufen werden. |
| IDT | Interrupt Descriptor Table – CPU-Struktur, die Interruptvektoren auf Handler-Funktionen abbildet. Das Hooking des NMI-Eintrags erfordert das Patchen der IDT. |
| kASLR | Kernel Address Space Layout Randomization – randomisiert Kernel-Code/Daten-Adressen bei jedem Start, um Ausbeutung zu erschweren. |
| NMI | Non-Maskable Interrupt – Hardware-Interrupt, der nicht durch Software deaktiviert werden kann (cli). Wird von Perf-Zählern zur hardwarenahen Beobachtung verwendet. |
| PCR | Platform Configuration Register – TPM-Register, das Messungen (Hashes) von Boot-Komponenten akkumuliert. Kann nicht zurückgesetzt werden (außer Neustart), nur erweitert. |
| PMU | Performance Monitoring Unit – Hardware-Zähler in der CPU, die Ereignisse (Takte, Cache-Fehler usw.) zählen und bei Schwellenwerten Interrupts (NMIs) auslösen können. |
| TPM | Trusted Platform Module – kryptografischer Koprozessor, der hardwarebasierte Schlüsselspeicherung, Zufallszahlenerzeugung und Messungsattestierung bereitstellt. |
| Verifier | Der BPF-Verifier – Kernel-Komponente, die BPF-Programme vor dem Laden statisch analysiert, um sicherzustellen, dass sie terminieren und nicht auf unsicheren Speicher zugreifen. |