




























































































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
A step-by-step guide on how to exploit command line vulnerabilities using a buffer overflow technique. It includes the manipulation of stack variables and the inspection of stack contents to overwrite the next instruction pointer. The goal is to execute arbitrary code by injecting a shellcode into the target process.
Typology: Lab Reports
1 / 113
This page cannot be seen from the preview
Don't miss anything!





























































































Date Issued: February 7, 2006 Due Date: February 14, 2006 Last Edited: February 18, 2006 Lab Goal 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. Pre-Lab The following readings are a must to understand this lab and complete it in a timely manner.
coded as it starts growing (lower right) – be sure you understand the stack manipulation before and after function calls. Note: When asked for input, type in a long string and watch it erase data in the stack memory. 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.
Exercise 3: Return Pointer Redirection Source file: example 3.c Observe the source file. This exploit attempts first to create a pointer (ret) to the function return address, then modify the return address value (*ret). At the end of the function call, the function will return to an incorrect address. The purpose of this exercise is to attempt to redirect the pointer and skip the “x=1” line, so that the example prints “0.” 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 example3.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
executed. exploit2 will store this to an environment variable ($EGG) which we will then pass as input to our vulnerable program. Unfortunately, unlike in our previous examples, we do not know exactly where in memory vulernable will place our buffer. We therefore must manually set the address we wish to place into the return pointer by entering an offset: our guess as the distance from the stack pointer to our malicious code. We also must determine a size of a buffer to create to attempt to overwrite the return pointer. The buffer size created by exploit2 must be large enough to overflow the return pointer, but small enough to avoid a segmentation fault. 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****. Now that you have exploited vulnerable.c, re-open the file and observe where exactly the buffer overflow has taken place. Add/change the code in the file to remove the vulnerability, then re-run “./vulnerable $EGG”
9) What changes did you make to vulnerable.c?
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.
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.
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 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 The line Targetting range to [] reports the lower and upper memory address of the NOP sled and the range of bytes that it spans. Now we know where the NOPS are located. The line Stack Overwrite from to [] shows the range of address that the memory redirection address overwrites. Now we know where the desired return address values are written. We want one of these locations to be the stack return address memory so that when the stack goes to the return address memory location, it will get a value the hacker wants it to use instead of the good original value that was in that memory location.
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]
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 Once again we did not get the shell we wanted. We did get a segmentation fault and noticed that the next instruction changed, so we successfully overwrote the instruction pointer. Unfortunately what we overwrote did not point to our NOP sled. We can see from the last line that we have missed were we want to be by about 1500 and thus need to re-run the “./exploit” with an offset to move where we start writing our NOP sled. Now we try again by doing the commands ./exploit 612 1500 ./cmdline $EGG
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.
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 Red Hat 7.2 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 and include it with your report. 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?
Probably at this point, you might be thinking that the only way to prevent such stack buffer overflow attacks from happening is to just pray that the original developer properly checked his buffers in his code. Considering that in reality this is usually not the case, the idea of preventing buffer overflow exploits from happening seems hopeless. Well, actually, that is not true. There are preventive measures that are available and being developed now. Traditionally, preventive measures on Linux have come in the form of kernel patches. These patches usually monitor system calls in the operating system and try to stop unusual or suspicious system calls made by potentially malicious code. In this case, one will have to recompile the kernel properly with the patch and hope it works out. For the purposes of this lab, it would seem unreasonable to make a copy of a kernel and wait for a long time to recompile the kernel just to see a buffer overflow exploit prevented. Instead, we will use another type of preventive measure. We are going to install libsafe, a dynamically loadable library that does not require any recompilation. Connect to the Network Attached Storage, and copy the rpm package libsafe-2.0- 16.i386.rpm from the Lab4 folder to your Red Hat 7.2 machine. Now install the package by opening a terminal window and typing the following command: rpm -ivh libsafe-2.0-16.i386.rpm There should be no problems installing this package. Now we are going to use Mozilla to take a look at the html man page on libsafe. Click on the main menu button on the bottom panel in the bottom left corner (the red fedora hat) and then highlight “Internet” and then select “Web Browser.” This should bring up the web browser Mozilla. Now click on Mozilla’s File menu and select “Open File.” Now type in /usr/doc/libsafe-2.0/doc/ in the “File Name” text box and hit enter. You should see a file called libsafe.8.html , double-
So let’s see libsafe prevent such an exploit. In your terminal window, go back to the directory where your exploit and cmdline programs are that you have executed previously in this lab. Go ahead and run the exploit and the cmdline programs again like you did previously in Section III – Task 1. Remember, however, to undo the changes you made to the code. If you don’t, there will be no vulnerability for Libsafe to guard. Libsafe should have stopped the exploit from being executed, and the output on your terminal should look similar to Figure 2. Copy this output to a text file and turn it in with your lab. Figure 2. Libsafe preventing a stack buffer overflow exploit in cmdline. Libsafe apparently detected our attempt to write across the stack boundary which is what we needed to do to rewrite cmdline’s return address in order to start executing our malicious code. You should notice also that it determined that the overflow was caused with the library function strcpy, which is at the center of our exploit. This is pretty nifty. However, consider some of the pros and cons of this strategy. Libsafe concentrates monitoring on a system-wide level through the library space, not the kernel or user space of the operating system. This is nice in the sense that this solution does not require recompilation of the kernel or directly interfering with programs that a user is trying to run. However, libsafe can only determine exploits that use the list of library functions that it knows have vulnerabilities to stack buffer overflows. If the attacker is able to create a stack buffer overflow exploit without using libsafe’s list of vulnerable library functions, it is possible that the libsafe will never detect the exploit and the attack has a good chance of succeeding. Therefore, while libsafe is a nice solution that can easily implemented, it still does not replace the real prevention of buffer overflow attacks, which is properly checking buffers in the code of programs. [root@group24-4893 lab]# ./exploit 612 100 Using address: 0xbffff6c [root@group24-4893 lab]# ./cmdline $EGG Inspect environment variables Targetting range bffff970 to bffffa89 [280 byte range] Stack Overwrite from bffffabc to bffffbd0 [280 byte range] bffffa89 <= bffff6c4 < bffff970? 281 <= -684 < 0 Inspect argument variables Targetting range bffff674 to bffff78d [280 byte range] Stack Overwrite from bffff7c0 to bffff8d4 [280 byte range] bffff78d <= bffff6c4 < bffff674? 281 <= 80 < 0 =========================== Stack:bffff300, Frame:bffff508[bffff528], Next Instruction:bffff50c[420158d4] Libsafe version 2.0. Detected an attempt to write across stack boundary. Terminating /home/tools/lab/cmdline. uid=0 euid=0 pid= Call stack: 0x40015871 /lib/libsafe.so.2.0. 0x4001597a /lib/libsafe.so.2.0. 0x8048834 /home/tools/lab/cmdline 0x420158cf /lib/i686/libc-2.2.93.so Overflow caused by strcpy() Killed [root@group24-4893 lab]#
That is all. Remember to copy the libsafe output to a text file, and turn it in with your lab. Also, go ahead and uninstall the libsafe library by executing the following command: rpm -e libsafe There are other utilities available which can be used for preventing buffer overflows. One such utility is Stackguard (http://www.immunix.org/gcc-2.96-113_SGb1.src.rpm) which protects your programs from buffer overflows by compiling them with compilers which take special action to protect the stack during run time. You compile your program using these compilers and depending on the programs implementation will protect the stack from stack-smashing attacks. Stackguard inserts an extra field on the stack called a “canary” next to the return pointer on the stack. It uses the canary to determine if the return pointer has been changed; if the canary is changed then the stack has been compromised and the program will stop execution. Another such tool is called the Stack Shield , which adds protection from stack overflows without changing the code. It causes the program to copy the RET address to an unoverflowable location on function beginnings and checks it with the RET address before the function returns. If the two values are different, the program crashes. Stack Shield is an assembler file processor and relies on the GCC/G++ front ends to automate compilation. It can be downloaded from http://www.angelfire.com/sk/stackshield/download.html. There are numerous tools to find flaws in code today. One of these tools is Flawfinder. Flawfinder analyzes C/C++ source files and warns of any potential problems in the code. When run on a source file flawfinder will show any gets, printf, strcpy commands and others that might cause vulnerabilities in the code. One nice feature of flawfinder is that the code need not be compiled to test it. http://www.dwheeler.com/flawfinder/flawfinder-1.26.tar.gz
If an attacker has a “Limited” account on: