CSG254: Network Security

Practical Systems Security Lab
Lab Assignment 1: Stack-based Buffer Overflows
Due date: 6:00PM Eastern, January 25, 2006

NOTE: Read this entire document before beginning the lab assignment.


Lab Assignment

  1. Building a Team

    Find teammates to form a team of 5 students from the class and choose a team name. Think carefully about who you want to team up with. Successful teams will consist of individuals who:

    Once you have settled on your team members and a team name, have one member of your team send an email to the lab TA (with a CC to Prof. Noubir) containing the following information:

    Upon receipt, you will receive a team number and credentials to remotely access your team's systems. At that point, you may begin part 2.


  2. Creating Accounts

    1. Once you have received the credentials for your team's systems, have one team member log into your Linux system via SSH and create one user account for each team member using the useradd(8) command. (Read the man page first for usage information.) When creating users, use the -G option to add them to the wheel group as well as the default initial group. (If you forget to do this when creating an account, see the man page for group(5) and add them manually with a text editor.)

    2. Next, have every team member log into the system with their own account via SSH and change their password. Each team member should read the man page on sudo(8), to understand how they may execute privileged commands with their own less-privileged account. Review the current /etc/sudoers file to understand currently granted privileges.

    3. Now, have one team member log into your Windows server via a terminal services client using the Administrator account credentials provided. Create one account for each user in your team, using the same usernames for each user that were used on your Linux system. (The passwords for each account need not be the same between the two systems.)

    4. Next, add each of the newly created user accounts to the Administrators group.

    5. Finally, change the root password on your Linux router and the Administrator password on your Windows server if you have not already done so.

    NOTE: Be sure to use strong passwords for the accounts. All systems will be partially exposed to the internet, and password brute-force attacks have become common-place.

  3. Stack-Based Buffer Overflows

    1. Fedora Core 4 employs two different kernel-based stack protection mechanisms. One 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. We need to disable both of these mechanisms, or else it will be very difficult to exploit overflows on your system.

      To do this, run the script (as root, or through sudo) /root/bin/disable-stack-protection. To re-enable stack protections, run /root/bin/enable-stack-protection. (If your system is ever rebooted, the stack protections will be re-enabled by default.)

    2. You will find on your team's Linux system, a directory /usr/local/netsec/lab1 which contains three files. The Makefile will compile the C programs uppercase.c and lowercase.c. 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";
         
      You may find it helpful to use Aleph1's paper, Smashing The Stack For Fun and Profit as a tutorial/reference for this section.
      More helpful hints:
      • Program names, argc/argv, and environment variables are all pushed onto the stack before main()'s stack frame is. Running your exploit or uppercase with a minimal set of environment variables can help make exploits work more consistently. See env(1) and execle(3).
      • Under Linux, the stack generally begins at the address 0xBFFFFFFA and addresses progressively decrease as new items are pushed onto it.
      • On x86 architectures, 0x90 is the NOP instruction.


    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 that you can stuff your shellcode.)

Questions

  1. What is the purpose of the sudo command? If sudo is used to grant access to a specific account, why is this better than simply giving that user's password to those who need it?

  2. How would you modify uppercase.c and lowercase.c to make them safe from buffer overflow attacks? You are encouraged to answer this question by including a code diff of each program. (See: diff(1). Universal diffs are preferred.)

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

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

  5. 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/references to an advisory on the vulnerability, the exploit itself, and any related information you find.)

Submission Notes