Ekaterina Gainullina, Security Vision
Introduction
If eBPF gives an attacker the ability to see and act inside the kernel, then the next logical step is the question of survival: how to stay in the system as long as possible? For any malicious payload of this level, stability is no less important than the ability to attack. Classic rootkits fight for presence in the system - they hide their files, processes, connections, survive reboots. eBPF rootkits do the same, but by other means - much more elegant and elusive.
eBPF unique is that its programs, once loaded into the kernel, run independently of the process that loaded them. The code remains active even if the script or binary that injected it no longer exists. The system "adopts" the program, so to speak, and no longer controls it as part of the user space. For an attacker, this opens up possibilities for subtle and persistent persistence: no traces on disk, no processes in ps, no ports in netstat.
In this section, we will analyze how an attacker can ensure the stability of an eBPF program: the ability to survive reboots, hide its activity, and even protect itself. We will also consider real techniques - from autoloading via systemd to namespace isolation, from self-defense via hooks to examples of attacks from practice. And most importantly, we will discuss what can be done about this: what countermeasures exist, how to detect such activity, and why traditional monitoring methods are powerless here.
Part 3. Stability and secrecy – consolidation in the system
So, we have seen that eBPF can give an attacker eyes and hands inside the kernel. Now the question arises: how to ensure the constant presence of such a tool? Traditional rootkits seek to take root in the system - to survive reboots, to hide their processes and files. eBPF rootkits are no exception. In this part, we will analyze how an attacker can achieve the stability of an eBPF program in the system and hide it from detection.
The first thing to note is that an eBPF program, once loaded into the kernel, lives separately from the user process. That is, after successfully loading and attaching, an eBPF program will run in the kernel even if the user code that loaded it is unloaded. For example, our keylogger from the 1 can be loaded by a small script, after which the script itself can terminate - the interceptor will remain hanging on readline calls. This already provides some simplification of the stealth task: there is no obvious malicious process in ps or top. However, the very fact of the presence of a loaded eBPF program can issue the bpftool prog, /sys/fs/bpfcommand filesystem, or debug points in /sys/kernel/debug.
How to load an eBPF program? An attacker has two main ways:
· Use the ready-made bpftool tool (or its equivalent). For example, by compiling a malicious eBPF program into a rootkit. o file, it can execute:
# Load the program and save (pin) it to the BPF file system
bpftool prog load rootkit.o /sys/fs/bpf/knock_filter
# Attaching a program (for example, to XDP on interface eth 0)
bpftool net attach xdp pinned /sys/fs/bpf/knock_filter dev eth0
The prog command load will load the eBPF bytecode into the kernel and commit it to the specified path (in the BPF pseudo FS). Next, net attach xdp will attach the downloaded program to the network interface.After that, the program will start working, and the rootkit.o file itself can be deleted - it is not needed, since the code is already in the kernel.
· Use the bpf () system call directly or the libbpf library in its malware code. This is a more stealthy method that does not require external utilities. For example, malware can contain eBPF bytecode inside itself or download it from a server, then call bpf(BPF_PROG_LOAD, ...) to register the program in the kernel. Then – bpf(BPF_PROG_ATTACH, ...) to attach it to the desired hook (for example, kprobe, tracepoint, cgroup , etc.). All this is done through the API, without creating any files on the disk.
The eBPF program itself will not survive a reboot - the kernel is "clean" at startup. Therefore, the attacker will take care of reloading. Possible tricks: add the eBPF boot command to the autoload scripts (rc.local, systemd unit file , cron task), disguise it as a legitimate service. For example, you can create a systemd service that executes the bpftool command with a pre-prepared bytecode at startup. Another way is to integrate booting into an existing process (through a vulnerability or patch). In a well-known case described by Dr. Web, the eBPF rootkit was installed first precisely to hide the installation of a classic modular rootkit. That is, attackers can combine techniques: the kernel module receives persistence (for example, through /etc/modules or DKMS), and the eBPF program protects this module from detection.
Concealment methods. Concealment is perhaps the most subtle and dangerous aspect of eBPF in the hands of an attacker. It’s one thing to eavesdrop and interfere, but quite another to remain invisible to even an experienced administrator and most security tools. And this is where eBPF reveals itself as a tool of camouflage – not just a rootkit, but a ghost living in the kernel that can control the behavior of the system without leaving a trace.
One of the key areas of stealth is deceiving debugging and diagnostic utilities. For example, the bpftool utility allows you to view loaded programs and eBPF maps, obtaining full information about attachment points and internal structures. But modern rootkits, such as ebpfkit, are able to interfere with the operation of this utility, forcing it to display an incomplete or fake picture. This is achieved by the fact that the eBPF program itself intercepts system calls or kernel functions associated with obtaining information about BPF objects, such as bpf_prog_get_info_by_fd, bpf_map_get_info_by_fd and similar. Using kprobe hooks, an attacker can change the contents of the returned structures, excluding their own identifiers from them. As a result, even with manual auditing via bpftool prog show malware will not be visible: the kernel "lies", being under the control of the rootkit itself. It turns out to be a kind of recursive protection - the eBPF program protects itself, suppressing attempts to detect it.
Another important mechanism is isolation and restriction of access to eBPF objects. BPF architecture allows loading programs in isolated namespaces or binding them to specific cgroups. This means that an eBPF program placed in a separate network namespace will not be available for auditing by other processes, even if they have root access - without crossing the namespace, this is simply impossible. In addition, BPF objects can be pinned in the bpffs virtual file system, and if an attacker mounts it in a non-standard location (for example, /tmp/.hidden_bpf), most tools, including automated scanners, will simply not look there. All this makes the malware invisible from the outside, even if it is actively running inside the kernel.
The classic element of disguise remains the interception of system calls. Previously, this was done through modification of the system call table or through the introduction of kernel modules (LKM rootkits), but now the same goals are achieved through eBPF - without the need to change anything on the disk. Special types of eBPF programs, such as LSM hooks and kprobe override, allow not only to observe a system call, but also to change its behavior - up to returning fake data. For example, if a process calls getdents () to read the contents of a directory, an eBPF program can cut out lines from there that refer to an "invisible" process or file. Similarly, intercepting the open () function can hide the presence of a certain executable file or configuration. All this happens in kernel mode, through built-in hooks, without external libraries or changes to system tables. In fact, the malware gets the ability to edit the user's or monitoring tools' perception of the system - in real time and without any signs of substitution.
Equally important, eBPF attacks can be almost completely traceless on disk. The program can be loaded into memory directly, and after activation, the bytecode is deleted - there is nothing in the logs, there is silence in cron, and the file system is empty. Some rootkits additionally encrypt or erase the remains of their bytecode, and can use non-standard paths for loading, bypassing kernel restrictions. For example, BPFDoor, one of the most famous examples of a stealthy backdoor, used eBPF inside BPF sockets without opening a single port and leaving no traces of eavesdropping. Moreover, in a number of cases, kernel vulnerabilities were used, such as CVE -2022-20268, allowing eBPF programs to be loaded, bypassing built-in checks and the verifier. These vulnerabilities have already been closed, but the very fact of their existence demonstrates how inventive attackers can be when it comes to introducing eBPF into a system.
Real-life cases reinforce these conclusions. For example, the aforementioned ebpfkit is a proof-of-concept rootkit demonstrated in 2021 that was able to hide its maps and programs from bpftool and other monitoring tools. The 2024 attack in Southeast Asia showed a combination of eBPF and a classic rootkit: the eBPF module was the first to hide the presence of the rest of the malware. The open - source boopkit project in 2022 posted an example of a backdoor that opens a reverse shell when it receives a special network packet - and all this is implemented purely through eBPF at the TCP level. Such tools are difficult to detect using traditional methods.
Detection and counteraction of eBPF rootkits. The stability of the eBPF rootkit is determined by its main feature: it runs in the kernel, with the same rights as the operating system. At the same time, it does not require the installation of modules, does not leave obvious traces in the file system, can be loaded dynamically, fixed in memory and disguised even from tools such as bpftool and bpftrace. All this makes it one of the most difficult to detect forms of malware on Linux - especially if the system is already compromised and the attacker has root access.
Classic methods of detecting malicious activity in Linux — looking for suspicious processes, anomalous cron jobs, unfamiliar binaries at startup, or open sockets — are useless in the case of eBPF. No processes in ps, no logs in journald, no enabled modules in lsmod. Everything is executed deep inside the kernel — within a pre-approved eBPF program that has already passed verification. Moreover, the malware can use hooks and spoofing to forge or filter any information returned by standard analysis tools, including calls stat, readlink, getdents, netstat and even bpftool or bpf ().
Nevertheless, there are technical and architectural approaches to protection, but they require a transition from a reactive model to a proactive one. The first and most basic is to prohibit unprivileged users from loading eBPF programs. The kernel.unprivileged_bpf_disabled=1 parameter allows you to disable the ability to use eBPF in user space without special rights (starting with Linux 5.8 and higher, it is recommended as mandatory in security systems). However, it is important to understand that this mechanism will not help if the attacker has already obtained root access - and this is the level of access most often needed to load more dangerous eBPF programs (for example, LSM hooks or XDP).
The next level is monitoring the fact that eBPF programs are loaded. This is possible because the kernel uses the bpf () system call, and it can be monitored, for example, via auditd, eBPF watchdogs, or other system tracers. Similarly, you can monitor the activity of the bpftool utility, especially the prog command. load, map create, program attach, etc. While these events do not directly indicate malicious activity, they can help identify suspicious behavior at an early stage - especially in non-standard scripts, unauthorized sessions, or isolated containers.
It is also extremely important to regularly audit the contents of the /sys/fs/bpf and /sys/kernel/debug/bpf/, virtual file systems, where eBPF pinned objects are stored. If an attacker has used a non-standard bpffs mount point, it is worth looking for signs of its existence: for example, manually scanning / proc / mounts, mount, umount system calls, or using tools like findmnt. Finding new mounts with the bpf type outside the standard path is already a potential indicator of compromise.
Some modern EDR and antivirus solutions have started to take into account the behavior of eBPF programs as a possible sign of an attack. For example, a sharp increase in the number of BPF maps, dynamic registration of kprobe hooks, non-standard LSM programs, and active use of bpf_trace_printk without obvious reasons can all be marked as suspicious. But, as with other technologies, the problem is that the malware can gain control first and disable monitoring of itself. In this sense, eBPF is an ideal rootkit: it can integrate into the kernel and suppress attempts to detect it using the same mechanisms that defenders use.
This leads to an important conclusion: countering eBPF abuse must be primarily preventive, not reactive. First of all, it is:
· Continuous audit of loaded eBPF programs and their attachment points (not only in standard paths, but throughout the entire system).
· Updating the core and all dependent components: eBPF is actively developing, but along with the functionality, vulnerabilities also appear. In recent years alone, more than 200 CVEs related to BPF and its verifier have been identified.
· Strict restriction on running privileged programs. If possible - via AppArmor, SELinux or similar systems. If not - via restricted containers, cgroup and seccomp.
· Using BPF LSM is a new mechanism that allows for the implementation of "whitelists" of trusted eBPF programs at the kernel level, similar to signed kernel modules.
Ultimately, eBPF is not only a threat, but also an opportunity. In the hands of an administrator, it is a powerful tool for debugging, analysis, profiling, and even security policy. But in the hands of a hacker, it is a full-fledged framework for creating living in the core, elusive and self-protected rootkits. The series "eBPF through the eyes of a hacker" shows how dangerous this technology can be if not properly controlled. And, most likely, the security community will have to build a new paradigm of protection in the coming years, in which the kernel will cease to be a "black box" - and will become a controlled space, including in relation to eBPF.
Conclusion
Everything a classic rootkit does — mask, interfere, intercept, persist — eBPF does better. It doesn't require kernel modules, doesn't create processes, can live in isolated namespaces, filter any data on the fly, and change the user's perception of the system. It can boot from memory, protect itself, and delete its traces. And it looks like a correct, valid part of the kernel — tested, verified, and acceptable.
In the final part, we saw that eBPF rootkits are not a hypothesis, but a reality. From boopkit and ebpfkit to examples of attacks in the wild, from Sabotage - as - a - Service to hidden shells activated by network packets, it has all happened before. Protecting against this level of threat requires rethinking the architecture of observability and trust. Control over user processes no longer saves. It is now important to know what is loaded in the kernel, what it is attached to, and who controls it.