
Rilevatore di rootkit Linux basato su eBPF che utilizza analisi multi-canale a vista incrociata (sched_switch, NMI, /proc) per rilevare DKOM, manomissioni dei tracepoint e occultamento di processi con verifica dell'integrità a livello hardware.
Integrità dei Processi di Sistema e Analisi Cross-view
"Canterò, quindi risplendi, SPiCa..."
SPiCa è un rilevatore di rootkit Linux basato su eBPF scritto in Rust. Il nome deriva dalla canzone di Hatsune Miku SPiCa e dalla stella a cui fa riferimento — Spica (Alpha Virginis), il punto più luminoso della Vergine. Ciò che a occhio nudo appare come una singola stella è in realtà una binaria spettroscopica: due stelle in orbita reciproca, indistinguibili come oggetti separati senza misurarne lo spettro. SPiCa applica lo stesso principio all'osservazione del kernel: canali indipendenti multipli misurano lo stesso stato del kernel da meccanismi fisicamente distinti, e un rootkit che ne sopprime uno viene esposto dagli altri.
Avvertenza: Parti significative di questo codice sono state generate o rifattorizzate con assistenza GLM. Sono stati applicati test rigorosi e progettazione iterativa, ma rivedere il codice per sicurezza e prestazioni prima dell'uso in produzione.
SPiCa è progettato per sconfiggere l'avversario vincolato da eBPF — un attaccante con privilegi elevati (CAP_BPF o CAP_SYS_ADMIN) che carica un programma eBPF privilegiato nel kernel. Questo avversario è fondamentalmente più debole di un rootkit LKM perché il verificatore BPF impone vincoli severi:
| Vincolo | Effetto sull'avversario |
|---|---|
| Cicli limitati (~1 milione di istruzioni per programma) | Non può scansionare tutta la memoria del kernel |
| Sicurezza dei puntatori (il dereferenziamento diretto richiede un target provabilmente valido e limitato) | Non può seguire puntatori arbitrari |
| Helper limitati (allowlist per tipo di programma) | Non può chiamare kmalloc, vmalloc o qualsiasi funzione di allocazione del kernel |
Nessuna scrittura arbitraria nel kernel (bpf_probe_write_kernel fortemente limitato) | Non può modificare task_struct, array di funzioni tracepoint o bytecode del programma BPF |
| Accesso alle mappe limitato ai riferimenti caricati | Non può accedere a mappe appartenenti ad altri programmi (a meno che non siano enumerate per ID — vedi §7) |
Con i moduli LKM bloccati dal gate LSM dopo l'avvio, questo avversario vincolato è la minaccia realistica rimanente. La macchina anti-evasione di SPiCa è calibrata per questa minaccia — ogni difesa è trasparente su cosa copre e cosa no.
init_module. SPiCa alza il livello bloccando il vettore LKM più facile, ma non pone un tetto all'avversario.SPiCa è il livello di ultima istanza in una pila di difesa in profondità, non un sostituto dei livelli superiori.
SPiCa esegue quattro programmi eBPF collegati a hook del kernel, più un motore di rilevamento in spazio utente che correla incrociata il loro output con la visione che il sistema ha di sé stesso (/proc).
| Canale | Meccanismo | Cosa lo sopprime | Costo della soppressione |
|---|---|---|---|
/proc | Enumerazione della directory procfs | DKOM (scollegare il processo dalla lista dei task) | Basso — tecnica di rootkit ben nota |
sched_switch | Tracepoint eBPF sullo scheduler | Scollegare il programma dall'array funcs; filtrare il contesto | Medio — richiede scrittura nel kernel nell'array funcs |
| NMI | Interrupt hardware del contatore di performance → Evento perf eBPF | Agganciare il gestore IDT/NMI | Panico del kernel quasi certo — interrupt hardware, non mascherabile |
La proprietà architetturale chiave: un rootkit non può sopprimere tutti e tre i canali simultaneamente senza che la soppressione stessa diventi rilevabile o destabilizzante. Sopprimere l'NMI richiede di patchare l'IDT (Interrupt Descriptor Table), che causa il panico sulla maggior parte dei kernel. Questa è la "bomba attiva" — l'unico percorso dell'attaccante verso la cecità totale è uno che probabilmente crasha il sistema.
sched sees it, /proc doesn't → DKOM NMI finds sched's heartbeat stopped → TAMPER sched goes globally silent, /proc != empty → SILENT
Ogni classe di rilevamento è un verdetto differenziale: una discrepanza tra due o più canali. Il motore di rilevamento è una funzione pura sul registro + snapshot /proc + timestamp dei canali — nessuna I/O, nessun effetto collaterale, completamente testabile tramite unit test.
### La riprogettazione dell'NMI: dall'osservazione all'integrità
Nel progetto originale, l'NMI era un secondo canale di osservazione dei processi che campionava la CPU e segnalava quale task era in esecuzione. Ciò era ridondante: sched\_switch osserva già lo scheduling, e l'NMI campionava gli stessi dati tramite un meccanismo diverso. La ridondanza costava circa 1000+ eventi ring-buffer/sec/CPU di dati di processo che per il 99.999% del tempo confermavano "sì, lo scheduler sta facendo ciò che fa lo scheduler."
Nell'architettura riprogettata, **l'NMI viene riutilizzato dall'osservazione dei processi alla verifica dell'integrità dei tracepoint.** Non segnala più quale processo è sulla CPU. Invece, verifica che `sched_switch` sia effettivamente in esecuzione leggendo un heartbeat condiviso `.bss`. Questo:
1. Elimina circa il 99% del traffico ring-buffer dell'NMI (eventi quasi zero in stato stazionario)
2. Rileva direttamente il distacco, la soppressione dei tracepoint e i fallimenti BTF/iniezione (il bug BTF originale — vedere [§11](#11-the-btf-bug-incident))
3. Viene eseguito da un interrupt hardware, al di fuori del percorso di dispatch dei tracepoint — immune a `bpf_override_return`, intercettazione kprobe e manipolazione di array di funzioni
4. Legge i globali `.bss` tramite accesso diretto alla memoria, non helper BPF — immune a `fmod_ret` sulle funzioni helper
---
## 3. Il canale di osservazione sched\_switch