从exp反推CVE-2022-0847dirtypipe原理
从exp反推CVE-2022-0847dirtypipe原理
原创 SQ SQ安全渗透 2024-04-11 04:07
点击上方蓝字关注我哦
前情提要
qian qing ti yao
想着让小白也能懂这个原理
壹
漏洞简介
CVE-2022-0847,也被称为”Dirty Pipe”,是一个影响Linux内核的严重安全漏洞。这个漏洞存在于Linux内核的内存管理子系统中,攻击者可以利用这个漏洞在受影响的系统上执行任意代码,甚至可以获取系统的完全控制权。
“Dirty Pipe”漏洞的名称来源于它所影响的内核数据结构——页表。在Linux内核中,页表用于跟踪物理内存的使用情况,包括哪些内存页面已经被修改(”脏”)以及哪些还没有。由于一个设计上的缺陷,攻击者可以通过创建特殊的、恶意的页表条目来破坏页表的完整性,从而实现对系统的控制。
贰
exp解释
-
定义了两个宏 PAGE_SIZE 和 PIPE_SIZE,分别表示页面大小和管道大小。
-
PAGE_SIZE:内存页是操作系统管理内存的基本单位,它的大小通常取决于操作系统的设计和硬件架构。例如,在一些系统中,一个标准的页面大小可能是4KB(4096字节)。
-
PIPE_SIZE:管道是Unix和类Unix系统中的一个传统IPC(进程间通信)机制。它允许两个进程之间以先进先出的方式传输数据流。所谓的PIPE_SIZE是指创建管道时,内核为该管道分配的缓冲区大小,这个大小会影响到一次能在管道中写入或读取的数据量。
-
定义了一个函数 SetCanMerge,该函数接受一个整数数组作为参数,用于设置管道的合并属性。
-
在 main 函数中,首先创建了一个名为 pipefd 的整数数组,用于存储管道的文件描述符。
-
调用 SetCanMerge 函数,将 pipefd 作为参数传入,以设置管道的合并属性。(
将pipe的缓冲区填满(
两个宏 PAGE_SIZE
和 PIPE_SIZE
,分别表示页面大小和管道大小,并且每一次设置一标签(是否合并)
) -
打印一条消息,表示管道的合并属性已设置完成。
-
使用 open 函数打开文件 /etc/passwd,并以只读方式获取文件描述符,将其赋值给变量 fd。
-
调用 splice 系统调用,将文件描述符 fd 的内容复制到管道的写入端 pipefd1。
-
打印一条消息,表示 splice 操作已完成,并显示返回值。
-
向管道的写入端写入字符串 “oots:”。
由于第一个字节为零拷贝,这样的话/etc/passwd的第一行变成了roots::…,原本第一行的内容为root:x:…,中间的x表示此用户有密码,而我们把x取消掉了,那么我们生成了一个 uid 为 0 且没有密码的用户roots,
叁
环境搭建编译运行exp
安装完成之后,reboot重启,开机界面按shift+TAB进入 ubuntu 引导界面,然后选择高级选项advance,选择我们刚刚安装的那个内核进入启动。
成功替换指定的版本。
1.root-roots
2.密码为0—-roots::0:0:root:/root:/bin/bash
肆
代码分析
读和写对应的调用,它们在内核层名为
pipe_read和pip_write
pipe_read
pip_write
/source/fs/pipe.c
fd[0]和fd[1]的来源
int do_pipe(int *fd)
{
struct file *fw, *fr;
int fdw, fdr;
//创建管道写端的file结构
fw = create_write_pipe();
//在写端的file结构基础上构建读端
fr = create_read_pipe(fw);
//创建读端fd
fdr = get_unused_fd();
//创建写端fd
fdw = get_unused_fd();
//fd 和 file进行关联
fd_install(fdr, fr);
fd_install(fdw, fw);
//返回读写端fd
fd[0] = fdr;
fd[1] = fdw;
...
return 0;
}
pipe_write:
static ssize_t pipe_write(struct kiocb *iocb, struct iov_iter *from)
{
struct file *filp = iocb->ki_filp;
struct pipe_inode_info *pipe = filp->private_data;
unsigned int head;
ssize_t ret = 0;
size_t total_len = iov_iter_count(from);
ssize_t chars;
bool was_empty = false;
bool wake_next_writer = false;
/* Null write succeeds. */
if (unlikely(total_len == 0))
return 0;
__pipe_lock(pipe);
判断数据来源是否为0,是0就关闭交易
if (!pipe->readers) {
send_sig(SIGPIPE, current, 0);
ret = -EPIPE;
goto out;
}
#ifdef CONFIG_WATCH_QUEUE
if (pipe->watch_queue) {
ret = -EXDEV;
goto out;
}
#endif
head = pipe->head;
was_empty = pipe_empty(head, pipe->tail);
chars = total_len & (PAGE_SIZE-1);
PAGE_SIZE通常情况下来说大小是4096,刚好是一个 2 的 12 次幂,那么再 -1 相当于就是二进制的 12 个 1,再用 & 运算就是取得total_len最低的 12 位
PAGE_SIZE
4096
total_len
if (chars && !was_empty) {
unsigned int mask = pipe->ring_size - 1;
struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask];
int offset = buf->offset + buf->len;
if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
offset + chars <= PAGE_SIZE) {
ret = pipe_buf_confirm(pipe, buf);
if (ret)
goto out;
ret = copy_page_from_iter(buf->page, offset, chars, from);
-
计算一个掩码值,该值为管道的环大小减去1。
-
获取管道缓冲区的地址,该地址由头部指针减1后与掩码进行位与运算得到。
-
计算偏移量,该值为缓冲区的偏移量加上其长度。
-
检查缓冲区的标志位是否包含PIPE_BUF_FLAG_CAN_MERGE,并且偏移量加上chars是否小于等于PAGE_SIZE。如果这两个条件都满足,那么它将执行以下操作:
-
调用pipe_buf_confirm函数确认管道缓冲区。
-
如果返回值不为0,那么它将跳转到out标签。
-
否则,它将调用copy_page_from_iter函数,将迭代器中的数据复制到缓冲区的页面中。
Splice
splice的函数原型如下:
c复制代码运行long splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
参数说明:
– fd_in:输入文件描述符,即要读取数据的文件。
-
off_in:输入文件的偏移量指针,指向要读取数据的起始位置。如果为NULL,则从当前文件位置开始读取。
-
fd_out:输出文件描述符,即要将数据写入的文件。
-
off_out:输出文件的偏移量指针,指向要写入数据的起始位置。如果为NULL,则从当前文件位置开始写入。
-
len:要传输的数据长度。
-
flags:控制传输行为的标志位,如SPLICE_F_NONBLOCK、SPLICE_F_MORE等
伍
gdb调试
不知道为什么本地上没找到文件,额,写点gdb调试的基础方法吧
1. 编译带有调试信息的程序:在编译源代码时,使用-g选项来添加调试信息。
gcc -g program.c -o program
- 使用GDB加载程序:通过gdb命令加载编译后的程序。
gdb program
- 设置断点:在特定的代码行上设置断点,以便在执行到该行时暂停程序。
b filename:line_number
例如,在main函数的第一行设置断点:
b main.c:15
- 运行程序:使用r(run)命令运行程序,并将程序暂停在断点处。
r
-
查看当前状态:可以查看当前的堆栈状态、变量值等。
-
查看堆栈状态:info stack
-
查看局部变量:info locals
-
查看全局变量:info global
-
单步执行:使用n(next)命令单步执行程序,遇到函数调用时,可以进入函数内部。
n
- 继续执行:使用c(continue)命令让程序继续执行,直到下一个断点或程序结束。
c
- 打印变量值:使用p(print)命令打印变量的值。
p variable_name
- 退出GDB:使用q(quit)命令退出GDB。
q
关注一波
理想·致敬每一个安全人
初心不改,筑梦未来
扫码关注后台回复“安全”
获取资料
点击菜单还有精美壁纸