Project 2: Application Security


Deadline: Thursday, October 17 by 11:59PM.

Before you start, review the course syllabus for the Lateness, Collaboration, and Ethical Use policies.

You may optionally work alone, or in teams of at most two and submit one project per team. If you have difficulties forming a team, post on Piazza’s Search for Teammates forum. Note that the final exam will cover project material, so you and your partner should collaborate on each part.

The code and other answers your group submits must be entirely your own work, and you are bound by the University’s Student Code. You may consult with other students about the conceptualization of the project and the meaning of the questions, but you may not look at any part of someone else’s solution or collaborate with anyone outside your group. You may consult published references, provided that you appropriately cite them (e.g., in your code comments). Don't risk your grade and degree by cheating!

Complete your work in the CS 4440 VM—we will use this same environment for grading. You may not use any external dependencies. Use only default Python 3 libraries and/or modules we provide you.


Helpful Resources

Introduction

In this project, you'll get hands-on experience with exploiting real-world security vulnerabilities in application software. You will perform attacks to gain total control of a system by exploiting a series of application targets containing critical security bugs. Attacks you will perform include buffer and integer overflows, as well as workarounds to popular defenses such as Data Execution Prevention and Address Space Layout Randomization. We will provide a series of vulnerable programs and a virtual machine environment in which you will write, test, and deploy your exploits.

Objectives


Start by reading this!

Before you begin, please carefully read through the following sections for important setup information and guidelines about this project.

Setup Instructions

Your target programs for this project are small C programs with (mostly) clear security vulnerabilities. We have provided source code, as well as a build.sh file that compiles all the targets.

Before you start writing your exploits, set up your application targets as follows:

Before continuing, make sure your cookie is set correctly! If you're working with a partner, you both must have the same identical cookie. Ordering of UIDs matters, so be sure to be consistent!

Important Guidelines


Part 1: Beginner Exploits

Targets 0–1 are designed to build up your confidence for the remaining parts of this assignment. To help you get started, we've provided some exercises on using GDB to inspect your target programs—a critical step of any pre-exploitation target reconaissance!

Test your exploits with the commands below. We'll use the same commands to grade your solutions.

Target0: Stack Variable Overwrite (16pts)

This program takes input from stdin (i.e., your keyboard) and prints a message. In this exercise, you will provide input to instead make this program print: "Hi UID! Your grade is A+". Your input will need to overwrite stack variable grade[] to achieve the intended behavior.

With your GDB Cheat Sheet in hand, work through the following exercise in crafting your exploit:

  1. Examine target0.c. Where does the buffer overflow occur?
  2. In your terminal, start the GDB debugger by running: $ gdb ./target0
  3. Disassemble function _main with the following command. What is _main's starting address?

    (gdb) disas _main
  4. Set a breakpoint at the beginning of _main and run the program:

    (gdb) break _main
    (gdb) run
  5. Draw a stack diagram. How are variables name[] and grade[] stored relative to each other?
  6. How could a value read into name[] affect the value contained in grade[]?
  7. Test your hypothesis by running ./target0 on the command line with several different inputs.
Hint: Be careful about null terminator characters!

Your task: Create a Python 3 program named sol0.py that causes the target to print "Hi UID! Your grade is A+" (where UID is replaced with one of your team's UIDs; or if working solo, just your UID). Test your program with the command line: $ python3 sol0.py | ./target0.

Target1: Return Address Overwrite (16pts)

This program takes input from stdin and prints a message. In this exercise, you will provide input to instead make this program print: "Your grade is perfect". Your input will need to overwrite function vulnerable()'s return address and redirect execution to function print_good_grade().

Here is one approach you might take in developing your exploit:

  1. Examine target1.c. Where does its buffer overflow occur?
  2. Examine function print_good_grade. What is its starting address?
  3. Set a breakpoint at the beginning of function vulnerable and run the program.
  4. Disassemble function vulnerable and draw the stack. Where is variable input[] relative to the ebp register? How long must an input be to overwrite this value and the return address?
  5. Use the following to inspect registers esp (the stack pointer) and ebp (the saved frame pointer):

    (gdb) info reg
  6. What are the current values of ebp and the return address from the stack frame? To examine two words of memory at location ebp, you can use the command shown below.

    (gdb) x/2wx $ebp
  7. What should these values be in order to redirect execution to function print_good_grade?
Hint: The x86 architecture is little endian. To encode addresses (e.g., a return address) in your exploit, use Python's pack() function with "<I" encoding (e.g., pack('<I', 0xDEADBEEF)).

Your task: Create a Python 3 program named sol1.py that causes the target to print "Your grade is perfect". Test your program with the command line: $ python3 sol1.py | ./target1.

What to Submit:

For each target, submit a Python 3 program (sol#.py) that exploits it to print the expected string.

Part 2: Intermediate Exploits

The remaining target binaries are owned by the root user (with their setuid bits set accordingly). Your goal is to exploit them each to invoke a shell with root privileges—how most attacks today work! This and the following targets will all take input as command-line arguments (rather than input from stdin). Unless otherwise noted, you should use the shellcode we have provided in shellcode.py (included with your targets.tar.gz). Successfully placing this shellcode in memory and redirecting execution to the beginning of the shellcode (e.g., by returning or jumping to it) will spawn a root shell!

To verify your opened shell is indeed a root shell, run the whoami command—it should return root! Be sure to review the information in Important Guidelines regarding how to correctly spawn root shells!

Target2: Redirection to Shellcode (14pts)

This program contains a straightforward buffer overflow vulnerability. Use your skills from Targets 0–1 to construct an exploit that loads your shellcode into the vulnerable buffer and redirects execution to it!

Your task: Create a Python 3 program named sol2.py that causes the target to open a root shell. Test your program with the command line: $ ./target2 $(python3 sol2.py).

Target3: Indirect Overwrite (14pts)

Here, the buffer overflow is restricted and cannot directly overwrite the return address. You need to find another way! Your input should cause the provided shellcode to execute and open a root shell.

Your task: Create a Python 3 program named sol3.py that causes the target to open a root shell. Test your program with the command line: $ ./target3 $(python3 sol3.py).

Target4: Beyond Strings (14pts)

This target takes as its command-line argument the name of a data file it will read. The file format is a 32-bit count followed by that many 32-bit integers. Create a data file that causes the provided shellcode to execute and open a root shell.

Hint #1: First try to figure out how an attacker could even trigger a buffer overflow in this program. Note that the function read_elements() breaks out of the for-loop once the end of the file is reached... so the 32-bit count variable does not need to be truthful!
Hint #2: Your exploit must open a root shell. If you're only spawning a non-root (i.e., cs4440) shell, you are very close! What part of the provided shellcode is your exploit skipping over?

Your task: Create a Python 3 program named sol4.py that outputs the contents of a file to be read by the target. Test your program with the command line: $ python3 sol4.py > tmp; ./target4 tmp. Your exploit should not output file tmp—we will pipe your exploit's output to tmp (as in the command).

What to Submit:

For each target, submit a Python 3 program (sol#.py) that exploits the target to open a root shell.

Part 3: Advanced Exploits

Targets 5–6 up the difficulty level: you'll be implementing workarounds to several common real-world application defenses! As usual, you'll be expected to exploit these targets into opening root shells.

Target5: Bypassing DEP (13pts)

This program resembles Target 2, but it has been compiled with Data Execution Prevention (DEP) enabled. DEP means that the processor will refuse to execute instructions stored on the stack. You can overflow the stack and modify values like the return address, but you can’t jump to any shellcode you inject. You will need to find another way to run the command /bin/sh and open a root shell!

Your exploit must not cause the program to display any output other than a root shell; this shell must also close gracefully—without segfaulting! If not, your attack is incorrect. Your solution cannot depend on you manually setting environment variables; you cannot assume that the autograder will run your solution with the same environment variables that you have set.

Hint: You may find it helpful to review Linux system calls exit() and _exit().

Your task: Create a Python 3 program named sol5.py that causes the target to open a root shell. Test your program with the command line: $ ./target5 $(python3 sol5.py).

Target6: Bypassing ASLR (13pts)

When we constructed the previous targets, we ensured that the stack would be in the same position every time the vulnerable function was called, but this is often not the case in real targets. In fact, a defense called Address Space Layout Randomization (ASLR) makes buffer overflows harder to exploit by changing the starting location of the stack and other memory areas on each execution. This target resembles Target 2, but the stack position is randomly offset by 0—255 bytes each time it runs. You need to construct an input that always opens a root shell despite this randomization.

Your exploit must not cause the program to display any output other than a root shell; this shell must also close gracefully—without segfaulting (at least half of the time)! If not, your attack is incorrect.

Hint: Think carefully about your exploit's stability—how can you maximize your chance of success?

Your task: Create a Python 3 program named sol6.py that causes the target to open a root shell. Test your program with the command line: $ ./target6 $(python3 sol6.py).

What to Submit:

For each target, submit a Python 3 program (sol#.py) that exploits the target to open a root shell.

Part 4: Super L33T Pwnage

The following targets offer an extra challenge for those wanting to test their exploitation skills. If you find these interesting, definitely come play Capture the Flag (CTF) competitions with us in UtahSec!

Extra Credit: Return Oriented Programming (10pts)

Target 7 is identical to Target 2, but it is compiled with DEP enabled. Implement a ROP-based attack to bypass DEP and open a root shell. You may find the objdump utility helpful.

Your exploit must not cause the program to display any output other than a root shell; this shell must also close gracefully—without segfaulting! If not, your attack is incorrect.

Your task: Create a Python 3 program named sol7.py that causes the target to open a root shell. Test your program with the command line: $ ./target7 $(python3 sol7.py).

Extra Credit: Heap Exploitation (10pts)

Target 8 implements a doubly-linked list on the heap, and takes three command-line arguments. Find a way to exploit it to open a root shell. You may need to modify the provided shellcode slightly.

Your task: Create a Python 3 program named sol8.py that prints lines to be used for each of the command-line arguments to the target. Your program should take a single numeric argument that determines which of the three arguments it outputs. Test your program with the command line:
$ ./target8 $(python3 sol8.py 1) $(python3 sol8.py 2) $(python3 sol8.py 3).

What to Submit:

For each target, submit a Python 3 program (sol#.py) that exploits the target to open a root shell.

Submission Instructions

Upload to Canvas a tarball (.tar.gz) named project2.uid1.uid2.tar.gz, replacing your team's UIDs accordingly (if working alone, provide only your UID once). Each UID must be in u####### format. Your tarball must contain only the files listed below. These will be autograded, so make sure that your solutions conform to the expected filenames, formatting, and behaviors.

Failure to follow assignment instructions (e.g., submitting a corrupted tarball; wrong, missing, or broken code; improper formatting; etc.) will be ineligible for regrades. External dependencies are prohibited. You may use only default Python 3 libraries and/or modules we provide you. Your solutions must work as-is in the CS 4440 VM. Make sure to thoroughly test your code before submitting!

Generate the tarball in your VM terminal using this command (be sure to first cd to the directory that contains your files):

tar -zcf project2.uid1.uid2.tar.gz cookie sol[012345678].py