copyFail.py 是一个概念验证(PoC)漏洞利用程序,演示了 Linux 内核中的一个本地权限提升漏洞。通过滥用 splice() 系统调用并结合精心构造的套接字选项,该漏洞利用程序能够破坏任意内核内存。它利用这一能力将磁盘上的任意 setuid 二进制文件覆盖为自定义的 ELF 可执行文件,然后运行被篡改的二进制文件以获得提升的权限。
攻击类型: 本地权限提升(LPE) 影响: 完全 root shell 访问权限(当针对 setuid-root 二进制文件时) 复杂度: 低(单个脚本,无外部依赖)
用法:
./copyFail_cleaned.py [目标二进制文件] # 默认:/usr/bin/su
原始 PoC 硬编码了 /usr/bin/su,但底层内核漏洞适用于任何文件——攻击者可以覆盖他们选择的任意 setuid 二进制文件。清理后的版本接受可选的命令行参数来指定目标,使这一行为更加明确。
Linux 内核提供了 splice() 系统调用,可以在内核内部完全在两个文件描述符之间复制数据——无需先将数据复制到用户空间。这被称为“零拷贝 I/O”,用于高吞吐量应用(如 Web 服务器和反向代理)中以提升性能。
该漏洞的产生是因为当 splice() 用于已配置了不受支持的协议级套接字选项的 PACKET 套接字时,内核未能正确验证某些套接字结构的内部状态。通过精心构造这些选项,攻击者可以破坏内核指针,然后利用 splice() 将内核内存写入重定向到任意位置。
简单来说:内核有一个在内部移动数据的快捷方式,而该漏洞利用程序欺骗这个快捷方式将数据写入错误的位置——具体来说,是写入系统二进制文件的内存副本中。
该漏洞利用程序遵循清晰的步骤序列:
漏洞利用程序创建一个原始 PACKET 套接字(AF_PACKET / SOCK_RAW),并将其绑定到故意构造的畸形地址。然后使用 SOL_PNIO(协议级别 279)调用 setsockopt(),这是一个 Linux 无法识别的 Solaris 级别常量。
为何重要: 当内核在 setsockopt() 中遇到不支持的协议级别时,会回退到不验证数据的通用处理程序。这使内核内存处于不一致、可利用的状态。
通过 accept() 建立套接字连接后,漏洞利用程序使用 sendmsg() 发送精心构造的辅助(控制)消息。这些消息具有故意不匹配的长度头——有些声称比实际短,有些声称比实际长。
为何重要: 内核的控制消息解析器使用这些长度进行指针运算。不匹配的长度导致解析器读取或写入超出预期边界,破坏相邻的内核结构——特别是 splice() 稍后将跟随的指针。
漏洞利用程序创建一个管道并调用 splice() 两次:
/usr/bin/su 中的数据拼接(splice)到管道中。为何重要: splice() 调用会跟随第 2 步中植入的被破坏的内核指针,导致数据被写入攻击者选择的内核内存地址,而不是套接字缓冲区。这为漏洞利用程序提供了任意内核内存写入原语。
漏洞利用程序的载荷是一个 160 字节的 ELF 可执行文件(见下文)。每次调用漏洞利用函数恰好写入 4 字节。因此主循环运行 40 次(160 / 4 = 40),每次:
在所有 40 次迭代之后,/usr/bin/su 的前 160 字节已被覆盖。
最后,漏洞利用程序运行 os.system("su")。内核从其页缓存中加载被修改的 /usr/bin/su(现在包含攻击者的 shellcode 而不是真正的 su 二进制文件),然后 shellcode 执行。
嵌入的载荷解压后是一个 160 字节的 x86-64 ELF 可执行文件,包含以下 shellcode:
; 尝试系统调用 105 (execveat) — 在较旧的内核上可能不可用
xor eax, eax
xor edi, edi
mov al, 0x69 ; 系统调用 105
syscall
; 回退:系统调用 59 (execve) — 可靠路径
lea rdi, [rip+0xf] ; RDI = 指向 "/bin/sh" 的指针
xor esi, esi ; RSI = NULL (envp)
push 0x3b ; 系统调用 59 编号
pop eax
cdq ; RDX = NULL (argv)
syscall
; 干净退出
xor edi, edi
push 0x3c ; 系统调用 60 (exit)
pop eax
syscall
; 数据段:"/bin/sh\0\0\0"
功能: 无参数生成 /bin/sh,继承运行 su 的进程的权限。如果漏洞利用程序以 root 身份运行(或具有授予 su root 访问权限的能力),则生成的 shell 是 root shell。
不执行的操作:
这是一个直接的一次性权限提升,符合研究性 PoC 的特征。
copyFail.py
├── hex_to_bytes() — 十六进制字符串解码器
├── exploit_splice() — 核心漏洞利用(套接字设置 + 破坏 + splice)
│ ├── 阶段 1:创建 PACKET 套接字,绑定,setsockopt (SOL_PNIO)
│ ├── 阶段 2:accept() 连接
│ ├── 阶段 3:使用精心构造的辅助消息调用 sendmsg()
│ ├── 阶段 4:pipe() + splice() 破坏内核内存
│ └── 阶段 5:recv() 尝试(巩固破坏)
└── 主循环:
├── 打开 /usr/bin/su(只读)
├── 解压嵌入的载荷(zlib → 160 字节 ELF)
├── 循环:每次迭代注入 4 字节(共 40 次迭代)
└── 执行被篡改的 su → root shell
漏洞利用程序以 O_RDONLY(只读)方式打开 /usr/bin/su。它不需要写访问权限,因为破坏是通过内核页缓存发生的——即内核在将更改刷新到磁盘之前使用的文件内存副本。基于 splice() 的写入完全绕过正常文件权限,直接写入内核内存。
/usr/bin/su 二进制文件被修改(与包管理器中的哈希值进行比较)setsockopt()splice() 系统调用sysctl 限制非特权用户创建 AF_PACKET 套接字copyFail.py(按分发版本)copyFail_cleaned.py(同一目录)net/packet/、fs/splice.c、net/core/sock.c本报告仅供安全研究和防御分析之用。
| 常量 | 值 | 在漏洞利用中的用途 |
|---|
AF_PACKET | 17 | 原始数据包套接字族 |
SOCK_RAW | 3 | 原始套接字类型 |
SOL_PNIO | 279 | 不支持的协议级别(Solaris) |
MSG_DONTWAIT | 0x400 | 非阻塞 sendmsg 标志 |