BPFDoor - Part 1 - The past

An exploration the archeological roots of the BPFDoor Linux malware.

BPFDoor - Part 1 - The past
"Just for fun" - The book shares it's titles with early BPFDoor malware sample's hardcoded authentication password and encryption key.
💡
This is the first part on a series of posts on the BPFDoor malware.
In Part 2 we look at evasive changes in samples reported in a significant telecommunication company's breach - along with IoCs.

In this post we follow breadcrumbs sprinkled across the Internet's past in an attempt to understand BPFDoor potential code origin which span almost 20 years ago. We also uncover a fork or early version which appeared in the wild in 2016

Refer to the following timeline of events described:

A timeline spanning almost 20 years

This post attempts to mostly avoid what's already been covered in prior literature. For readers not familiar with BPFDoor, the following resources are recommended for prior reading: Trend Micro (2025), Sandfly Security (2022), Elastic (2022).

💡
This post makes no assertion related to the attribution of BPFDoor's developer(s) nor attributed threat actor(s).

Just for fun

Central to this post is sniffdoor - the source code is not easy to find. A mirror can be found here.

Many early samples of BPFDoor found in the wild use the hardcoded password justforfun. We also see this in leaked source code:

Pivoting off this phrase, we land back to the year 2011, stumbling on an archived blog, "Just for fun", also sharing its title with the name of book by Linus Torvalds, published in 2001. The domain was registered in 2011:

BPFDoor includes a hardcoded epoch timestamp used for time stomping. The date is October 30, 2008 GMT

Notably the most recent samples have retained this code, but never call it.

21 days after this hardcoded date, a program titled "Program Just for Fun!" was published by the developer of sniffdoor:

If you compile and run the program, you will be greeted with an ASCII animation of a tank firing shells across your terminal.

Also in 2006, the developer released source to soshell (source mirror)

And sniffdoor (cloud-sec.org archive.org, archive.org, archive.org):

Both sniffdoor and BPFDoor use the pty control handling from another program, bindtty (mirror). While bindtty is not dated, there is some code overlap with a kernel rootkit, published in Phrack #58 in 2001 by sd@fs.cz.

Timestamps from the obtained sniffdoor archive align with the posting date on forum.eviloctal.com

e08028d5582f14b3f810a299330318119deb03a8d6e3ffac43cb7782a1f8c25e
evaloctal.com forum post

The comment section in the compiled binaries in the tarball indicate the binary compiled on the Asianux distribution giving a compilation date sometime after January 23rd 2006:

As such, we can date sniffdoor to be likely developed and released between 2006-2007.

Similarities between BPFDoor and sniffdoor (v1.0):

  • Sharing the same code for its pseudo terminal handling (taken from bindtty).
  • Numerous overlapping function names/routines
  • Uses raw sockets to intercept magic / wakeup packets
  • Supports both connect/bind and reverse shells

BPFDoor has improved stealth capability and other improvements that's not present in sniffdoor:

  • Uses a BPF filter to reduce volume of traffic hitting the process as it looks for magic packets
  • In addition to TCP for magic packets, UDP and ICMP is also supported
  • Payload is encrypted
  • Anti-forensics such as masquerading its process name, time-stomping, overwriting environment variables
  • Injects iptables rules in bind mode

As an example with BPFDoor on the left and sniffdoor on the right. Both routines originate their code from bindtty.c:

BPFDoor (left) and sniffdoor (right)

BPFDoor's decompiled controller's getshellfunction overlaps with sniffdoor's getshell_local. Note that there is no known source for the controller in the public domain at the time of writing.

BPFDoor controller (left) and snniffdoor (right)

As sniffdoor and soshell borrowed code from bindtty.c. The soshell client source includes a comment that code was also taken from conntty which could not be found archived or otherwise.

In the todo file of snniffDoor's code we can see features that would make it's way into BPFDoor:

In future (I hope):
 - Support ICMP,UDP protocol woke up.
 - Make it more stable.

Also found are comments on an intention to add process name masquerading - in a dynamic manner - another feature which also made its way into BPFDoor

An (automated) translation:

Hide, hide, now the initial plan is to use a fake process name to fool around, making it look like -bash, hehe, then create an LKM to hide it, ideally achieving dynamic hiding.

We see thatBPFDoor did 'fake' it's process name from by randomly selecting from a predefined list. (The newest versions removed the random selection, as seen in part 2 of this post)

An LKM would also too eventuate as the WNPS rootkit - also sharing code overlaps with both sniffdoor and the bpfdoor client:

bfpdoor client
wnps client

The circumstantial details here provides no evidence in respect to the attribution of the author of BPFDoor. The choice of the password justforfun for BPFDoor is an curious one though. Some possible explanations:

  • The sniffdoor developer, WZT was the very first initial developer of BPFDoor
  • Another developer visited WZT's blogs, obtained the sniffdoor or soshell code and was influenced with the phrasing "Just for fun" or added the term as password as a means of a false flag (misattribution)
  • The phrasing is a complete coincidence, the developers of BPFDoor and WZT being Linux enthusiasts, both enjoyed reading Linus's book, titled "Just For Fun".
  • Also must be considered is the possibility that BPFDoor and sniffdoor both borrowed code from a common source other then bindtty.c. This code goes back many decades and version/derivates are likely to have been shared amongst individuals and groups.

The sniffdoor developer released other (more well known) software in that era, such as the adore-ng rootkit (which was reported to be included in APT 41's toolset (Mandiant report, page 47).

💡
The timing of the development of sniffdoor developer was coincides around the time they were reported to have left the NCPH Group (source, page 203).

Notably, some remaining members of NCPH Group became associated to APT 41. Also for interesting reading is this testimony by Adam Kozy.

NotBPFDoor - An early direct descendent

While testing YARA rule sets for BPFDoor for part 2 of this post, a very early version of BPFDoor was identified. The two samples are not compatible with the observed clients to date: and more notably, does not use a BPF filter (as sniffdoor). Hence, to differentiate, we give the name - NotBPFDoor.

Two samples were uploaded in August 2016, with the initial submitters originating from Hong Kong (link). This was before the source code had leaked into the public domain.

b2d3c212e71ddbaf015d8793d30317e764131c9beda7971901620d90e6887b30
ebffd115918f6d181da6d8f5592dffb3e4f08cd4e93dcf7b7f1a2397af0580d9

NotBPFDoor shares significant code overlap with BPFDoor. Although there is extra functionality which has been removed in BPFDoor samples:

  • Optional boot persistence mechanism
  • Ability to re-configure it's process name and password through a configuration menu

Additionally, NotBPFDoor:

  • Uses a single password rather then two (to differentiate mode of operation)
  • Uses a semaphore as a mutex lock rather then writing a file to disk

Initial process name and passwords

A single hardcoded process name is used (portmap and rhnsd). Later BPFDoor samples randomly select from a list of process names. The malware then goes full circle as we will see with the most recent variants revert back to a single process name.

b2d3c212e71ddbaf015d8793d30317e764131c9beda7971901620d90e6887b30

Mutex Lock

  • The mutex lock uses libc's semget function to set a semaphore for the process rather then to write a file to disk. The semaphore key is 1433490800 (0x55715570) which corresponds to a epoch date of Friday, June 5, 2015 UTC. This could hit at the approximate year the code was in development.
ebffd115918f6d181da6d8f5592dffb3e4f08cd4e93dcf7b7f1a2397af0580d9

If the process abnormally terminates, the semaphore is still held by the kernel, meaning it will have to be manually removed before the process can be started again (unless the system is rebooted)

$ ipcs
...
------ Semaphore Arrays --------
key        semid      owner      perms      nsems
0x55715570 0          root       600        1

$ ipcrm -S 0x55715570

Persistence

A feature that seems to be removed in later BPFDoor samples is the ability for maintaining persistence across system reboot. If the x flag is used, the file /etc/profile.d/lang.sh is checked to contain the string unset LC_TIME. It then adds itself to this script which will be run every time a user logs in with a shell.

Self Modifying Configuration

The masqueraded process name and password can be configured with the C switch. A "start time" and "end time" can also be configured.

The start/end time options appear to be unused.

Rather then write to an external configuration file, the changed parameters appended to itself, increasing the ELF binary file size by 598 bytes. The first 64 bytes is a fixed byte sequence originating from a global in the .ro section. If these 64 bytes are not found at the end of itself, then they will be written - followed by 534 bytes of the actual configuration:

The 'marker' bytes Shannon entropy: 5.65625. It's origin has not been identified.

In one version it looks as if hex encoded bytes were pasted into the source as a string, rather then as as actual byte values (a likely mistake, fixed in the second identified sample)

b2d3c212e71ddbaf015d8793d30317e764131c9beda7971901620d90e6887b30

If others would like to have a go at trying to identify what the byte sequence could be, here it is:

4A 8A BA AB A8 80 F7 F0 24 C6 A5 4B 4A B4 0D DD E4 C6 FF 80 75 0E B7 25 7C 95 B2 9A E6 6C A6 87 B2 CC 06 FF 26 D2 3D FF 26 7E 37 1B 10 D3 1B 51 AC 7B 81 60 08 F8 50 EC 05 90 68 4B FF 44 14 8B

An early packet_loop

We see a very early version of the packet_loop routine. Only TCP is supported and no setsockopt with SO_ATACH_FILTER. The hardcoded magic bytes are 0x5571.
(BPFDoor samples have been observed to use 0x7255, 0x5293 and 0x39393939).

ebffd115918f6d181da6d8f5592dffb3e4f08cd4e93dcf7b7f1a2397af0580d9

In summary, NotBPFDoor appears to either an early fork or early version of BPFDoor with slightly different functionality.

Next, onto part 2 where we jump to the year 2025 and look at how the malware has evolved.

Appendix

Citations

A list of links referenced in this post series (This is the only content in which an LLM was used to assist in writing this post)