脏牛(DirtyCow)Linux本地提权漏洞复现(CVE-2016-5195)

漏洞名称:脏牛漏洞(Dirty COW)

漏洞编号:CVE-2016-5195

漏洞概述:

  Linux内核的内存子系统的get_user_page内核函数在处理Copy-on-Write(写时拷贝,以下使用COW表示)的过程中,存在条件竞争漏洞,导致可以破坏私有只读内存映射。一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可能进一步导致提权漏洞(修改su或者passwd程序就可以达到root的目的)

影响范围:Linux kernel >= 2.6.22(2007年发行,直到2016年10月18日才修复)

提权为root权限的EXP一:  https://github.com/FireFart/dirtycow

测试环境(centos6.5)

1、查看系统内核版本:Linux kernel >2.6.22

2、查看用户信息:普通用户

 3、将exp一下载到本地,使用gcc -pthread dirty.c -o dirty -lcrypt命令对dirty.c进行编译,生成一个dirty的可执行文件

 4、执行./dirty 密码命令,即可进行提权。

5、漏洞原理及分析

5.1 脏牛漏洞回顾
在分析大脏牛漏洞前,我们需要对原始的脏牛漏洞利用方式进行完整的分析理解: 之前的漏洞是在get_user_pages函数中,这个函数能够获取用户进程调用的虚拟地址之后的物理地址,调用者需要声明它想要执行的具体操作(例如写/锁等操作),所以内存管理可以准备相对应的内存页。具体来说,也就是当进行写入私有映射的内存页时,会经过一个COW(写时拷贝)的过程,即复制只读页生成一个带有写权限的新页,原始页可能是私有保护不可写的,但它可以被其他进程映射使用。用户也可以在COW后的新页中修改内容之后重新写入到磁盘中。         

现在我们来具体看下get_user_pages函数的相关代码:

5.2、现在我们来具体看下get_user_pages函数的相关代码:

整个while循环的目的是获取请求页队列中的每个页,反复操作直到满足构建所有内存映射的需求,这也是retry标签的作用;

follow_page_mask读取页表来获取指定地址的物理页(同时通过PTE允许)或获取不满足需求的请求内容;

在follow_page_mask操作中会获取PTE的spinlock,用来保护试图获取内容的物理页不会被释放掉;

faultin_page函数申请内存管理的权限(同样有PTE的spinlock保护)来处理目标地址中的错误信息;

在成功调用faultin_page后,锁会自动释放,从而保证follow_page_mask能够成功进行下一次尝试,以下是涉及到的代码。         

原始的漏洞代码在faultin_page底部:

上面这个判断语句想要表示的是,如果当前VMA中的标志显示当前页不可写,但是用户又执行了页的写操作,那么内核会执行COW操作,并且在处理中会有VM_FAULT_WRITE标志。换句话说在执行了COW操作后,上面的if判断为真,这时就移除了FOLL_WRITE标志。         

一般情况下在COW操作后移除FOLL_WRITE标志是没有问题的,因为这时VMA指向的页是刚经过写时拷贝复制的新页,我们是有写权限的,后续不进行写权限检查并不会有问题。 但是,考虑这样一种情况,如果在这个时候用户通过madvise(MADV_DONTNEED)将刚刚申请的新页丢弃掉,那这时本来在faultin_page后应该成功的follow_page_mask会再次失败,又会进入faultin_page的逻辑,但是这个时候已经没有FOLL_WRITE的权限检查了,只会检查可读性。这时内核就会将只读页面直接映射到我们的进程空间里,这时VMA指向的页不再是通过COW获得的页,而是文件的原始页,这就获得了任意写文件的能力。   

基本来看,上述的过程流也就是脏牛漏洞的利用过程。   

在faultin_page中有对应的修复补丁:

 

同时也加入了另一个新的follow_page_mask函数:

 与减少权限请求数不同,get_user_pages现在记住了经过COW循环的过程。之后只需要有FOLL_FORCE和FOLL_COW标志声明过且PTE标记为污染,就可以获取只读页的写入操作。

5.2大脏牛漏洞分析

THP通过PMD(Pages Medium目录,PTE文件下一级)的_PAGE_PSE设置来打开,PMD指向一个2MB的内存页而非PTEs目录。PMDs在每一次扫描到页表时都会通过pmd_trans_huge函数进行检查,所以我们可以通过观察PMD指向pfn还是PTEs目录来判断是否可以聚合。在一些结构中,大PUDs(上一级目录)同样存在,这会导致产生1GB的页。 仔细查看脏牛补丁中关于THP的部分,我们可以发现大PMDs中用了和can_follow_write_pte同样的逻辑,其添加的对应函数can_follow_write_pmd:

 然而在大PMD中,一个页可以通过touch_pmd函数,无需COW循环就标记为dirty:

这个函数在每次get_user_pages调用follow_page_mask试图访问大页面时被调用,很明显这个注释有问题,而现在dirty bit并非无意义的,尤其是在使用get_user_pages来读取大页时,这个页会无需经过COW循环而标记为dirty,使得can_follow_write_pmd的逻辑发生错误。

在此时, 如何利用该漏洞就很明显了,我们可以使用类似脏牛的方法。这次在我们丢弃复制的内存页后,必须触发两次page fault,第一次创建它,第二次写入dirty bit。

调用链如下:

经过这个过程可以获得一个标记为脏的页面,并且是未COW的,剩下的就是要获取FOLL_FORCE和FOLL_COW标志了。这个过程可以采取类似dirtyCOW的利用方式。

 

总结这个漏洞利用的思路如下:   

①首先经过COW循环,获取到FOLL_COW标志
②用madvise干掉脏页
③再次获取页将直接标记为脏
④写入

“脏牛”漏洞是Linux内核之父Linus亲自修复的,他提交的补丁单独针对“脏牛”而言并没有问题。从我们的分析过程中发现,内核的开发者希望将“脏牛”的修复方法引用到PMD的逻辑中,但是由于PMD的逻辑和PTE并不完全一致才最终导致了“大脏牛”漏洞。

 

posted @   无名之辈。  阅读(7807)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示