
Breaking Linux Runtime Security: eBPF Rootkits, Syscall Interception, and Evading Modern EDR (Falco & Tetragon)
Exploring the offensive frontier of Extended Berkeley Packet Filter (eBPF): manipulating kernel probe hooks, TOCTOU race conditions in runtime sensors, and designing high-assurance detection strategies.
The Paradigm Shift in Linux Threat Modeling
For decades, kernel-level persistence on Linux required compiling and loading a Loadable Kernel Module (LKM). However, modern enterprise Linux distributions have steadily closed this door:
- Secure Boot verification cryptographically blocks unsigned kernel modules.
- Strict kernel lockdown modes (
kernel.lockdown=confidentiality) prevent writing to/dev/memand direct kernel patching. - Modern Endpoint Detection and Response (EDR) agents specifically monitor
init_moduleandfinit_modulesyscalls.
To maintain stealth, advanced threat actors shifted focus to eBPF (Extended Berkeley Packet Filter).
Originally designed for safe, sandboxed kernel observability and high-performance networking, eBPF allows programs to execute inside the kernel without recompiling or loading traditional modules. Today, attackers weaponize eBPF to construct invisible rootkits, subvert runtime security tools like Falco and Tetragon, and intercept sensitive system calls before telemetry is recorded.
---
Anatomy of an eBPF Rootkit
An eBPF program is attached to kernel probes (kprobes), user probes (uprobes), or tracepoints. When a targeted kernel event occurs, the verified bytecode executes in kernel space.
User Space (Process)
│
▼ Syscall (e.g. sys_execve)
──────────────────────────────────────────── Kernel Boundary
│
├─► [eBPF Hook: Attacker Rootkit] (Rewrites buffer in memory)
│
├─► [eBPF Hook: Security Sensor] (Inspects already modified buffer)
│
▼ Native Kernel Execution
Manipulating Process Memory via bpf_probe_write_user
While the eBPF verifier enforces safety to prevent kernel panics, the kernel provides helper functions intended for debugging. One such helper is bpf_probe_write_user.
When an eBPF program hooks sys_enter_execve, it can inspect the path of the binary being launched. If an administrator runs an auditing command, the eBPF program can rewrite the arguments in user memory before the kernel executes them:
// Malicious eBPF kprobe payload snippet
SEC("kprobe/__x64_sys_execve")
int trace_execve(struct pt_regs ctx) {
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
// Detect if administrative security auditor is checking active processes
char target_cmd[] = "ps";
char redirect_cmd[] = "ls";
// Overwrite the execution buffer in user memory
bpf_probe_write_user((void
)PT_REGS_PARM1(ctx), redirect_cmd, sizeof(redirect_cmd));
return 0;
}
---
Subverting Modern Runtime Monitors (Falco & Tetragon)
Kubernetes security monitors like Falco and Tetragon rely heavily on eBPF tracepoints or kprobes to stream audit events into centralized detection engines.
However, adversaries exploit Time-of-Check to Time-of-Use (TOCTOU) discrepancies in user-space buffers:
- Memory Pointer Race: When a program issues an
execvesyscall, the arguments reside in user space. - Sensor Capture: Falco reads the pointer from user memory to generate an alert rule (e.g. checking for
nc -e /bin/bash). - Kernel Read: The Linux kernel reads the user pointer to execute the command.
- The Attack Window: An attacker thread rapidly swaps the memory buffer pointer concurrently with the syscall invocation. The security monitor inspects the benign payload (
/bin/true), while the kernel executes the malicious shell runner.
Furthermore, an eBPF rootkit with CAP_BPF or CAP_SYS_ADMIN can hook into the socket buffer filters (XDP or tc subroutines) to completely conceal reverse shells from local netstat, ss, and packet capture tools.
---
Advanced Evasion: Hiding from Process Listings
Rootkits hide files and processes by hooking sys_getdents64, the system call responsible for reading directory entries.
When a user executes ls /proc or launches top, the kernel returns a stream of directory entries representing active Process IDs (PIDs). The eBPF rootkit filters out the attacker's PID by adjusting the d_reclen (record length) field in the linux_dirent64 structure, causing the parsing loop to leap directly over the hidden process.
---
Detection Engineering and Hardening Strategies
Detecting eBPF rootkits requires auditing the eBPF subsystem itself rather than relying solely on syscall telemetry.
1. Enforce Strict Kernel BPF Restrictions
Ensure unprivileged users cannot load BPF programs:# Disable unprivileged BPF entirely
sudo sysctl kernel.unprivileged_bpf_disabled=2
Restrict JIT compiler modifications
sudo sysctl net.core.bpf_jit_harden=2
2. Audit Loaded eBPF Programs via bpftool
Regularly audit the inventory of loaded BPF programs and maps on critical production servers:
# List all active eBPF programs
sudo bpftool prog show
Verify pinned programs inside bpffs
ls -la /sys/fs/bpf
3. Implement Integrity Monitoring on Kernel Probes
Deploy baseline integrity verification that monitors/sys/kernel/debug/kprobes/list and alerts on unexpected probe registrations against critical syscall handlers.