Lab 4: Buffer Overflows - Internetwork Security | ECE 4112, Lab Reports of Electrical and Electronics Engineering

Material Type: Lab; Class: Internetwork Security; Subject: Electrical & Computer Engr; University: Georgia Institute of Technology-Main Campus; Term: Spring 2004;

Typology: Lab Reports

Pre 2010

Uploaded on 08/05/2009

koofers-user-otb
koofers-user-otb 🇺🇸

9 documents

1 / 21

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
1
Group Number:______
Member Names: ____________ _____________
ECE 4893 Lab 4: Buffer Overflows
Version 2/2/04
Date Issued: 2/3/04
Due Date: 2/10/04
Lab Purpose:
This lab will introduce you to the memory stack used in computer processes and
demonstrate how to overflow memory buffers in order to exploit application security
flaws. You will then execute several buffer overflow attacks against your Linux and
Windows XP machines in order to gain root or administrative access using application
vulnerabilities.
Before You Begin
Carefully read the entire article Smashing the Stack for fun and profit by Aleph One. It is
essential that you have a thorough understanding of this article before you attempt these
attacks, and although the author’s computer system differs from ours, it will be useful as
a reference during the lab. After completion of the lab, turn in answers to the questions
found at the end this document.
Background
Although computer programs are frequently written in English-based user-friendly
languages such as C, they must be compiled to an assembly language built for the
machine on which they will be executed. The assembly language has much fewer
commands than C, and these commands are much less varying in structure and less
obvious semantically. Commands are stored in memory so that each is referenced by its
location in memory rather than its line number in the code. Commands are executed
sequentially, and functions are executed by jumping to a particular memory location,
continuing sequential execution, and jumping back at the end of the function.
A tutorial describing conversion from C code to x86 assembly can be found at:
http://linuxgazette.net/issue94/ramankutty.html
When a computer process is executed, it gains access to a portion of the computer’s
memory system. In the lower set of addresses of the allocated memory, the compiled
assembly instruction set is placed so that the computer can execute these instructions
directly from memory. This part of memory is generally flagged as read-only, and
attempting to modify it results in a segmentation fault. Segmentation faults can occur for
other reasons as well, such as if an invalid instruction is executed. At a higher portion of
addresses, variables are allocated and stored. Whenever a process saves some data to
memory (e.g. int a=4), they are placed in this region.
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15

Partial preview of the text

Download Lab 4: Buffer Overflows - Internetwork Security | ECE 4112 and more Lab Reports Electrical and Electronics Engineering in PDF only on Docsity!

Group Number:______ Member Names: ____________ _____________

ECE 4893 Lab 4: Buffer Overflows Version 2/2/ Date Issued: 2/3/ Due Date: 2/10/

Lab Purpose:

This lab will introduce you to the memory stack used in computer processes and demonstrate how to overflow memory buffers in order to exploit application security flaws. You will then execute several buffer overflow attacks against your Linux and Windows XP machines in order to gain root or administrative access using application vulnerabilities.

Before You Begin

Carefully read the entire article Smashing the Stack for fun and profit by Aleph One. It is essential that you have a thorough understanding of this article before you attempt these attacks, and although the author’s computer system differs from ours, it will be useful as a reference during the lab. After completion of the lab, turn in answers to the questions found at the end this document.

Background

Although computer programs are frequently written in English-based user-friendly languages such as C, they must be compiled to an assembly language built for the machine on which they will be executed. The assembly language has much fewer commands than C, and these commands are much less varying in structure and less obvious semantically. Commands are stored in memory so that each is referenced by its location in memory rather than its line number in the code. Commands are executed sequentially, and functions are executed by jumping to a particular memory location, continuing sequential execution, and jumping back at the end of the function.

A tutorial describing conversion from C code to x86 assembly can be found at: http://linuxgazette.net/issue94/ramankutty.html

When a computer process is executed, it gains access to a portion of the computer’s memory system. In the lower set of addresses of the allocated memory, the compiled assembly instruction set is placed so that the computer can execute these instructions directly from memory. This part of memory is generally flagged as read-only, and attempting to modify it results in a segmentation fault. Segmentation faults can occur for other reasons as well, such as if an invalid instruction is executed. At a higher portion of addresses, variables are allocated and stored. Whenever a process saves some data to memory (e.g. int a=4), they are placed in this region.

Finally, the highest portion of addresses contains the memory stack. The stack helps coordinate the hierarchical execution of functions within applications. When a function is called, a variable known as the frame pointer is pushed onto the stack, which references the memory locations of variables local to the function. Next, since a function is executed by a jump from a different location in memory, a return address is pushed onto the stack so that the computer knows where in memory to return once the function has been completed. Finally, when a function is passed variables (e.g. myfunc(a,b,c)) these variables are also placed on the stack.

Theoretically, stack manipulation should be accomplished entirely by the process, which allocates and sets pointers and variables at appropriate stages of execution, such as function calls. The key to buffer overflow attacks is to maliciously manipulate the data in the stack. By changing the return pointer, for example, it is possible for the process to jump to a memory location containing user data rather than the correct location in the instruction set. If the user data is crafted to include malicious assembly commands, such as a backdoor, these will be executed.

Read the paper carefully and try to understand how the exploit works. Use the following commands to view the assembly code for the example:

gdb example disassemble main

Observe the assembly code memory addresses to determine the correct change of the return address value in order to skip the x=1 line. Modify line 7 of the code appropriately.

Next, modify the return variable pointer (line 6 of example.c). The location of the return address pointer in memory cannot be calculated explicitly, because it is dependent on your memory stack. You must therefore use trial and error to determine the offset between buffer1 and the return pointer. As a guide, remember that the offset must be at least the size of buffer1 and the frame pointer, and that although the offset is in bytes, it must be an integral number of words.

Record the necessary code changes.

Exercise 4: Creating a Shell Source file: shellcode.c

Shellcode.c executes a shell within its process, which we can then use to execute other system commands. Observe the source file.

Type the following: gcc –o shellcode –ggdb –static shellcode.c gdb shellcode disassemble main diassemble __execve

Make sure you have a clear understanding of the assembly code, using the paper as a guide.

We are going to use this code to execute a shell from a victim program. First, however, we want to make sure that after our shell code is executed our process quits. Otherwise, if we were executing our code from inside data memory, the computer could continue to execute data in memory, causing a core dump or segmentation fault. We should view the assembly translation of the exit command so that we can use this to stop the execution of our shell code after the __execve call. This can be done by observing exit.c.

gcc –o exit –ggdb –static exit.c gdb exit disassemble _exit

In this case, the exit function is operating by pushing 0x1 to %eax and the exit code (%edx) to %ebx.

Now, we must add a hex representation of the binary code to our program’s data memory so that we can execute the binary code at runtime. First, let’s observe the assembly code for our task. In separate windows, open the disassembled shellcode , the disassembled exit , and the source file shellcodeasm.c. Notice that the essential components of shellcode and exit have been placed into shellcodeasm. We have changed our error code to a static zero.

Type: gcc –o shellcodeasm –g –ggdb –static shellcodeasm.c gdb shellcodeasm disassemble main

to view your code.

Our intentions are now to copy these assembly commands into memory so that we can have a process execute them. To do this, you must see the binary machine code representations of the assembly code. Type x/bx main to see the representation of the first line. For any other line (e.g. main+3) simply include the offset (e.g. x/bx main+3 ).

We can now test our shell code by placing this machine code in data memory and executing it. Observe testsc.c. The machine code values determined from x/bx above have been placed in the character array shellcode. We then change the return pointer to point to the beginning of this array so that the data is executed.

gcc –o testsc testsc.c ./testsc

After running this, you should have access to the shell prompt $ exit

This exploit worked, but because we are storing it as an array of characters, the null character ( \x00 ) in our string could potentially cause problems. To solve this problem, we rewrite the assembly code to remove all null bytes ( shellcodeasm2.c ). Using the same process as above, we then extract the machine code once again, this time without any null characters for our string.

gcc –o shellcodeasm2 –g –ggdb shellcodeasm2.c gdb shellcodeasm gcc –o testsc2 testsc2.c ./testsc You should now have a shell $ exit

Using the instructions below and your knowledge of buffers, attempt to use exploit2 to create a shell. If you do not receive a shell or you receive an error message, you have failed and should attempt a different buffer size and offset. If you are not successful after ten tries, record your attempted values and why you chose them , and continue with the lab. You will not lose credit for not succeeding in this exploit as long as you justify your choices for buffer sizes and offsets.

./exploit2 [buffersize][offset] ./vulnerable $EGG exit (if you succeeded in creating a shell) exit (to leave a shell that exploit2 has spawned)

You may have realized that it is fairly hard to guess the correct exact offset to execute the shell code. The next variation includes a string of NOP instructions to simplify redirection.

Observe the file exploit3.c. Rather than placing our shell code at the beginning of our buffer, we now fill the first half of the buffer with NOPs, or instructions that have no effect, and start our shell code halfway through the buffer. Modify your buffer size and offset, repeating until your exploit succeeds.

./exploit3 [buffersize][offset] ./vulnerable $EGG exit (if you succeeded in creating a shell) exit (to leave a shell that exploit3 has spawned)

Record your successful offset and explain why this exploit was easier than exploit****.

Sections II – A Real World Exploit

imapd is a mail server program released by the University of Washington. This is a beta version that is vulnerable to buffer overflows. An exploit on imap is particularly dangerous because the daemon can be run as root, therefore anyone who compromises the application has root access to the entire system. The following links give more information about this exploit.

  • CERT Advisory CA-1997-09 and CA-1998- http://www.cert.org/advisories/CA-1997-09.html http://www.cert.org/advisories/CA-1998-09.html
  • Bugtraq Mailing List – this link gives detailed explanation of the exploit http://www.securityfocus.com/archive/1/
  • Imapd Buffer Overflow Discussion http://www.securityhorizon.com/whitepapers/hvah/imapd.html http://www.washington.edu/imap

Running imapd exploit on the same machine as the imapd server:

Open RedHat 7.2 which will be used for this exploit. Connect to the Network Attached Storage and copy Lab4/imapd_exploit.tgz to your local machine. Extract the file by typing: tar zxvf imapd_exploit.tgz then enter the directory by typing cd imapd_exploit

View the network services currently running by typing nmap localhost

Imap should not be running at this time. Install imapd and netcat by typing make make install make restart

Rerun nmap to make sure that imap2 is now running on the machine. Note the port on which imap2 is running.

The exploit code needs netcat (/usr/sbin/nc, installed above) and cat in combination to perform the buffer overflow. Netcat is the swiss army knife of the Internet, and has many functions including the ability to send streams of data to a destination.

The exploit code makes use of a string of 942 NOP instructions and allows the user to input an offset from which to attempt the attack.

Section III – Common Vulnerabilities

Introduction: In order to further experiment with buffer overflows and their effects, four programs have been created with specific types of vulnerabilities. Copy the file Lab4/Vulnerable.tgz to your RedHat8.0 machine and then extract the file. We employ the last exploit program from Section I to attack the vulnerable programs.

Type make to compile the programs: adjacent , cmdline , input , and remote. The Aleph One exploit has also been included, and has been entitled exploit.

Buffer Overrun: Before getting into the more detailed stack smashing attacks we will show a simple buffer overrun vulnerability. The source file adjacent. c (compiles to adjacent ) has two adjacent buffers, storeddata and userinput , both 16 characters (or bytes) long. If you read the source file, you will notice that both buffers have their contents unconditionally set to zero. Occasionally you may encounter a dirty stack frame and your buffers will not be completely empty. The string function bzero( buffer_pointer, buffer_length) is used to clear out a buffer.

The program will print out the contents of the two buffers to show you what is in them. The buffer storeddata will always contain the string "abcdefghijkl". In the source file you will notice an explicit null character, '\0', is added to the end of the string. Without a terminating null character, most string functions will not stop at the end of the string's buffer. This is a critical piece of information.

A buffer overrun occurs when a string function reads past the end of a string (or character) buffer. In this example, you will see that the program does not allow the user to input more data into storedata than the size of the array, 16. Logically it is correct. Now lets try out the "adjacent" program.

  1. Enter a string of some length less than 16 (not including the newline or carriage return) by typing ./adjacent and then at the prompt enter your own string. The final print statement will show you the proper length and contents of the each buffer on the screen. Note this works just as you expect.
  2. Enter a string of length exactly 16 (not including the newline or carraige return) In the final print statement, you will notice that inputbuffer is now 28 characters long. storeddata remains the same length and its contents are untouched. How can inputbuffer have 28 characters in it, it is only allowed 16?
  3. enter a string of length greater than 16 (not including the newline or carriage return)

In the final print statement you will notice that inputbuffer is again 28 characters long and storeddata is untouched.

What is the cause of this behavior?

The Exploit Program: This program is similar to the final exploit of Section 1, but has been reformatted for legibility with comments added to it.

In Section 1, we used the exploit program to overflow a relatively large buffer, so we were able to copy all of the shell code to this buffer and then copy the return address afterwards to the return pointer. The exploit program does not work very small buffers, because the NOP sled or shellcode itself would overwrite the return instruction pointer. In order to target a small buffer you should instead start with the memory locations, followed by the NOP sled leading into the shellcode.

Open the include file stackinfo.h , which contains a simple macro called SHOW _ STACK and a function called inspect _ buffer. The two combine to provide users with information about the original saved return instruction pointer and let one see if the return instruction pointer if will redirect to somewhere in the NOP sled.

SHOW_STACK prints:

  • The value of the Stack pointer register
  • The value of Frame pointer register and the value that the Frame pointer register points to (which is the saved frame pointer of the previous stack)
  • The memory location of the saved return pointer and the memory location that the saved return pointer points to.

Example: Stack:bffff4c0, Frame:bffff6c8[bffff708], Next Instruction:bffff6cc[40046507]

The function inspect _ buffer takes as input the buffer containing attack code. If you don't run exploit before this function is called, you are very likely to crash the example programs because they search for the environment variable called EGG. inspect _ buffer will report back to the user what memory locations contain NOPS so as to help us find if the return instruction pointer points to somewhere in the NOP sled. This may not be the case if we have not over written the return instruction pointer or have not put our buffer into the stack at a good point.

Here is some sample output from inspect_buffer:

Targetting range bffff775 to bffff88e [280 byte range] Stack Overwrite from bffff8c1 to bffff9d5 [280 byte range] bffff88e <= bffff898 < bffff775? 281 <= 291 < 0

Task 1: Use a buffer overflow to exploit command line vulnerabilities

Cmdline is very similar to the first program we observed, vulnerable. A value of arbitrary size from the command line is copied to a buffer which is only 512 bytes. Cmdline differs in that it displays a significant amount of extra information about the stack at runtime. Because the program includes new functions and function calls, the results will not be identical to that of vulnerable. The functions will, however, give extra data that will aid in the analytical selection of buffer sizes and offsets, rather than simply using trial and error.

Example of exploiting cmdline:

Observe a sample run of cmdline using the default buffer size and offset.

[root@fred lab]# ./exploit Using address: 0xbffffa [root@fred lab]# ./cmdline $EGG Inspect environment variables Targetting range bffffc95 to bffffd7c [230 byte range] Stack Overwrite from bffffdad to bffffe91 [232 byte range] bffffd7c <= bffffa88 < bffffc95? 231 <= -525 < 0 Inspect argument variables Targetting range bffff83d to bffff924 [230 byte range] Stack Overwrite from bffff955 to bffffa39 [232 byte range] bffff924 <= bffffa88 < bffff83d? 231 <= 587 < 0 =========================== Stack:bffff4c0, Frame:bffff6c8[bffff708], Next Instruction:bffff6cc[40046507] Stack:bffff4c0, Frame:bffff6c8[bffff708], Next Instruction:bffff6cc[40046507] =========================== Inspect stack variables Targetting range bffff4c0 to bffff5a7 [230 byte range] Stack Overwrite from bffff5d8 to bffff6bc [232 byte range] bffff5a7 <= bffffa88 < bffff4c0? 231 <= 1480 < 0 [root@fred lab]# exit

This did not result in the shell we desired. Let’s observe why.

In the output, the value 0xbffffa88 is the target for the NOP sled. Next we see some information about the memory contents.

The ENV part of the stack contains our environment variable, $EGG. Inspect environment variables Targetting range bffffc95 to bffffd7c [230 byte range] Stack Overwrite from bffffdad to bffffe91 [232 byte range] bffffd7c <= bffffa88 < bffffc95? 231 <= -525 < 0

NOPS have been found in the address range of bffffc95 to bffffd7c We are not overwriting the stack in this part, so ignore the second line. The third line tells us that the place where the NOP sled was found (which is in $EGG ) is –525 locations away from the local stack pointer. Since this is the environment part of the stack we do not really care where the NOP sled is in the environment part of the stack, we just wanted the program to put it in there somewhere so we can copy it later.

The next part of the example output is from the ARGV part of the stack, to which we have copied $EGG. Inspect argument variables Targetting range bffff83d to bffff924 [230 byte range] Stack Overwrite from bffff955 to bffffa39 [232 byte range] bffff924 <= bffffa88 < bffff83d? 231 <= 587 < 0

Here we see the location of the copy of $EGG’s NOP slide. The last line tells us that the target address bffffa88 does not fall within the NOP sled that is in the argument part of the stack.

The output now shows us

Stack:bffff4c0, Frame:bffff6c8[bffff708], Next Instruction:bffff6cc[40046507] Stack:bffff4c0, Frame:bffff6c8[bffff708], Next Instruction:bffff6cc[40046507] =========================== The important values here are the contents of the next instruction pointers. The next instruction which is contained in memory location bffff6cc has the value of [40046507] both before and after our copy. The next instruction has not changed, therefore we have not successfully overwritten the next instruction pointer.

The output now shows us some information from the Stack “part of the stack.”

Inspect stack variables Targetting range bffff4c0 to bffff5a7 [230 byte range] Stack Overwrite from bffff5d8 to bffff6bc [232 byte range] bffff5a7 <= bffffa88 < bffff4c0? 231 <= 1480 < 0

We have overwritten our return address in the stack from bffff5d8 to bffff6bc, but we know from above that we did not change the return address pointer, so it is not in this range. The return address we have written, bffffa88 , is outside of our NOP sled, bffff4c0 to bffff5a7.

We have two problems at the moment with the values we are using in the exploit program. First, we did not overwrite the return instruction value. Second, the address we overwrote many times would not place us on our NOP slide.

Let’s first try to fix the first problem of not overwriting the next instruction pointer value. We do this by adding an argument to use a buffer size of 612 instead of the default 512. (After all the buffer we are trying to overflow is 512 bytes in size).

Now we try again by doing the commands ./exploit 612 ./cmdline $EGG

[root@fred lab]# ./exploit 612 Using address: 0xbffffa [root@fred lab]# ./cmdline $EGG …. Stack:bffff400, Frame:bffff608[bffff648], Next Instruction:bffff60c[40046507] Stack:bffff400, Frame:bffff608[bffffa78], Next Instruction:bffff60c[ bffffa78 ] =========================== Inspect stack variables Targetting range bffff400 to bffff519 [280 byte range] Stack Overwrite from bffff54c to bffff660 [280 byte range] bffff519 <= bffffa78 < bffff400? 281 <= 1656 < 0

please input a 16 character string: Inspect stack variables Targetting range bffff6d0 to bffff7b7 [230 byte range] Stack Overwrite from bffff7e8 to bffff8cc [232 byte range] bffff7b7 <= bffff6e4 < bffff6d0? 231 <= 20 < 0 Stack:bffff6d0, Frame:bffff8d8[bffff918], Next Instruction:bffff8dc[40046507]

Notice we did not overwrite the next instruction pointer since its value did not change before and after. Thus we need to increase the buffer size from 512 to something larger so as to overwrite it. Note however, we were successful in the exploit choice of a return address pointing to inside the NOP sled.

Record the buffer size and offset used to successfully exploit input****.

Task 3: Use buffer overflow to exploit network vulnerabilities

Observe the source code for remote.c. In this application, the function gets causes information sent over the network to the application’s listening port to be copied to memory. We will use netcat to send our malicious $EGG input.

Open a terminal and run: ./remote 4200 (4200 is the port on which that the remote server will be running)

In a second terminal run: ./exploit [buffersize] [offset] (echo $EGG;cat) | nc localhost 4200

If this exploit runs properly, you should have access to the shell at the end (not necessarily with a prompt). You may have to change the buffer size and the offset values in the exploit call.

Record your successful buffer size and offset values.

Copy your output to a text file using cut and paste and turn in the output and the output of a successful run of whoami from the exploited shell prompt.

Section IV – A Contemporary Vulnerability

Buffer overflow vulnerabilities are extremely common in modern computing. In the final section, we will observe a vulnerability that exists in Windows 2000 Serivce Packs 0- and Windows XP Service Packs 0-1. The vulnerability was eventually patched in August,

The vulnerability makes use of the Windows DCOM RPC service, which is run automatically with Administrator privileges on both TCP and UDP port 135. The service is designed to allow computers to request services from each other, however it does no size checking on the input buffer.

Connect to Network Attached Storage, and copy the file Lab4/dcom.c to your RedHat 8. machine. Compile dcom with: gcc –o dcom dcom.c Now run ./dcom to see your command line options.

Your target will be your Windows XP Machine. It is currently not running any service packs (SP0).

Run the exploit, and if you receive a command prompt execute several commands such as dir and cd. Copy your results to a text file

After you exit, this exploit will cause Windows XP to crash. Take a screenshot of your crashing system and include it with your report.

(Note: depending on the status of your Windows XP system, it is possible that this vulnerability will cause XP to crash immediately. If so, allow it to reboot and try again).

For a very long time, this exploit could be used to install a backdoor and/or crash any Windows 2000 or Windows XP machine remotely. What steps could a system administrator take to prevent this problem?

  1. How were we able to modify the assembly code to remove null characters?

Writing an Exploit

  1. exploit2 successful buffer size and offset or ten attempts and reasoning:

  2. exploit3 successful buffer size and offset:

  3. Why was it so much easier to utilize exploit3?

Section II imapd

  1. imapd-ex successful offset:

  2. What privileges did you gain on your Virtual Machine by running the exploit in RedHat 8.0?

Section III – Common Vulnerabilities

  1. What is the cause of the behavior in adjacent.c?

  2. input successful buffer size and offset:

  3. remote.c successful buffer size and offset:

Section IV – A Contemporary Vulnerability

  1. What steps could a system administrator take to prevent this vulnerability?