Linux Kernel Patched - exec: remove legacy custom binfmt modules autoloading
Author : 堇姬 Naup
Patched
搜尋執行檔處理器(binary handler)的邏輯中,包含了遺留下來的舊程式碼,用來在系統遇到不支援的執行檔格式時,自動載入對應的核心模組。
這段邏輯是從過去 a.out 轉移到 ELF 格式時保留下來的歷史產物。
但在移除對 a.out 支援之後,這段程式碼已經不再有實際用途。
解決方案:
清除這段位於 binary handler 搜尋邏輯中的舊程式碼。
同時也移除將 retval 初始化為 -ENOENT 的那一行,改為:
如果程式流程走到了函式的最後,就直接回傳 -ENOEXEC。
1 | diff --git a/fs/exec.c b/fs/exec.c |
前置準備
先把 source code 載下來,然後解壓縮
1 | wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.14.tar.xz |
在 kernel/naup.c
加入
1 |
|
之後去 kernel/Makefile
加入
1 | obj-y += naup.o |
去 arch/x86/entry/syscalls/syscall_64.tbl
加入
1 | 549 common naup sys_naup |
1 | make defconfig |
之後去
1 | make -j$(nproc) |
他會在 arch/x86/boot/bzImage
之後就正常的跑 qemu 把他跑起來
完整環境在這裡
https://github.com/Naupjjin/2025-NHNC-CTF-challenge/tree/main/No549
沒錯,我把這題出在了 NHNC CTF 2025 這場上
接下來就是嘗試 EoP 他了,把 PoC 丟上去就可以了
analyze
根據之前提及的使用 modprobe 來提權
簡單來說便是
search_binary_handle() 遍歷 formats,去嘗試 load_binary()
如果沒有找到對應的 binary handle
就會進入到 request_module
分支
https://elixir.bootlin.com/linux/v6.13.7/source/fs/exec.c#L1764
1 | retry: |
他會用 root 權限去執行 modprobe_path
通過覆蓋 modprobe_path 就可以來做提權
https://elixir.bootlin.com/linux/v6.13.7/source/kernel/module/kmod.c#L71
1 | argv[0] = modprobe_path; |
不過這份 patch 也闡明了,這段 code 本身是時代遺留產物,也因次在這份 patch 整段 code 被修改掉了
從 6.14 的 kernel 開始就沒有這個好用的手法了,畢竟已經不會去呼叫 request_module
了,嗎(?
https://elixir.bootlin.com/linux/v6.14-rc1/source/fs/exec.c
1 | read_lock(&binfmt_lock); |
Others Way 1
首先是真的沒有其他地方有呼叫到 request_module
了嗎
答案是有的
簡單一搜就可以發現有多處仍舊 reference 這個 function
https://elixir.bootlin.com/linux/v6.14-rc1/A/ident/request_module
不過需要注意,要觸發這段 code 需要滿足
- 低權限就可以執行到
- 必須是 Ubuntu、Debian 等主流 Linux 發行版,預設就內建的功能或驅動,而不是需要額外安裝、啟用、或啟用稀有選項才能使用的東西
- 簡單就可以摸到這段 code
這邊就可以使用 AF_ALG socket
https://www.kernel.org/doc/html/v6.14/crypto/userspace-if.html
AF_ALG 是 Linux 核心提供的一種 socket family,用於加密
使用者可以透過它來使用 kernel crypto API,例如:SHA1, AES, HMAC 等
這邊可以關注這段
當你去 call bind
,他會 call 到 alg_bind
他會去搜尋 sa->salg_type
如果沒有搜到就會去 call request_module
,嘗試去 load “algif-%s”
https://elixir.bootlin.com/linux/v6.14-rc1/source/crypto/af_alg.c#L148
1 | static int alg_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) |
PoC
1 |
|
Other way 2
這裡也有可以觸發的,當起去 call websocket 時候
需要去指定他的 Socket 的通訊協定家族(Family)
如果找不到對應的 family 的話,就會去用 net-pf-%d
來去 load 看看
這時候就會 call 到 request_module
所以只要指定一個不合法的 family 就可以了
PS: 第一個參數就是 domain 或稱作 family,其中『AF_』代表 Address Family,有些系統使用『PF_』(Protocol Family)
https://elixir.bootlin.com/linux/v6.14.11/source/net/socket.c#L1520
1 |
|
PoC
1 |
|
Other way 3
socket 非法的 protocol 也行
https://elixir.bootlin.com/linux/v6.15.2/source/net/ipv4/af_inet.c#L252
PoC
1 |
|
總結
總結就是,就算傳統通過不合法檔案執行來觸發 request module 的路線不行了,依舊有相當多的路可以去觸發
思想的話是去找 source code 中 request_module 位置,大致上是使用各種非法的東西
像是本篇的非法的 type、protocol、family 等等的