CS177: Project 4 - Local Buffer Overflows (20% of project score)

Project Goals


The goals of this project are:

  • to exploit a remote memory corruption vulnerability [10% of project score]
  • to inject shellcode and take control of a process [10% of project score]

Administrative Information


The project is an individual project. It is due on Friday, May 26, 2023, 23:59:59 PST (extended by a day; no further deadline extensions; late flags will not be accepted).

Introduction


A buffer overflow occurs when a program or process tries to store more data in a buffer (or some temporary data storage area) than that buffer was intended to hold. This extra data, which has to go somewhere, overflows adjacent memory regions, corrupting or overwriting the valid data stored there. If the buffer is stored on the stack, as it is the case for local variables in C, control information such as function return addresses can be altered. This allows the attacker to redirect the execution flow to arbitrary memory addresses. By injecting machine code into the process memory (e.g., as part of the data used to overflow the buffer, or in environment variables), the attacker can redirect the execution flow to this code and execute arbitrary machine instructions with the privileges of the running process. Thus, it is imperative that the length of the input data is checked before copied into buffers of fixed lengths. Unfortunately, a number of popular C functions (e.g., strcpy, strcat, sprintf, gets, or fgets) do not perform such length checks, making many applications vulnerable to this kind of attack.

Detailed Description


Your task is to exploit vulnerabilities in two simple applications, called minecraft_hello and minecraft.

For your first buffer overflow challenge, we want to keep things a little simpler. Hence, you will not interact with a remote service but rather log into a server that we have prepared for you. We call this server the Binary Gym (bingym). We tried to make it easier for you and installed many tools that you might need to solve the challenge.

The bingym is a virtual machine that we run for you and that you can access via secure shell (ssh). We will send each of you a SSH key with login information to access your account via email. The gym is hosted on 192.35.222.122.

Once you have logged in, you will find a directory called challenges. When you change into this directory, you will find two folders, one for each of the two challenge applications. You will see the source code for each program, the actual binary that you will need to exploit, and a flag file.

You will notice that you cannot access the flag file directly. This is because it is only readable by the file owner, which has a different user ID (UID) than your own. However, if you check carefully, you will notice that the vulnerable application is owned by the same user (UID) as the flag file. Moreover, its setuid bit is set. Thus, when the vulnerable application is launched, it will assume the effective user ID (EUID) of the file owner. This is exciting! It means that when we launch the vulnerable application, it can actually access the flag file. Now, if only we could force the vulnerable application to read the flag file and give back its content to us. Wikipedia has a good overview of the setuid mechanism if you are not familiar with it.

If you are able to exploit/trick the vulnerable application to read the flag file (or spawn a shell that allows you to read the flag file), you can use CTFd to submit to us the flag and collect your points.

We used gcc to build the binaries from the sources, using different command line flags for the different challenges. You can use checksec to understand what security related features the binary is built with (or without). For this challenge, we pretty much disabled any security protections.

Hints


Performing a successful remote overflow can be tricky, because it is important to get all the little details right. This section lists a few things that you might consider.

When you look at minecraft_hello, you will see that we have added a convenient function called win(). If you manage to craft an input that makes the vulnerable function (after you have overflown the buffer) return there, you might not need any shellcode at all!

For the second service (minecraft), the win() function is no longer useful. Bummer! :-) At this point, you will need a shellcode. If you haven't written a buffer overflow before, check out the standard tutorial article by Aleph One. Everyone interested in security should read this paper, it’s a really good introduction and a classic.

There are powerful tools available that help you craft your shellcode. For example, check out pwntools or venom/metasploit.

Make sure that your shell code works correctly. That is, write a little test program that jumps to your shell code and make sure that it does what you want it to do before proceeding. Also, think about using a NOP sled. It makes exploitation easier if you don't get the address of your shellcode perfectly right.

Use the debugger (gdb) to see what is going on when you try your exploit. The debugger is your best friend. Getting all the addresses right can be difficult. Consider powering up your good old gdb with modern tools like gef. It makes your debugging life much more enjoyable. Keep in mind, however, that when running the program in gdb the addresses of the process can be a bit different.

There are a couple of important details that you should consider as you work with the vulnerable setuid binaries. First, when you try to invoke the shell from a process whose effective user ID (EUID) is different from the real user ID (RUID) -- which is the case in our setup -- the shell will drop the EUID. This is a (pretty brittle) security mechanism. There are a couple of ways around this; for example, you can invoke the shell with the -p parameter. Check out the win() function. Alternatively, you can set the real UID to the effective UID as part of your shellcode before you invoke the shell. Here is a short discussion about this issue.

The second detail is that when you launch the vulnerable application from within the debugger (gdb), it actually does not honor the setuid bit. Instead, it will run the process as your user. This makes sense; otherwise, it would be trivial to exploit setuid programs by just launching them in a debugger and manipulating the execution any way you want. So, use the debugger to craft and refine your exploit and make sure it works as expected, and then run it directly against the vulnerable application.

Because this is a local buffer overflow, you can run the vulnerable program directly in the bingym. This should make things easier.

Note that calling conventions are not always strictly followed. Sometimes, your mental model may differ from the actual code being executed. In addition to debugging, reverse engineering gives you insight about how the program actually executes in gory details. There are many powerful RE tools out there but you may want to try Binary Ninja (they have a freely accessible cloud version). You may also want to check out Ghidra, Radare2, or IDA.