劫持SUID程序提权彻底理解Dirty_Pipe:从源码解析到内核调试
字数 3036 2025-08-29 22:41:01

Dirty Pipe (CVE-2022-0847) 漏洞分析与利用完全指南

一、漏洞概述

Dirty Pipe (CVE-2022-0847) 是 Linux 内核 5.8 及之后版本中存在的一个本地提权漏洞,攻击者可以通过覆盖任意可读文件的内容(即使文件权限为只读)将普通用户权限提升至 root。该漏洞源于管道(Pipe)机制与 Page Cache 的交互缺陷,与经典的 Dirty COW (CVE-2016-5195)漏洞类似,但利用更简单、影响范围更广。

漏洞核心原理

  1. 管道的"零拷贝"特性:当通过 splice 系统调用将文件内容写入管道时,内核会直接将文件的 Page Cache 页面作为管道的缓冲区页使用,而非复制数据。

  2. 未初始化的标志位漏洞:管道缓冲区的 flags 变量在初始化时未正确重置,导致攻击者可以错误地认为该页是可写的。

  3. Page Cache 的覆盖效果:文件的 Page Cache 页面被直接关联到管道缓冲区,攻击者通过向管道写入数据可覆盖 Page Cache 中的原始文件内容。

二、环境搭建与调试准备

1. 内核准备

编译带调试信息的内核,确保配置以下选项:

Kernel hacking → Kernel debugging
Kernel hacking → Compile-time checks and compiler options → Compile the kernel with debug info
Kernel hacking → Generic Kernel Debugging Instruments → KGDB: kernel debugger

2. 文件系统准备

构建最小根文件系统(基于BusyBox),配置磁盘镜像和rcS文件。

3. 工具链准备

安装 Qemu 和 pwndbg,配置 gdb.sh 和 start.sh 脚本。

三、Linux内核源码阅读和调试技巧

1. 系统调用实现原理

Linux 系统调用是用户空间与内核交互的核心接口,其实现依赖于架构相关的中断机制和系统调用表(sys_call_table)。每个系统调用通过唯一的系统调用号索引,对应内核中的 sys_xxx 函数。

2. f_op 结构体原理

struct file_operations (f_op)定义了文件操作的函数指针,如 open、read、write 等。内核通过 file->f_op 调用这些函数,具体实现由文件系统或设备驱动提供。

3. GDB动态调试技巧

常用命令:

  • n:执行下一行源码,不进入函数内部
  • ni:执行下一条汇编指令,不进入函数内部
  • s:进入当前行调用的源码函数内部
  • si:进入call调用的函数内部,以汇编指令级别单步调试

四、管道(Pipe)机制详解

1. 管道基本概念

在Linux系统中,pipe是一种进程间通信(IPC)机制,通过pipe系统调用可以创建一个管道,返回两个文件描述符:

  • 写端(fd[1]):用于写入数据
  • 读端(fd[0]):用于读取数据

2. 管道内核实现

管道通过 struct pipe_inode_info 和 struct pipe_buffer 两个核心结构体实现:

struct pipe_inode_info {
    unsigned int head;
    unsigned int tail;
    unsigned int max_usage;
    unsigned int ring_size;
    struct pipe_buffer *bufs;
    // ...
};

struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    unsigned int flags;
    // ...
};

3. 管道读写机制

写入流程

  1. 数据按页写入 bufs[head]
  2. 更新 head 指针
  3. 若缓冲区满,写进程进入睡眠

读取流程

  1. 从 bufs[tail] 读取数据
  2. 更新 tail 指针
  3. 若缓冲区空,读进程阻塞

4. Page Cache 机制

Page Cache是内核管理的一块内存区域,用于缓存磁盘上的文件数据块(以内存页为单位,通常4KB)。其工作原理:

读操作

  • 缓存命中:直接返回内存中的数据
  • 缓存未命中:从磁盘读取数据,存入Page Cache

写操作

  • 缓冲写入(Writeback):默认修改Page Cache中的缓存页,异步刷回磁盘
  • 直写(Writethrough):同步写入磁盘(较少使用)

五、splice系统调用与零拷贝机制

1. 零拷贝机制概述

传统的文件拷贝过程(open→read→write)需要在用户态和内核态之间多次切换,并进行CPU和DMA之间的数据拷贝。而splice系统调用可以实现内核态内的"零拷贝",只需2次上下文切换。

2. splice调用链

SYSCALL_DEFINE6(splice) → do_splice → do_splice_to → generic_file_splice_read → 
generic_file_read_iter → generic_file_buffered_read → copy_page_to_iter → 
copy_page_to_iter_pipe

关键函数 copy_page_to_iter_pipe 中的 buf->page = page 实现了将文件的page_cache直接替换掉管道page,完成零拷贝。

六、Dirty Pipe漏洞原理深入分析

1. 漏洞触发条件

  1. 通过pipe_write将管道数据写满,给pipe_buffer的flags赋值为PIPE_BUF_FLAG_CAN_MERGE
  2. 通过pipe_read将管道清空,确保splice有足够拷贝空间
  3. 调用splice将只读文件内容写入管道,将目标文件的Page Cache页面关联到管道缓冲区
  4. 调用pipe_write向管道写入恶意数据,覆盖原Page Cache页面

2. 漏洞利用限制

  1. 文件需可读:攻击者必须对目标文件拥有读权限
  2. 单页覆盖限制:每次写入最多覆盖一页大小(通常为4KB)
  3. 修改临时性:仅篡改内存中的Page Cache,不会同步到磁盘

七、漏洞复现与动态调试分析

1. 复现步骤

  1. 创建只读测试文件
  2. 创建管道并填满数据
  3. 清空管道数据
  4. 使用splice将只读文件内容写入管道
  5. 使用pipe_write覆盖Page Cache

2. 关键调试点

  1. open文件结构体:观察以只读模式打开的struct file对象
  2. pipe结构体:观察pipe_inode_info和pipe_buffer结构体
  3. pipe_write/pipe_read:观察flags字段的初始化和管道状态变化
  4. splice调用链:跟踪零拷贝过程,特别是page的替换

3. 常见问题解答

为什么一定要将管道填满再清空?

  • 填满管道是为了初始化所有pipe_buffer的flags为PIPE_BUF_FLAG_CAN_MERGE
  • 清空管道是为了确保splice有足够空间进行零拷贝
  • 不完全填满会导致无法正确覆盖目标page

为什么程序会在splice调用时卡死?

当管道已满且未设置非阻塞标志时,wait_for_space会调用pipe_wait等待,导致进程阻塞。

八、漏洞利用:劫持SUID二进制文件提权

通过dirty_pipe漏洞劫持拥有root权限的SUID二进制程序:

  1. 覆盖目标二进制程序,注入恶意ELF文件
  2. 执行被覆盖的二进制程序,创建具有root权限的可执行文件
  3. 通过该文件实现权限提升

示例利用代码结构:

// 1. 打开目标SUID程序
int fd = open("/usr/bin/target", O_RDONLY);

// 2. 创建管道并准备漏洞利用条件
int p[2];
pipe(p);
for(int i=0; i<16; i++) write(p[1], "A", 1);
for(int i=0; i<15; i++) read(p[0], buf, 1);

// 3. 使用splice进行零拷贝
lseek(fd, offset, SEEK_SET);
splice(fd, &offset, p[1], NULL, 1, 0);

// 4. 写入恶意payload
write(p[1], evil_elf, evil_elf_size);

// 5. 执行被篡改的程序
system("/usr/bin/target");

九、扩展资源

  1. 漏洞公告:CVE-2022-0847 NVD详情
  2. Linux内核修复提交记录:官方修复补丁的代码提交
  3. 工具与代码
    • GitHub - n3rada/DirtyPipe:自动化利用工具
    • GitHub - veritas501/pipe-primitive:漏洞利用原语研究
    • Exploit-DB Dirty Pipe PoC:可直接编译运行的PoC
  4. 技术分析
    • DirtyPipe与Dirty Cow对比分析
    • Qualys技术深度解读
    • LWN.net内核机制解析

十、总结

Dirty Pipe漏洞通过精心构造的管道操作,利用内核在管道缓冲区管理上的缺陷,实现了对只读文件的越权写入。理解该漏洞需要深入掌握Linux内核的管道机制、Page Cache管理和零拷贝技术。通过本指南的系统性分析,读者不仅可以掌握该漏洞的原理和利用方法,还能学习到Linux内核调试和分析的基本技能,为后续的内核漏洞研究打下坚实基础。

Dirty Pipe (CVE-2022-0847) 漏洞分析与利用完全指南 一、漏洞概述 Dirty Pipe (CVE-2022-0847) 是 Linux 内核 5.8 及之后版本中存在的一个本地提权漏洞,攻击者可以通过覆盖任意可读文件的内容(即使文件权限为只读)将普通用户权限提升至 root。该漏洞源于管道(Pipe)机制与 Page Cache 的交互缺陷,与经典的 Dirty COW (CVE-2016-5195)漏洞类似,但利用更简单、影响范围更广。 漏洞核心原理 管道的"零拷贝"特性 :当通过 splice 系统调用将文件内容写入管道时,内核会直接将文件的 Page Cache 页面作为管道的缓冲区页使用,而非复制数据。 未初始化的标志位漏洞 :管道缓冲区的 flags 变量在初始化时未正确重置,导致攻击者可以错误地认为该页是可写的。 Page Cache 的覆盖效果 :文件的 Page Cache 页面被直接关联到管道缓冲区,攻击者通过向管道写入数据可覆盖 Page Cache 中的原始文件内容。 二、环境搭建与调试准备 1. 内核准备 编译带调试信息的内核,确保配置以下选项: 2. 文件系统准备 构建最小根文件系统(基于BusyBox),配置磁盘镜像和rcS文件。 3. 工具链准备 安装 Qemu 和 pwndbg,配置 gdb.sh 和 start.sh 脚本。 三、Linux内核源码阅读和调试技巧 1. 系统调用实现原理 Linux 系统调用是用户空间与内核交互的核心接口,其实现依赖于架构相关的中断机制和系统调用表(sys_ call_ table)。每个系统调用通过唯一的系统调用号索引,对应内核中的 sys_ xxx 函数。 2. f_ op 结构体原理 struct file_ operations (f_ op)定义了文件操作的函数指针,如 open、read、write 等。内核通过 file->f_ op 调用这些函数,具体实现由文件系统或设备驱动提供。 3. GDB动态调试技巧 常用命令: n :执行下一行源码,不进入函数内部 ni :执行下一条汇编指令,不进入函数内部 s :进入当前行调用的源码函数内部 si :进入call调用的函数内部,以汇编指令级别单步调试 四、管道(Pipe)机制详解 1. 管道基本概念 在Linux系统中,pipe是一种进程间通信(IPC)机制,通过pipe系统调用可以创建一个管道,返回两个文件描述符: 写端(fd[ 1 ]):用于写入数据 读端(fd[ 0 ]):用于读取数据 2. 管道内核实现 管道通过 struct pipe_ inode_ info 和 struct pipe_ buffer 两个核心结构体实现: 3. 管道读写机制 写入流程 : 数据按页写入 bufs[ head ] 更新 head 指针 若缓冲区满,写进程进入睡眠 读取流程 : 从 bufs[ tail ] 读取数据 更新 tail 指针 若缓冲区空,读进程阻塞 4. Page Cache 机制 Page Cache是内核管理的一块内存区域,用于缓存磁盘上的文件数据块(以内存页为单位,通常4KB)。其工作原理: 读操作 : 缓存命中:直接返回内存中的数据 缓存未命中:从磁盘读取数据,存入Page Cache 写操作 : 缓冲写入(Writeback):默认修改Page Cache中的缓存页,异步刷回磁盘 直写(Writethrough):同步写入磁盘(较少使用) 五、splice系统调用与零拷贝机制 1. 零拷贝机制概述 传统的文件拷贝过程(open→read→write)需要在用户态和内核态之间多次切换,并进行CPU和DMA之间的数据拷贝。而splice系统调用可以实现内核态内的"零拷贝",只需2次上下文切换。 2. splice调用链 关键函数 copy_page_to_iter_pipe 中的 buf->page = page 实现了将文件的page_ cache直接替换掉管道page,完成零拷贝。 六、Dirty Pipe漏洞原理深入分析 1. 漏洞触发条件 通过pipe_ write将管道数据写满,给pipe_ buffer的flags赋值为PIPE_ BUF_ FLAG_ CAN_ MERGE 通过pipe_ read将管道清空,确保splice有足够拷贝空间 调用splice将只读文件内容写入管道,将目标文件的Page Cache页面关联到管道缓冲区 调用pipe_ write向管道写入恶意数据,覆盖原Page Cache页面 2. 漏洞利用限制 文件需可读:攻击者必须对目标文件拥有读权限 单页覆盖限制:每次写入最多覆盖一页大小(通常为4KB) 修改临时性:仅篡改内存中的Page Cache,不会同步到磁盘 七、漏洞复现与动态调试分析 1. 复现步骤 创建只读测试文件 创建管道并填满数据 清空管道数据 使用splice将只读文件内容写入管道 使用pipe_ write覆盖Page Cache 2. 关键调试点 open文件结构体 :观察以只读模式打开的struct file对象 pipe结构体 :观察pipe_ inode_ info和pipe_ buffer结构体 pipe_ write/pipe_ read :观察flags字段的初始化和管道状态变化 splice调用链 :跟踪零拷贝过程,特别是page的替换 3. 常见问题解答 为什么一定要将管道填满再清空? 填满管道是为了初始化所有pipe_ buffer的flags为PIPE_ BUF_ FLAG_ CAN_ MERGE 清空管道是为了确保splice有足够空间进行零拷贝 不完全填满会导致无法正确覆盖目标page 为什么程序会在splice调用时卡死? 当管道已满且未设置非阻塞标志时,wait_ for_ space会调用pipe_ wait等待,导致进程阻塞。 八、漏洞利用:劫持SUID二进制文件提权 通过dirty_ pipe漏洞劫持拥有root权限的SUID二进制程序: 覆盖目标二进制程序,注入恶意ELF文件 执行被覆盖的二进制程序,创建具有root权限的可执行文件 通过该文件实现权限提升 示例利用代码结构: 九、扩展资源 漏洞公告 :CVE-2022-0847 NVD详情 Linux内核修复提交记录 :官方修复补丁的代码提交 工具与代码 : GitHub - n3rada/DirtyPipe:自动化利用工具 GitHub - veritas501/pipe-primitive:漏洞利用原语研究 Exploit-DB Dirty Pipe PoC:可直接编译运行的PoC 技术分析 : DirtyPipe与Dirty Cow对比分析 Qualys技术深度解读 LWN.net内核机制解析 十、总结 Dirty Pipe漏洞通过精心构造的管道操作,利用内核在管道缓冲区管理上的缺陷,实现了对只读文件的越权写入。理解该漏洞需要深入掌握Linux内核的管道机制、Page Cache管理和零拷贝技术。通过本指南的系统性分析,读者不仅可以掌握该漏洞的原理和利用方法,还能学习到Linux内核调试和分析的基本技能,为后续的内核漏洞研究打下坚实基础。