
在正式开始之前,说明一下,本篇文章一方面算是完成瑞不可当团队交代的作业之一,另一方面算是在各位技术大牛针对这个漏洞的分析文章的基础之上来个锦上添花。所以,要先感谢之前各位大牛针对这个漏洞(CVE-2022-0847)的详细描述,我只是从gdb调试的角度来复现这个漏洞的利用过程(本次是从用户态层面调试,下次是从内核态层面调试),参考文章很多,不一一列举,包括但不限于如下内容:
《大白话图解 Linux 脏管道(Dirty Pipe) 漏洞(CVE-2022-0847)》
https://blog.csdn.net/weixin_44820088/article/details/123364275
《Linux内核权限提升漏洞“DirtyPipe”(CVE-2022-0847)分析》
一、Exp文件的编译执行
从这个地址下载exp文件并编译:
https://haxx.in/files/dirtypipez.c
该exp执行效果简单直接,一步到位:
该漏洞的另外一种利用方法的介绍也可以参看如下链接发布的内容:
https://mp.weixin.qq.com/s/nfX29mmLbkLtmr-055oX1Q
二、EXP文件的两个要素:
整个exp有详细的备注内容,非常清晰。我这里就说两个关键要素:
关键结构体pipe-buf指向某个page,实际指向了suid文件。我们在exp执行过程中使用/usr/bin/chsh这个文件,意思就是pipe-buf指向了/usr/bin/chsh这个文件。
关键结构体pipe-buf 带着PIPE_BUF_FLAG_CAN_MERGE,意思就是还可以向这个page中写入内容。
结合这两个关键因素,就相当于可以向/usr/bin/chsh文件中写入内容,就这样一个只是可读的suid就变成了可写的文件。至于写成什么样子,就是各个exp攻击灵活的地方了。但是根本上同时构建这两个要素就是所有该漏洞exp的基础。
三、EXP执行过程中用户态动态调试
第一阶段:main函数准备环节
Gdb进入main函数:
设置main断点,一步一步调试:
这一段就是将fd中的从第1个字节开始算起,406个字节长度的内容拷贝到orig_byte中。 备份好,后面恢复的时候会使用上。
其中对应的data如下内容:
其中,/usr/bin/chsh获得的fd如下:
对应的elfcode的长度为406:
这里的0x7f被注释掉,因为用不到,因为fd会设置offset为1。
至此,main函数完成了准备阶段,将/usr/bin/chsh的offset为1之后的406个字节拷贝到orig_bytes中。
第二阶段:exp的hax函数
源码如下:
关键是进入到hax中去执行:
汇编代码进入hax函数:
解释一下:file就是/usr/bin/chsh, offset 设置为1,data指向我们构建好的elfcode,len就是406个字节。
是的,再次打开suid文件:
是的,再次得到3:
第三阶段:进入prepare函数
进入这个函数的目的就是实现关键要素里面的第二个要素。
此时p[2]
栈上的一堆乱码而已。完成pipe之后:
接着两个for循环:
其中buffer的大小为4096,
而r的初始大小为65536两个for循环的操作下来的效果就是16个page,连续注入和然后连续清空。目的就是保存flag为0x10,就是can merge后续的内容,这里留个悬念,我会在复现内核态的gdb调试效果里面展开。
第四个阶段:进入splice函数
这个环节主要目的就是实现exp的第一个要素:
还记得上面获得pipe
这里就是说,根据如下的描述:
从/usr/bin/chsh 这个文件的offset为0的地方开始,move(传递,搬动)一个1字节的内容到,pipe的出口fd的offset为0的地方。
Flag为0,不是上面这些数值,说明正常处理,不做其他特殊处理:
这里的splice的效果非常妙,是整个漏洞的核心,在内核态(很快我会来补充这个漏洞在内核态的gdb的调试效果),效果就是将/usr/bin/chsh的page,挂到了pipe管道结构体中的pipe_buffer上。从这张图中的信息,就是pipe-buf通过splice函数的操作,直接指向了我们在程序设置/usr/bin/chsh文件,碰巧同时pipe-buf指向的page还保留了PIPE_BUF_FLAG_CAN_MERGE这个flag。
第五阶段:改造可写的suid文件
目前为止,已经凑齐了两个要素,剩下的就是充分利用内存中这个可写的suid文件page:
接着向pipe中写入我们之前准备的elfcode内容。
这里的data就是我们前面准备的elfcode,而len就是elfcode的长度。Write完成之后,查看一下nbytes的数值:
Ok,说明顺利将elfcode写入到了suid文件中,现在的suid就是我们想要的样子了。下面就好办了。
最后hax函数收尾。下面就又回到main函数中了。
第六阶段:执行全新的suid文件和收尾
Call hax之后就回到main了:
这个阶段的源码如下三个部分:
第一个部分就是先执行新的suid程序,最终会产生能够产生/tmp/sh文件,该文件可以启动root权限的shell。
第二个部分就是还是使用hax函数来恢复我们修改过的suid的文件为最初的suid,如何恢复,使用我们最初保存的orig_bytes,这样做还是比较讲究的,执行完收尾工作。
第三个部分就是整个exp的结尾,启动具有root权限的shell
这个阶段的三个部分调试之前,在还未执行该system(path)之前,tmp文件夹的内容如下:
之所以,这个三个部分一起调试,是因为system函数会产生子进程,该子进程回去执行elfcode的操作:
执行一下,gdb直接到了shell:
同时tmp文件下也产生了对应的sh文件。
可以看到tmp文件夹下也生成的sh文件。至此,gdb在用户态的调试也结束了,可以看到顺利拿到shell。后续,会详细分析内核态下两个关键要素的状态。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)