$30
In this assignment you will learn about security vulnerabilities in software. In particular, you will find and exploit memory corruption vulnerabilities in order to better understand how simple programming errors can lead to whole system compromise. You will also examine some common mitigation techniques and analyze exploit code (i.e., shellcode).
Objectives
• Be able to identify and avoid buffer overflow vulnerabilities in native code
• Understand the severity of buffer overflows and the necessity of standard defenses
• Gain familiarity with machine architecture and assembly language
1.1 Enabling core dump files
Core dump files can be useful for analyzing the cause of an execution crash. Once enabled, the operating system will create a file named core in your current working directory when a process crashes (e.g., due to a segmentation fault). Used in conjunction with gdb, the core file can give you a complete view of the process state when it crashed. To analyze a crashed process, simply launch gdb with the names of the executable file and its core file as arguments.
You can enable core dump files for processes that crash by using the command ulimit -c unlimited from the command prompt. However, to enable core dump for setuid executables, you should use an additional command (echo 1 > /proc/sys/fs/suiddumpable). If you need assistance enabling core files, see Figure 1 or ask for help via the course discussion board.
1.2 Miscellaneous
You may find it helpful to write an additional program or script that will assist in figuring out the correct input to exploit any of the vulnerabilities. Note that this can be separate from the script/program you will write to exploit the vulnerabilities. If you’re unsure about what is acceptable for this assignment, please don’t hesitate to email us. Be sure to turn in the code for any additional program or script that you write.
2 Targets
The target programs for this project are simple, short C programs with (mostly) clear security vulnerabilities. We have provided source code and a Makefile that compiles all the targets. Your exploits must work against the targets as compiled and executed within the provided VM.
These targets are owned by the root user and have the suid bit set. Your goal is to cause them to launch a shell, which will therefore have root privileges. This and the following targets all take input as commandline arguments rather than from stdin. Unless otherwise noted, you should use the shellcode we have provided in shellcode.py[1] for you to use. Successfully placing this shellcode in memory and setting the instruction pointer to the beginning of the shellcode (e.g., by returning or jumping to it) will open a shell. A successful run is shown below.
For each program that we have provided, we ask that you explicitly:
1. Briefly describe the behavior of the program.
2. Identify and describe the vulnerability as well as its implications.
3. Discuss how your program or script exploits the vulnerability and describe the structure of your attack.
4. Provide your attack as a self-contained program written in Python.
5. Suggest a fix for the vulnerability. How might you systematically eliminate vulnerabilities of thistype?
For vulnerable4.c, provide answers to the following questions in addition to those listed above.
1. What is the value of the stack canary? How did you determine this value?
2. Does the value change between executions? Does the value change after rebooting your virtual machine?
3. How does the stack canary contribute to the security of vulnerable4?
vulnerable1: Redirecting control to shellcode
What to submit Create a Python program named sol1.py that prints a line to be used as the commandline argument to the target. Test your program with the command line:
./vulnerable1 $(python sol1.py)
If you are successful, you will see a root shell prompt (#). Running whoami will output “root”.
If your program segfaults, you can examine the state at the time of the crash using GDB with the core dump: gdb ./vulnerable1 core. The file core won’t be created if a file with the same name already exists. Also, since the target runs as root, you will need to run it using sudo ./vulnerable1 in order for the core dump to be created.
vulnerable2: Overwriting the return address indirectly
What to submit Create a Python program named sol2.py that prints a line to be used as the commandline argument to the target. Test your program with the command line:
./vulnerable2 $(python sol2.py)
vulnerable3: Beyond strings
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 opens a root shell.
What to submit Create a Python program named sol3.py that outputs the contents of a data file to be read by the target. Test your program with the command line:
python sol3.py > tmp; ./vulnerable3 tmp
vulnerable4: Bypassing DEP
This program resembles target2, but it has been compiled with data execution prevention (DEP) enabled.
Warning: do not try to create a solution that depends on you manually setting environment variables. You cannot assume that the grader will run your solution with the same environment variables that you have set. Please make sure that your program exits gracefully when you close the spawned shell as well (it should not segfault).
Note that in this vulnerable program you will not achieve a root shell with just a basic attack. This is okay!
This is due to protections in newer versions of bash/dash that are specifically intended to drop setuid privileges when a shell is spawned through /bin/sh.
If your program spawns a shell in the default environment and a root shell in zsh, then you have successfully completed the exploit for this class.
If you would like to know more about this, please read on...
From the documentation for the system call [2]:
”system() will not, in fact, work properly from programs with set-user-ID or set-group-ID privileges on systems on which /bin/sh is bash version 2, since bash 2 drops privileges on startup.”
bash is explicitly programmed to check for setuid[3]:
if (runningsetuid && privilegedmode == 0)
disablepriv mode ();
Basically, if dash/bash detects that it is executed in a setuid process, it immediately changes the effective user ID to the process’s real user ID, essentially dropping the privilege. This was not always true in previous versions.
To see that this is actually just an artifact of the type of shell we are running, the VM also has zsh installed, which does not include these protections. We can switch the shell to zsh and run the exact same exploit with a very different result. See the example or the commands below for how to do this.
To switch to zsh:
sudo rm /bin/sh sudo ln -s /bin/zsh /bin/sh
Open a zsh shell by running: zsh
Once you have opened the new shell, try running your exploit again. This should give you a root shell.
If we want to reset back to the default shell:
sudo rm /bin/sh sudo ln -s /bin/dash /bin/sh
There are potential workarounds for this dropping of privileges in the default shell[4]... However, if your program spawns a shell in the default environment and a root shell in zsh, then you have successfully completed the exploit for this project.
Extra credit: For extra credit, create a Python program that will spawn a root shell using the default environment and shell. If you complete the extra credit, please let us know in the answers.pdf file and submit this solution instead.
vulnerable5: Variable stack position
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 ASLR (address-space layout randomization) makes buffer overflows harder to exploit by changing the starting location of the stack and other memory areas on each execution. This target resembles vulnerable1, 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.
What to submit Create a Python program named sol5.py that prints a line to be used as the commandline argument to the target. Test your program with the command line:
./vulnerable5 $(python sol5.py)
A word of caution If you see any output from the program before a root shell is opened, you have not done vulnerable5 of the project correctly and your solution will not be accepted by the grader.
vulnerable6: Return-oriented programming [Extra credit] (Difficulty: Hard)
This target is identical to vulnerable1, but it is compiled with DEP enabled. Implement a ROP-based attack to bypass DEP and open a root shell. It may be helpful to use a tool such as ROPgadget (https: //github.com/JonathanSalwan/ROPgadget).
1. Though there are a number of ways you could implement a return-oriented program, your ROP shoulduse the execve system-call to run the ”/bin/sh” binary. This is equivalent to: execve(”/bin/sh”, 0, 0);
2. For an extra push in the right direction, int 0x80 is the assembly instruction for interrupting executionwith a syscall, and if the EAX register contains the number 11, it will be an execve. Now all you need to figure out is what values you need for EBX, ECX, and EDX, and set them using ROP gadgets!
You may find the objdump utility helpful.
For this target, it’s acceptable if the program segfaults after the root shell is closed.
vulnerable7: Heap-based exploitation [Extra credit] (Difficulty: Hard)
This program implements a doubly linked list on the heap. It takes three command-line arguments. Figure out a way to exploit it to open a root shell. You may need to modify the provided shellcode slightly.