Skip to content

eBPF Rootkit

Loading an eBPF program attached to kernel tracepoints or kprobes to hide processes, files, or network connections from userspace tools — a kernel-assisted rootkit that needs no loadable kernel module.

The extended Berkeley Packet Filter (eBPF) lets a userspace program load small, verified bytecode programs into the kernel and attach them to hooks — tracepoints, kprobes, or network-socket filters — without writing a full kernel module. That legitimate observability mechanism (the basis of tools like bpftrace and modern EDR sensors) also gives an attacker with sufficient privilege a way to run code at the kernel boundary: an eBPF program attached to a syscall like getdents64 (used by ls, ps, and most directory-listing code) can filter out entries matching the rootkit's own PID or file names before the result ever reaches userspace, hiding it from every tool that walks /proc or the filesystem through the normal API — no unsigned .ko for a kernel-module signature check to flag.

How it works

The rootkit's userspace loader compiles or embeds an eBPF bytecode blob and attaches it via the bpf() syscall:

c
// simplified loader
int prog_fd = bpf_prog_load(BPF_PROG_TYPE_KPROBE, ebpf_bytecode, ...);
int perf_fd = perf_event_open_for_kprobe("sys_getdents64");
ioctl(perf_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0);

The attached program runs on every invocation of the hooked syscall and rewrites its return buffer in kernel space before the calling process ever sees it — filtering out dirent entries whose name matches the rootkit's hidden PID, file, or connection, entirely transparently to ps, ls, or netstat, none of which do anything wrong; they simply never receive the hidden data from the kernel.

Detection & analysis

Static analysis: A userspace binary that calls bpf() with BPF_PROG_TYPE_KPROBE (or the newer BPF_PROG_TYPE_TRACING) attached to syscalls like getdents64, bpf itself, or network-facing hooks, especially combined with perf_event_open, is a strong signal outside of known observability tooling (Falco, Cilium, Tetragon, standard eBPF-based EDR agents) — context and provenance matter here, since the mechanism itself is also entirely legitimate.

Dynamic analysis: bpftool prog list (and bpftool prog show <id> --json for detail) enumerates every eBPF program currently loaded in the kernel, including its type and attach point; diff this against an expected baseline for the host. Cross-view detection is the strongest signal available: compare what /proc or ps report against an independent enumeration path — reading task lists via a separate kernel interface, or querying from a hypervisor/host outside the affected guest — since a discrepancy proves something is filtering one view but not the other.

Detection rule hint: Enable kernel lockdown mode (lockdown=confidentiality or stricter) where feasible, which restricts unprivileged BPF program loading. Alert on bpf() syscalls from processes outside an allow-listed set of observability agents, and periodically run bpftool prog list via an out-of-band collector (not something the rootkit could also hook) to catch programs a compromised in-guest agent might otherwise hide from itself.

Votes

Comments(0)