Northeastern University

CS 4740/6740 - Network Security

Lab: Stack-Based Buffer Overflows

In this lab, you will be learning about buffer overflows in the stack, a common vulnerability in programs that allows you to run arbitrary code. This lab will require some programming in C, some knowledge of assembly, and a touch of debugging tools.

Late submissions will result in a 10% penalty per day (e.g., 2.5 days late result in 25% penalty)

1. Background

Stack-based buffer overflows have been around for a long time and all indications are that these and similar attacks will continue to be a problem in the near future. Aleph One wrote the definitive paper on using this technique to exploit programs, Smashing The Stack For Fun And Profit, and Paul Makowsky expanded on the protections that are now standard to allow Smashing the stack in 2011. You should read these papers carefully. They will walk you through every aspect of overflowing the buffer, creating a shellcode, current protections implemented in modern operating systems, and ultimately exploiting a vulnerable program. It is very long and full of technical detail -- make sure to set aside plenty of time to work through it.

Several strategies have been created to counter these types of bugs in programs. The version of Linux installed on your machine employs two different kernel-based stack protection mechanisms, and one hardware-flag. One kernel protection randomizes stack addresses to make it difficult to predict locations of shellcode. The other places random canary values on the stack to protect stored addresses. The hardware flag used (NX bit) sets memory regions as non-executable, triggering a signal and the termination of the program. You need to disable all of these mechanisms or else it will be difficult to exploit overflows on your system.

To disable stack protection, you will need to write a value in a special system file (as root or running a script with sudo). We have provided scripts that do this called disable-stack-protection and enable-stack-protection. located inside the zip file referenced below. You should read these scripts to see how to enable and disable stack randomization. Note that if your system is ever rebooted, the stack protections will be re-enabled by default. To disable the placing of canary values in your executables, you need to use a gcc version that does not place canary values, or disable the functionality with the -fno-stack-protector option. Again, we have automated this task during the compilation of the code you will be exploiting. Read the Makefile shipped with the zip file below to see how to disable canary values.

2. Instructions

  1. Log on to your linux machine, and obtain the files needed for this lab: wget http://strawman/lab1/lab1.zip and unzip them with unzip lab1.zip

  2. You will find a directory lab1/ which contains three files. The Makefile contains instructions to compile the C programs uppercase.c and lowercase.c. To compile the programs, simply type make inside the lab1/ directory. Make sure you don't compile the programs directly with gcc, as that will use gcc version 4 which inserts canary values by default. Examine both programs and discuss within your team what possible security issues exist with them.

  3. Now, focus on uppercase.c. This program takes a string in on argv[1] and prints out the same string with all ASCII lowercase characters changed to uppercase. Think about what would happen if a user provided a string longer than 512 bytes. Now, try it out. Run the program with a first argument that is much longer than 512 bytes.

  4. Use gdb to run the program again with the same, very long, first argument (hint: set args AAAAAAA...). When you run the program and it finishes, record the %eip address reported when it segfaults. Compare this value to your string's ASCII hexadecimal representation (hint: man ascii). Now, repeatedly shorten/lengthen your string and re-run uppercase in gdb to determine the length of the shortest string needed to control %eip.

  5. Set a breakpoint at main() and run uppercase again with your shortest (but %eip-controlling) argv[1]. Once the breakpoint is reached, find the address of uppercase's argv[1] (hint: print &argv[1]).

  6. Write a new program in C which exploits this stack-based buffer overflow to execute a shell. Your exploit should embed a shellcode in the argv[1] buffer of uppercase and then overwrite the stack frame return address so that execution continues within that buffer when main() returns. Note: since buf will get modified before main() returns, you won't be able to jump into that buffer.

    The shellcode you need to use is:

    char scode[] = 
       "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89"
       "\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c"
       "\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff"
       "\xff\xff/bin/sh";
    		
  7. Once you have a working exploit which executes a shell, make uppercase a setuid root program by running chmod u+s uppercase as root. Now, run your exploit again as a non-root user. Once your shellcode runs, use the id(1) command to determine what user the shell is executed as. If everything works correctly, you'll have elevated privileges to root through this overflow.

  8. Now take a look at lowercase.c. Notice what happens to argv[1] before it is copied into buf. If you try to exploit this overflow by placing your shellcode in argv[1], why won't it work? Think about other locations your shellcode could be stored instead of argv[1].

  9. Write a new exploit for lowercase which does not embed the shellcode in buf or argv[1]. Instead, just repeatedly fill argv[1] with the address where your shellcode resides. There's at least two places in lowercase's address space where you can stuff your shellcode.

Report

For this lab, your team must submit a report with the following information:

  1. Submit all exploits with every line commented to demonstrate you understand how the exploits work.

  2. Submit a diagram of the stack for uppercase which includes the stack frames for main() and strcpy() and all local variables for main(). Also, include the location of main()'s return address and how it gets overwritten by an overflown buf. Finally, include argv[1] in the diagram as well, relative to the stack frames. Exact offsets aren't that important here, but relative locations are.

  3. How would you modify uppercase.c and lowercase.c to make them safe from buffer overflow attacks? Submit an answer to this question in the form a code diff of each program in the unified format. See the man page for diff(1). Verify that your changes fixed the problem by running your exploits on the patched programs.

  4. If buf were instead allocated via malloc(3) in these vulnerable programs, would the same techniques for exploitation work? Why or why not?

  5. What is different about executing a setuid program versus a non-setuid program? What are the dangers of setuid programs? Why are setuid programs sometimes necessary?

  6. Find an advisory for a remote overflow vulnerability published within the last year on the web which has an associated, published exploit. The exploit must be in the form of source code, and should be designed to execute code on the remote system (you do not need to test this). In what scenarios can the vulnerability you found be exploited? Include links and references to an advisory on the vulnerability, the exploit itself, and any related information you find.

Grading

Your grade for this lab will be composed of: