A1: Exploiting Buffer Overflows
Authored by Professor Gang Tan of Penn State University. Adjusted by Jie Zhou for this course.
This assignment requires a good understanding of the lecture assigned readings of September 8th.
Part 1
We will use the vlab machine as specified in the lecture. Make sure you have access to the machine. Alternatively, you may attempt this assignment in your own Linux environment; however, the instructions on this page may not work as intended, since other experiments may enforce different safety policies that make your exploitation easier or more difficult.
Part 2 (4 points)
We have the following program lucky.c
that generates lucky numbers. It takes a password as input, but always refuses to generate a lucky number. Luckily, the program is vulnerable to a buffer overrun in the goodPassword()
procedure. The goal is to take advantage of the vulnerability so that it can generate lucky numbers for us.
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
char *gets(char *);
long *ret;
void generateLuckyNumber() {
struct timeval t;
gettimeofday(&t, 0);
srand((unsigned int) t.tv_usec);
printf("Your lucky number today is %d!\n", rand()%100);
}
char goodPassword() {
int good='N';
char Password[50]; // Memory storage for the password
gets(Password); // Get input from keyboard
return (char)good;
}
int main() {
struct timeval t;
printf("Enter your password:");
if (goodPassword() == 'Y') {
generateLuckyNumber();
}
else {
printf("No lucky numbers for you today.\n");
exit(-1);
}
return 0;
}
Figure out a password that can make the program output a lucky number. Explain how your password works. Hint: no need to overwrite the return address for Part 2; there is another easy target to overwrite in this program.
Compile the above program using clang lucky.c -fno-pic -no-pie -o lucky
(in particular, without compiler optimizations and address randomization that will be covered later in this course) and verify your password works on the generated executable; ignore the warning about gets from clang.
Part 3 (4 points)
Suppose you are allowed to add some code between gets(Password);
and return (char)good;
in the goodPassword()
procedure. We intend to use the following code template to modify the return address on the stack so that the program jumps directly to the THEN branch without checking the result of goodPassword()
:
ret = (long *) ((long) Password+?);
*ret = *ret + ?;
Each ?
above represents an integer constant. Figure out these constants. You’d need the help of a debugger such as gdb or ddd to figure out the offset between the Password
buffer and the return address on the stack. On-paper calculation probably won’t work, as the stack layout depends on the compiler.
Part 4 (5 points)
Assume we cannot insert extra code as in Part 4. Figure out a password that makes the program luck.c to call the generateLuckyNumber
function so that a lucky number can be generated. Basically, your attack should overflow the Password
buffer and overwrite the return address so that the new return address points to the address of the generateLuckyNumber
function.
Part 5 (2 points)
In Part 4, even if you successfully make generateLuckyNumber
generate a lucky number for you, chances are that the program will terminate abnormally with a segmentation fault due to the wreck of the normal stack. This is undesirable from an attacker’s perspective, as such abnormal termination can be easily detected by the victim system.
In this part, (1) building on the input you used in Part 4, figure out how to extend it so that the vulnerable program not only generates a lucky number for you but also exit gracefully without any error messages. (2) Briefly describe how your program terminates after generating the lucky number. That is, after the number generation, what major steps the execution goes through before it goes out of the main()
function? You do not need to list every single instruction but only the major transition points in the program.
If your input in Part 4 does not trigger a segfault, you are good with step (1) above. In this case, you only need to complete (2).
Tools you may need
-
clang -S lucky.c -o lucky.s
compiles the C program to assembly code. -
clang -ggdb lucky.c -o lucky
produces an executable with debugging information, which is used by a debugger like gdb. - gdb is a tool that you can use to figure out the difference between the original return address and the new return address for Part 3. You may also use vscode as an IDE to examine memory in vscode’s GUI.
- x86 uses the little-endian format for storing addresses.
- You can pipe your payload to
lucky
instead of manually typing it, which is error-prone and even impossible for certain payload. You can usepython
to generate your payload, e.g.,python3 -c 'print("f"*30)' | ./lucky
.
What you need to submit
Submit your work through Blackboard. The deadline is September 22nd by midnight. The submission should be a PDF file that contains the answers to the required parts and also explanations of the process you took to get your answers and how your answers work to achieve the desired results. Screenshots of important steps, such as memory/register status shown by gdb or other tools, are not required, but you can include them if they think they facilitate your answers. Do not include screenshots for all steps. Note that it’s not enough to just give answers, you need to explain in detail how you got the answers.