Affected Platforms: Linux
Impacted Users: Linux-based network appliances or servers
Impact: stealthy malicious C2 communication
Severity Level: Medium
eBPF—extended Berkeley Packet Filter—is a very interesting kernel technology that lets users load tiny, sandboxed programs into the Linux kernel to inspect or modify network packets, system calls, and more. The technology was introduced in 2015 to renovate the “old” BPF technology of 1992, which was no longer adapted to modern computer architectures (e.g., 64-bit).
As usual, the technology was quickly noticed by malware authors, resulting in the Bvp47 malware in 2015, as well as a collection of rootkits, such as Ebpfkit and TripleCross. However, due to the required skills needed to use or exploit eBPF, the malware remains rare (in number). Today, the malware scene mostly consists of two families: Symbiote and BPFDoor, both from 2021.
It’s 2025. Have we gotten rid of BPF malware yet? Yes and no.
Yes, so far, we detect all new variants of those families, and our customers are safe. But “no”, eBPF malware is not something of the past. In 2025, FortiGuard Labs detected 151 new samples of BPFDoor, and three of Symbiote.
EBPF malware are not massive – in number. They were never meant to be, because their development requires much more skill than random scams or ransomware. eBPF malware is another class, and it is particularly dangerous because it is difficult to detect, efficient, and powerful. This makes it a common choice for state-sponsored malware (which is allegedly the case for BPFDoor).
BPF uses its own instruction set. It’s quite different from the well-known x86/ARM. eBPF ISA is intentionally minimal, using fixed-size 64-bit instructions (x86 and ARM instructions both use variable-length instructions).
When reversing malware with BPF bytecode, while the eBPF ISA is not “complex,” it adds another layer of difficulty to reverse engineering. A few tools do exist: bpftool, an eBPF processor for IDA Pro, or the use of the Capstone engine.
It is perhaps less known that Radare2 supports BPF bytecode natively. For example, let’s analyze a recent sample of Symbiote from July 30, 2025. This sample is quite similar to earlier versions (read more here): at some point, the malware attaches a BPF filter to a socket. Except in this recent version, the BPF filter has changed.
Using Radare2, we can locate the eBPF bytecode object within the malicious binary. We know its length is 352 bytes (by reversing the function that uses the bytecode). We can tell Radare2 to switch to the BPF architecture (e asm.arch) and disassemble the bytecode (pd). Radare2 easily disassembles it—ldh (load half word) and jeq (jump if equal) are typical BPF instructions.
The BPF instructions show the new variant only accepts IPv4 or IPv6 packets for protocols TCP, UDP, and SCTP on ports 54778, 58870, 59666, 54879, 57987, 64322, 45677, and 63227.
This is common for botnets: C2 communication over non-standard ports. Some security tools (e.g., basic firewalls, IDS or IPS detection engines) often focus on inspecting known ports (e.g., HTTP) but do not bother with unknown high ports. With traffic such as Symbiote’s, those tools are likely to log no traffic at all, or at best flag a few packets to an “unknown port” - which can be many legitimate things and might not get noticed.
In the original version of Symbiote, the malware only accepted TCP and SCTP packets, not UDP, and from a smaller list of ports. A longer list of ports usually means the malware performs port hopping, i.e., if a given port is blocked or flagged, the malware uses another port from the list. The longer the list is, the less easy it is for network administrators to write an efficient rule blocking the ports without risking causing a False Positive.
As for accepting UDP packets, this also seems to be an intelligent move: UDP is connectionless, so the malware can conveniently hop between UDP ports because there is no connection to establish or close. Moreover, a classic IDS is natively more efficient over TCP, as many rules are tuned to TCP handshakes, while identifying malicious UDP sessions is more difficult.
Yes, Radare2 disassembles the BPF bytecode, but we we still need to understand what the BPF instructions do. In the past, the solution would have been “RTFM.” Today, we can use Artificial Intelligence’s vast knowledge to assist us.
I provided the BPF bytecode to Claude.AI. Note that, at that time, I incorrectly assumed the “decoding” was wrong. It’s just that I hadn’t switched to the BPF architecture in Radare2. Claude Sonnet 4.5 explained each instruction: the jump for IPv6 packets, cases for SCTP, TCP, and UDP protocols, etc.
The AI concluded with a summary explaining what the BPF bytecode does. Note the well-advised assumption that this is related to malware.
Asking the AI about the BPF bytecode is certainly far quicker than reading the eBPF ISA documentation. Nevertheless, always keep a critical eye on whatever the AI claims. I have encountered several errors:
Besides Symbiote, Linux/BPFDoor is the other significant malware family that abuses BPF. It appeared in 2021, but we have found a significant number of new variants in 2025 (240 samples since 2021, 150 in 2025 alone). What are the differences? Are they just minor, or are there new features?
Let’s ask an AI to scout the area. I set up an MCP server for Radare2: this arms the LLM with a Radare2 disassembler. I then asked the LLM to compare an original analysis of BPFdoor of 2021 with a recent sample of June 19, 2025.
(82ed617816453eba2d755642e3efebfcbd19705ac626f6bc8ed238f4fc111bb0)
In the screenshot below, we see (1) my initial prompt at the top and (2) the MCP server preparing to open the recent sample in the Radare2 disassembler.
The LLM + MCP server reverses the BpfDoor sample—in particular, the function named apply_bpf_filter, which configures a BPF filter on a socket. It then generates a rather extensive markdown analysis (see screen capture below). The cost of this analysis was 0.50 USD.
The most important part of the report boils down to the following table:
Feature |
2022 Version (Article) |
2025 Sample (bpfdoor.elf) |
Status |
Notes |
BPF Packet Filter |
IPv4 only (0x800) |
IPv4 + IPv6 (0x800, 0x86dd) |
✅ Enhanced |
Major upgrade - now supports IPv6 traffic monitoring |
Magic Packet Values |
UDP: 0x7255ICMP: 0x7255TCP: 0x5293 |
‘X’ (0x58) character + colon delimiter |
⚠️ Changed |
Completely different packet identification mechanism |
Packet Protocols |
ICMP, UDP, TCP |
ICMP, UDP, TCP (via AF_PACKET) |
✅ Same |
Still monitors all three protocols |
RC4 Encryption |
Present |
Present (sym.rc4 @ 0x0000169c) |
✅ Present |
Encryption layer still used |
Reverse Shell |
Yes (connect-back) |
Yes (sym.reverse_shell @ 0x00401562) |
✅ Present |
Connects back using /bin/sh |
Process Masquerading |
10 fake process names |
None found |
❌ Removed |
No masquerading strings in binary |
File Location |
/dev/shm/kdmtmpflush |
Not present |
❌ Removed |
No /dev/shm hardcoded paths |
PID Dropper |
/var/run/haldrund.pid |
Not visible |
❌ Removed |
No dropper file references |
Timestomping |
Yes (Oct 30, 2008 date) |
Not visible |
❌ Removed |
No timestomp code found |
Binary Self-Deletion |
Yes |
Not visible |
❌ Removed |
No self-deletion code found |
Environment Wipe |
Yes (clears envp) |
Not visible |
❌ Removed |
No environment clearing code |
Firewall Bypass |
iptables redirect rules |
Not visible |
⚠️ Unknown |
No iptables manipulation found |
Shell Port Range |
42391-43391 |
Not visible |
⚠️ Unknown |
No port range logic found |
Bindshell |
Yes (local shell) |
Only reverse shell visible |
⚠️ Changed |
Simplified to reverse shell only |
The table has been generated by AI. I have manually checked that point 1 (BPF Filter), point 2 (magic value), and point 5 (reverse shell) are true. Point 4 (RC4 encryption) is wrong/a hallucination: I haven’t been able to locate the RC4 code in the sample. The other points have not been checked because they carry less importance for this blog post.
In the BPFDoor sample, the BPF filter is set over a raw socket (type 3) in the function apply_bpf_filter.
In apply_bpf_filter, we don’t have direct BPF bytecode as in Symbiote, but a structure of socket options which are interpreted as a BPF filter because the level is SOL_SOCKET and the optname is SO_ATTACH_FILTER. Specifically, those are actually classic BPF bytes.
The BPF filter structure is represented in C below:
struct sock_filter bpf_filter[] = {
{ 0x28, 0, 0, 0x0000000c }, // load the Ethernet type field
{ 0x15, 0, 4, 0x000086dd }, // is it IPv6? no: jump forward 4
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 0, 11, 0x00000011 }, // is it UDP? no: jump forward 11
{ 0x28, 0, 0, 0x00000038 }, // get destination port of UDP IPv6
{ 0x15, 8, 9, 0x00000035 }, // accept DNS port
{ 0x15, 0, 8, 0x00000800 }, // is it IPv4? no: jump 8
{ 0x30, 0, 0, 0x00000017 }, // get IPv4 protocol
{ 0x15, 0, 6, 0x00000011 }, // is it UDP (over IPv4)? no: jump 6
{ 0x28, 0, 0, 0x00000014 }, // get IPv4 fragment offset and flags
{ 0x45, 4, 0, 0x00001fff }, // if fragmented: jump 4
{ 0xb1, 0, 0, 0x0000000e }, // compute IPv4 header length
{ 0x48, 0, 0, 0x00000010 }, // get destination port
{ 0x15, 0, 1, 0x00000035 }, // if DNS, accept, otherwise reject
{ 0x6, 0, 0, 0x00040000 }, // jump here to accept
{ 0x6, 0, 0, 0x00000000 } // jump here to reject
};
I confirmed that the sample now supports IPv6 packets. It will keep only UDP port 53 (DNS) traffic, over IPv4 or IPv6. This is a subtle way for malware to hide its presence: DNS traffic is frequent and usually not suspicious. The malware uses this for its own communications.
In 2025, we have new variants of Symbiote and BPFDoor, two malware families that abuse BPF. The reverse engineering of these samples and their comparison with older variants show that malware authors are enhancing their BPF filters to increase their chances of evading detection. Symbiote uses port hopping on UDP high ports, and BPFDoor implements IPv6 support.
Thanks to my colleague, Geri Revay, for introducing me to eBPF Malware.
The malware described in this report is detected and blocked by FortiGuard Antivirus as:
Linux/Symbiote.B!tr (SIGID: 171365647)
Linux/BpfDoor.F!tr (SIGID: 171124526)
FortiGate, FortiMail, FortiClient, and FortiEDR support the FortiGuard AntiVirus service. The FortiGuard AntiVirus engine is part of each of these solutions. As a result, customers who have these products with up-to-date protections are protected.
Fortinet has also released IPS signatures to protect customers against reverse shell communications:
Backdoor.BPFDoor.TCP
Backdoor.BPFDoor.TCP2,
Backdoor.BPFDoor.ICMP,
Backdoor.BPFDoor.UDP
FortiGuard IP Reputation and Anti-Botnet Security Service proactively block these attacks by aggregating malicious source IP data from the Fortinet distributed network of threat sensors, CERTs, MITRE, cooperative competitors, and other global sources that collaborate to provide up-to-date threat intelligence about hostile sources.
If you believe this or any other cybersecurity threat has impacted your organization, please contact our Global FortiGuard Incident Response Team.