
Detector de rootkit Linux baseado em eBPF usando análise multi-canal de visão cruzada (sched_switch, NMI, /proc) para detectar DKOM, adulteração de tracepoint e ocultação de processos com verificação de integridade em nível de hardware.
Integridade de Processos do Sistema e Análise de Visão Cruzada
"Vou cantar, então brilhe intensamente, SPiCa..."
SPiCa é um detector de rootkit Linux baseado em eBPF escrito em Rust. O nome vem da música de Hatsune Miku SPiCa e da estrela que ela referencia — Spica (Alpha Virginis), o ponto mais brilhante em Virgem. O que parece uma única estrela a olho nu é na verdade uma binária espectroscópica: duas estrelas em órbita mútua, indistinguíveis como objetos separados sem medir seus espectros. SPiCa aplica o mesmo princípio à observação do kernel: múltiplos canais independentes medem o mesmo estado do kernel a partir de mecanismos fisicamente distintos, e um rootkit que suprime um é exposto pelos outros.
Aviso: Partes significativas deste código foram geradas ou refatoradas com auxílio de GLM. Testes rigorosos e design iterativo foram aplicados, mas revise o código quanto à segurança e desempenho antes do uso em produção.
SPiCa foi projetado para derrotar o adversário restrito por eBPF — um atacante com privilégios elevados (CAP_BPF ou CAP_SYS_ADMIN) que carrega um programa eBPF privilegiado no kernel. Esse adversário é fundamentalmente mais fraco que um rootkit LKM porque o verificador BPF impõe restrições rígidas:
| Restrição | Efeito no adversário |
|---|---|
| Laços limitados (~1M instruções por programa) | Não pode escanear toda a memória do kernel |
| Segurança de ponteiros (desreferência direta requer alvo comprovadamente válido e limitado) | Não pode seguir ponteiros arbitrários |
| Helpers restritos (lista de permissão por tipo de programa) | Não pode chamar kmalloc, vmalloc ou qualquer função de alocação do kernel |
Nenhuma escrita arbitrária no kernel (bpf_probe_write_kernel fortemente restrita) | Não pode modificar task_struct, arrays de funções de tracepoint ou bytecode de programas BPF |
| Acesso ao mapa limitado às referências carregadas | Não pode acessar mapas pertencentes a outros programas (a menos enumerados por ID — veja §7) |
Com LKMs bloqueados pela porta LSM após a inicialização, esse adversário restrito é a ameaça realista restante. A maquinaria anti-evasão do SPiCa é calibrada para essa ameaça — toda defesa é honesta sobre o que cobre e o que não cobre.
init_module. SPiCa eleva o nível ao bloquear o vetor mais fácil de LKM, mas não limita o adversário superior.SPiCa é a camada de último recurso em uma pilha de defesa em profundidade, não um substituto para as camadas acima.
SPiCa executa quatro programas eBPF anexados a hooks do kernel, além de um motor de detecção em userspace que correlaciona suas saídas com a visão que o sistema tem de si mesmo (/proc).
| Canal | Mecanismo | O que o suprime | Custo da supressão |
|---|---|---|---|
/proc | Enumeração de diretórios procfs | DKOM (desvincular processo da lista de tarefas) | Baixo — técnica de rootkit bem compreendida |
sched_switch | Tracepoint eBPF no escalonador | Desanexar programa do array de funções; filtrar contexto | Médio — requer escrita no kernel no array de funções |
| NMI | Interrupção de contador de performance por hardware → evento de perf eBPF | Hook no IDT/manipulador NMI | Quase certeza de pânico no kernel — interrupção de hardware, não mascarável |
A principal propriedade arquitetural: um rootkit não pode suprimir todos os três canais simultaneamente sem que a supressão em si se torne detectável ou desestabilizadora. Suprimir NMI requer patchear o IDT (Tabela de Descritores de Interrupção), o que causa pânico na maioria dos kernels. Esta é a "bomba viva" — o único caminho do atacante para a cegueira total é aquele que provavelmente trava o sistema.
sched sees it, /proc doesn't → DKOM NMI finds sched's heartbeat stopped → TAMPER sched goes globally silent, /proc != empty → SILENT
Cada classe de detecção é um veredito diferencial: uma discrepância entre dois ou mais canais. O motor de detecção é uma função pura sobre o registro + snapshot do /proc + timestamps dos canais — sem E/S, sem efeitos colaterais, totalmente testável unitariamente.
### A reformulação do NMI: da observação à integridade
No design original, o NMI era um segundo canal de observação de processos que amostrava a CPU e reportava qual tarefa estava em execução. Isso era redundante: o sched\_switch já observa o escalonamento, e o NMI amostrava os mesmos dados por meio de um mecanismo diferente. A redundância custava ~1000+ eventos/s/CPU no ring-buffer de dados de processos que 99,999% das vezes confirmavam "sim, o escalonador está fazendo o que o escalonador faz."
Na arquitetura reformulada, **o NMI é redirecionado da observação de processos para a verificação de integridade de tracepoints.** Ele não reporta mais qual processo está na CPU. Em vez disso, ele verifica se `sched_switch` está realmente em execução lendo um heartbeat compartilhado `.bss`. Isso:
1. Elimina ~99% do tráfego do ring-buffer do NMI (eventos de estado estacionário próximos de zero)
2. Detecta diretamente desanexação de tracepoint, supressão e falhas de BTF/attach (o bug original do BTF — veja [§11](#11-the-btf-bug-incident))
3. Executa a partir de uma interrupção de hardware, fora do caminho de despacho do tracepoint — imune a `bpf_override_return`, interceptação de kprobe e manipulação do array de funções
4. Lê globais `.bss` via acesso direto à memória, não helpers do BPF — imune a `fmod_ret` em funções helper
---
## 3. O Canal de Observação sched\_switch