>samit_hota
Back to research
THREAT INTELLIGENCE

Unmasking eBPF Rootkits: Kernel Telemetry and Detection Artifacts

Samit Hota·
#ebpf#threat-hunting#linux#telemetry

The Fallacy of Invisible In-Kernel Evasion

eBPF has been hailed as the ultimate observability paradigm, but its capability to run custom bytecode at near-native speed inside the Linux kernel makes it equally attractive for stealth persistence. Attackers hook tracepoints, kprobes, or LSM hooks to modify user-space buffers or force synthetic return codes. When utilities like ps or ls query file descriptors or process listings via getdents64, an eBPF program loaded with helpers like bpf_probe_write_user or bpf_override_return can selectively redact entries before the buffer reaches ring 3.

However, offensive eBPF suffers from a fundamental paradox: to alter kernel behavior, the program must reside in kernel memory and attach to concrete kernel hooks. While an attacker can hide a process from procfs by filtering directory entries in user space, they cannot prevent the kernel’s BPF subsystem from tracking its own execution objects.

Uncovering Anomalous Hooks with bpftool

When an eBPF program loads, the kernel assigns it an integer ID, allocates memory tracking structures, and maintains reference counts for attached hooks. Even if an attacker unlinks the program path from the virtual filesystem (/sys/fs/bpf), bpftool communicates directly with the kernel via bpf() system calls to list active objects.

Run the following command to audit all loaded BPF programs across the host:

bpftool prog show --json --pretty

Look specifically for programs with type set to kprobe, tracepoint, or fexit. Note the id, name, and loaded_at timestamps. Attackers frequently leave name blank or use benign-sounding names like sys_enter to blend in.

To correlate a program ID with its active kernel attachment, query the active BPF links:

bpftool link show

A suspicious link will display an attachment to high-value targets like sys_enter_getdents64, tcp_v4_connect, or security_file_open. If bpftool reveals an unidentified kprobe program attached to file listing or process execution functions, inspect the disassembled bytecode instructions using:

bpftool prog dump xlated id <PROG_ID>

Search the disassembled instructions for helper calls like bpf_probe_write_user (helper ID 36) or bpf_override_return (helper ID 58). In production environment agents, bpf_probe_write_user is virtually never present outside of explicit debugging environments due to system stability risks, making its presence a high-confidence indicator of compromise.

Auditing Tracepoints and Kprobe Registrations

If an attacker attempts to corrupt or restrict bpftool execution, fallback directly to lower-level kernel tracing interfaces in sysfs and debugfs. The Linux kernel maintains global registers for all active kprobe and tracepoint dynamic events.

Inspect the registered kprobes directly via debugfs:

cat /sys/kernel/debug/tracing/kprobe_events

Legitimate security sensors list their probes clearly, whereas unauthorized entries pointing to internal system call routines (e.g., p:kprobes/my_probe __x64_sys_getdents64) indicate unmonitored hook creation.

To check which functions currently have active tracing attached, examine the enabled_functions file:

cat /sys/kernel/debug/tracing/enabled_functions

Cross-reference these function offsets against your baseline of authorized monitoring sensors. Any probe mapped to raw system call wrappers without an associated vendor binary process holding the corresponding file descriptor is an immediate anomaly.

Inspecting BPF Maps for Persistence Data

Offensive eBPF programs rely on BPF maps (such as BPF_MAP_TYPE_HASH or BPF_MAP_TYPE_ARRAY) to store configuration state, such as target PIDs, IP addresses, or string patterns to hide. Identifying these maps provides visibility into what the rootkit is attempting to conceal.

List all active maps on the system:

bpftool map show

Identify maps linked to suspicious program IDs discovered during the initial audit. Once a candidate map ID is isolated, dump its key-value pairs:

bpftool map dump id <MAP_ID>

For instance, if a map contains integer keys corresponding to PIDs that do not appear in ps aux output or standard /proc listings, you have identified suppressed processes. Comparing the raw keys inside eBPF maps against live procfs directories reveals the delta between what the kernel knows and what user space is permitted to see.

Behavioral Anomalies and Defensive Telemetry

Beyond static object inspection, eBPF rootkits introduce unique behavioral side-effects that auditing tools can capture:

  1. System Call Auditing: Enable auditd rules on the bpf() system call (sys_enter_bpf). Monitor for BPF_PROG_LOAD and BPF_LINK_CREATE commands initiated by non-standard parent processes or interactive shell sessions.
  2. Lock Mem Limits: Loading eBPF programs requires locking memory pages (RLIMIT_MEMLOCK). A sudden spike in locked memory attributed to unauthorized accounts or transient system services indicates program injection.
  3. Ring Buffer Misalignment: Compare user-space process trees derived from /proc against out-of-band kernel execution records generated by hardware performance counters (perf_event_open). eBPF rootkits filtering getdents64 can suppress file listing queries, but they cannot prevent raw performance counters or low-level network drivers from registering execution side-effects.

By combining bpftool introspection with direct debugfs validation and audit logging on the bpf() system call, security teams can strip away eBPF-based stealth and identify malicious kernel attachments regardless of user-space manipulation.

Want a second set of eyes on your security posture?

Let's talk about where your real exposure is.

Book an advisory call