













Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Material Type: Lab; Class: Internetwork Security; Subject: Electrical & Computer Engr; University: Georgia Institute of Technology-Main Campus; Term: Spring 2004;
Typology: Lab Reports
1 / 21
This page cannot be seen from the preview
Don't miss anything!














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.
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.
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:
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.
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?
Writing an Exploit
exploit2 successful buffer size and offset or ten attempts and reasoning:
exploit3 successful buffer size and offset:
Why was it so much easier to utilize exploit3?
Section II imapd
imapd-ex successful offset:
What privileges did you gain on your Virtual Machine by running the exploit in RedHat 8.0?
Section III – Common Vulnerabilities
What is the cause of the behavior in adjacent.c?
input successful buffer size and offset:
remote.c successful buffer size and offset:
Section IV – A Contemporary Vulnerability