This project will give you first-hand experience with buffer overflow attacks. This attack exploits a buffer overflow vulnerability in a program to make the program bypass its usual execution and instead jump to alternative code (which typically starts a shell). There are several defenses against this attack (other than fixing the overflow vulnerability itself), such as address space randomization, compiling with stack guard, and making the stack non-executable.
The learning objective of this lab is for students to gain first-hand experience of the buffer-overflow attack. This attack exploits a buffer-overflow vulnerability in a program to make the program by- pass its usual execution sequence and instead jump to alternative code (which typically starts a shell). Specifically, the attack overflows the vulnerable buffer to introduce the alternative code on the stack and appropriately modify the return address on the stack (to point to the alternative code). There are several defenses against this attack (other than fixing the overflow vulnerability), such as address space randomization, compiling with stack-guard, dropping root privileges, etc.
In this lab, students are given a set-root-uid program with a buffer-overflow vulnerability for a buffer allocated on stack. They are also given a shellcode, i.e., binary code that starts a shell. Their task is to exploit the vulnerability to corrupt the stack so that when the program returns, instead of going to where it was called from, it calls the shellcode, thereby creating a shell with root privilege. Students will also be guided through several protection schemes implemented in Ubuntu to counter this attack.
Note: There is a lot of helpful information in Section 13; be sure to read it before you get started. Also, if you get stuck, “Smashing the Stack for Fun and Profit” and the lecture notes and slides will help.
This is the machine we will use for testing your submissions. If it doesn’t work on that machine, you will get no points. It makes no difference if your submission works on another Ubuntu version (or another OS).
The amount of code you have to write in this lab is small, but you have to understand the stack. Using gdb (or some equivalent) is essential. The article, Smashing The Stack For Fun And Profit, is very helpful and gives ample details and guidance. Read it if you’re stuck.
Throughout this document, the prompt for an ordinary (non-root) shell is “$”, and the prompt for a root shell is “#”.
Starter files are available at the class projects page.
Ubuntu, and several other Linux-based systems, use “address space randomization” to randomize the starting address of heap and stack. This makes it difficult to guess the address of the alternative code (on stack), thereby making buffer-overflow attacks difficult. Address space randomization can be disabled by executing the following command:
$ sudo sysctl -w kernel.randomize_va_space=0
To re-enable ASLR, you simply run the above command but with a two instead of a zero:
$ sudo sysctl -w kernel.randomize_va_space=2
A “set-root-uid” executable file is a file that a non-root user can execute with root privilege; the OS temporarily gives root privilege to the user. More precisely, each user has a real id (ruid) and an “effective” id (euid). Ordinarily the two are the same. When the user enters the executable, its euid is set to root. When the user exits the executable, its euid is restored (to ruid).
However if the user exits abnormally (as in a buffer-overflow attack), its euid stays as root even after exiting. To defend against this, a set-root-uid shell program usually drops its root privilege before starting a shell if the executing process is only an effective (but not real) root. So a non-root attacker would get a shell but it would not be a root shell. Ubuntu’s default shell program, /bin/bash, has this protection mechanism. There is another shell program, /bin/zsh, that does not have this protection scheme. You can make it the default by modifying the symbolic link /bin/sh.
$ cd /bin $ sudo rm sh $ sudo ln -s /bin/zsh /bin/sh
Whenever you are not working on this project, you will want to change back to running bash. You can do that by running the above commands with /bin/bash instead of /bin/zsh.
Note: Avoid shutting down Ubuntu with /bin/zsh as the default shell! Instead, in VirtualBox, chose to “Save the machine state.” Otherwise, when Ubuntu reboots, the GNOME display is disabled and only a tty comes up. If that happens, here are several fixes:
Log in, run the following:
$ sudo shutdown
A menu comes up. Choose “filesystem clean”, then “normal reboot”.
Log in, run the following:
$ sudo mount -o remount / mountsthefilesystemasread-write $ sudo /etc/init.d/gdm restart restartsGNOMEDisplayManager
We will be using gcc to compile all of the programs in this project. There are a few non-standard ways we will be using gcc, like turning off basic protection mechanisms, but this can all be done by providing gcc some commandline arguments, which we describe here.
In general, we highly recommend creating a Makefile that will automate these for you.
gdb will be your best friend in this project. To get useful information from gdb regarding the names of functions and variables, include the -g commandline argument to gcc.
This semester, we will be using the latest, 64-bit version of Ubuntu. For the sake of this project, how- ever, we will be running in 32-bit. To have gcc compile to 32-bit, provide the following commandline option: -m32
The gcc compiler implements two security mechanisms that help protect against buffer overflows and code injection. For the sake of this project, we will be turning both of these off (but please leave them on in practice!).
Canaries: gcc implements the idea in the “Stack Guard” paper by introducing canaries in each stack frame. You can disable this protection by compiling with the commandline argument -fno-stack-protector
Non-executable stack: In the updated VM we are using, gcc by default will make the stack non-executable, thereby making it more difficult to launch arbitrary code. You can disable this with the commandline argument -z execstack
To compile a program vulnerable.c into a binary named vuln, with the above protections dis- abled, in 32-bit, and ready for gdb, you would run the following command:
gcc -fno-stack-protector -z execstack -m32 -g vulnerable.c -o vuln
To get things started, consider the following simple program (provided in the start-up files as guesser.c):
/* guesser.c */
/* Provide THREE different versions of this,
* that each win the "guessing game" in main(). */
int mine = 0;
int yours = 0;
yours = your_fcn();
mine = yours + 1;
if (mine > yours)
This program runs a simple “guessing game” to see who can choose the biggest number. It draws your number by calling your_fcn(), a function that you have complete control over. It chooses its own number by just adding some to yourshow convenient! Your task is to write not one but three different versions of the function that each win the guessing game every time. As a slight hint, note that the only way that we determine whether or not you win is if the program prints “You won!” (followed by a newline) at the end.
We will be compiling them with address space randomization and stack protection turned off and the stack executable (Sections 2 and 3).
Caveats. While you are allowed to set the body of your_fcn() as you wish, you are not allowed to modify main() itself. Also, this task permits an exception to the syllabus: hardcoding is allowed, if you think it will help you win! All of your solutions must be fundamentally distinct. You do not have to use a buffer overflow as one of your three solutions, but it is certainly one way to go!
Submitting. Create three copies of the guesser: guesser1.c, guesser2.c, and guesser3.c, each of which has a different implementation of your_fcn(). (There are general submission instructions in Section 12.