Dirty COW漏洞原理与简单利用
我将分为3个部分进行介绍,包括:漏洞的概述,漏洞的成因以及漏洞的利用。
首先是漏洞概述:
Dirty COW漏洞是一种发生在写时复制的竞态条件漏洞,它影响所有基于Linux的操作系统,包括Android,这个漏洞2007年起就存在于Linux内核中,直到2016年才被发现和修复。可以利用这个漏洞修改受保护的文件,也可以利用这个漏洞提权。
Dirty COW漏洞是发生在写时复制的竞态条件漏洞,我们先看看什么是竞态条件和写时复制。
竞态条件是指一个系统或者进程的输出依赖于不受控制的事件出现顺序或者出现时机。我们看下边的这个例子:这个php代码的功能为从银行执行取款交易,首先检查要提取的金额是否少于余额,如果是,则授权,之后更新余额并退出。那么,如果同时有两个提款请求,则可能会出现竞态条件漏洞。比如说,当前余额是100元,线程1要求提取90元,在服务器更新余额之前,线程2尝试提取90元,这将被批准,因为当前的余额仍然是100元,因此总共提取了180元,账户中的余额为10元。
竞态条件通常发生在多个进程(线程)同时访问和操作相同的数据 以及 执行结果取决于特定的顺序这两种情况。那么如果特权程序具有竞态条件漏洞的话,攻击者就可以通过对不可控事件施加影响来影响特权程序的输出。
好,我们接下来再看一下写时复制:写时复制是一种允许不同进程中的虚拟内存映射到相同物理内存页面的技术。比如我们使用fork
系统调用创建子进程,那么这个子进程通过使页表条目指向相同的物理内存来共享父进程内存,当任何进程试图写入内存时,会引发异常,OS将为子进程分配新的物理内存,从父进程复制内容,更改每个进程的页表使它指向自己的私有内存副本,这就是写时复制技术。
写时复制执行过程执行三个重要步骤,包括:制作映射内存的副本;更新页表,使虚拟内存指向新创建的物理地址;写入内存。由于三个步骤不是原子性的,一个线程在执行这三个步骤过程中可能被其他线程中断,从而产生潜在的竞态条件。
那么,什么地方会利用到内核的写时复制机制呢?我们来看一个比较常见的系统调用mmap(),它是一个将文件或者设备映射到进程内存的系统调用,也就是说对进程内存的读写就是对文件的读写。
这个系统调用包含图中的几个参数:前两个参数是映射内存区域的起始地址和大小,第三个参数是读写方式,即是否可读是否可写,第四个参数指定映射的方式,包括map_shared和map_private,map_shared是指当多个线程将同一个文件映射到自己的虚拟地址中,它们都共享同一个物理内存块,说通俗点就是共享内存,map_private则是将文件映射到进程的私有内存,这个后一页重点介绍,后两个参数是映射的文件以及文件内偏移。
map_private参数指定将文件映射到进程的私有内存。从图中可以看到,两个进程将同一个文件映射到自己的虚拟内存地址,如果两个文件都是只读的,那么虚拟内存地址将指向同一个物理内存块。但是如果一个进程试图写入数据,就像图中的进程2,此时就会发生写时复制,将物理内存块复制一个副本,然后更新进程2的页表指向新的内存块,最后向物理内存块的副本中写入数据。
**这里需要注意的是,**即使程序是以只读的方式来做内存映射,map_private允许程序通过WRITE系统调用往物理内存块的副本中写入数据,这为我们后面的利用创造了条件。
前面我们知道了写时复制时会有竞态条件漏洞呢?那么该如何利用呢,这里介绍另一个系统调用,madvise(),这个调用通过指定第三个参数为MADV_DONOTNEED告诉内核不再需要声明地址部分的内存,内核将释放该地址的资源,进程的页表会重新指向原始的物理内存。
组合使用mmap和madvise就可以利用写时复制的竞态条件漏洞,这张图展示了整个过程。
先看右边的图,这是一个进程将只读文件映射到进程的虚拟内存地址中,当mmap指定参数为map_private,尽管是只读的,仍然可以写入数据,只不过这时写到的是原始物理内存的副本。
左边的图,步骤A,B,C,是写时复制的三个步骤,首先创建映射内存的副本,然后将进程页表指向原始物理内存的副本,最后向副本写入数据。
那么当同一个进程的线程1执行写时复制过程,具体的就是执行完步骤B但还没执行步骤C,另一个线程2调用了madvice(),那么进程的页表将重新指向原始的映射的内存物理块,线程1继续执行步骤C会向只读文件写入数据。
这就是整个Dirty-cow漏洞的利用过程。
最后,我们看一下漏洞利用。利用的基本思路是在一个进程里创建两个线程,一个线程向只读的映射内存通过Write系统调用写入数据,这时候发生写时复制,另外一个线程通过madvise系统调用来丢弃映射内存的私有副本,这两个线程相互竞争从而向只读文件写入数据。
这是我们的主程序,它将指定的只读文件映射到物理内存块中,参数是MAP_PRIVATE,然后创建两个线程。
这个线程通过访问Linux的/proc文件系统来访问所在进程的内存地址空间,并通过Write系统调用不断尝试向映射的物理内存块写入数据,这时候发生写时复制。
这个线程通过madvise系统调用不断让系统释放原始映射的物理内存块的副本,这样总能产生一个时序使得写时复制的线程将数据写到原始的映射内存物理块中。
这是Linux内核版本,当前用户无root权限。
编译利用程序。
准备测试文本文件,这个文件对普通用户是只读的。
然后在普通用户下运行攻击程序,第一个参数是目标文件,第二个参数是写入的数据,可以看到成功向只读文件写入数据。
第二个复现内容是修改/etc/passwd文件提权,其实这个过程和上一个实验过程是一样的,只不过目标文件换成了/etc/passwd,向文件中写入一行来创建一个新的用户,将uid设置成0就可以了。下面看一下攻击结果。
这是环境。
编译的过程
最后总结一下,漏洞需要较早的内核版本,该漏洞可以在低权限下提升对文件的操作权限,也可以获得root权限。
更多推荐
所有评论(0)