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

Project Goals


The goals of this project are:

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

Administrative Information


The project is an individual project. It is due on Monday, May 18, 2020, 23:59:59 PST (no 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 remote server applications. Specifically, your objective is to create and then inject appropriate exploit inputs into each target application so that you force the program to return a flag. Like for previous challenges, you will first go to our CTFd site and launch your service instances. The site will give you the address and destination port where your server is running and listening for your connections.

For this challenge, we have prepared three vulnerable services. The first two applications (minecraft_hello and minecraft) are easier to exploit, and if you succeed with these two, you will receive full credit for this project. The third application (lazy_panel) is significantly more challenging. You will receive extra credit (an additional 10% of your project score) if you manage to exploit it. Although we don't expect all students to be able to do so, we encourage everyone to at least give it a try. However, to keep it challenging, we don't plan to provide a lot of support for this part.

You can find the source code (for the first two applications) and the binaries (for all three) from here. The first two applications are compiled for 32-bit x86 on Linux. The third application is compiled for 64-bit. The binaries running on the server are the exact copies of the binaries you get.

Our service is launched by xinetd. Xinetd is a wrapper that listens on a specific port and starts a corresponding program. When it receives data from the network, it will forward it to the (standard) input of the program. Similarly, it will send data that it gets from the program (written to the standard output) back over the network. To talk to the services and come up with exploits, we recommend pwntools.

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).

Hints


Performing a successful remote buffer 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.

The first two applications: minecraft_hello / minecraft

  • The "mine" piece of the service implements an information leakage vulnerability. This allows you to read data from the stack of the remote program. This will come in very handy for you to understand what values are stored on the stack. Moreover, it will also be crucial to help you bypass certain security protections (such as stack canaries and random stack locations, due to ASLR).
  • 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! Also, this "hello world" challenge does not use stack canaries.
  • For the second service (minecraft), the win() function is no longer useful. Bummer! :) In addition, we have enabled stack canaries. That is, the compiler will insert a value before the return address that protects from buffer overflows. This means that you need to make sure that you include the canary value as part of your buffer overflow so that the correct value remains at the right position.
  • At this point, you will also 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' shellcraft 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.
  • While -no-pie makes sure that the code is always at the same location, this is not true for the stack. Our system has ASLR enabled. This means that, for each execution of a program, the operating system will place the stack segment at a random value. This makes it very difficult to predict where the stack will be, and as a result, it makes it hard to know the address of your buffer where you will put the shellcode. This is where the mining part (the info leak vulnerability) comes in handy. Find a value on the stack that points into the stack, and use this as an anchor to understand where the stack of the current instance (of the application that is running) is located. Then, adjust the address that you jump to accordingly to make it point to the buffer that you have overflown.
  • 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 different. For an accurate representation of the addresses, attach to a running process instead of starting the process in gdb.
  • Since you cannot debug the vulnerable programs directly (as it runs remotely), you might want to run a local version of the vulnerable program and try to exploit it first.
  • 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 Ghidra first (it's free!). You may also want to check out IDA, Radare2 or Binary Ninja (they have a freely accessible cloud version).
  • The flag is stored in the file /flag.

The third application (for extra credit): lazy_panel

  • lazy_panel requires you to first crack/bypass/guess-your-way-through a poor password check. Only then can you exploit a buffer overflow.
  • For this application, we have disabled stack canaries. However, we have enabled data execution protection (DEP). Thus, you can no longer just inject shellcode into the buffer on the stack and jump there. Instead, you have to launch your exploit using return-oriented programming (ROP). Adopting rop'ing tools like angrop will definitely make your life easier.
  • This time, you will have to reverse engineer the binary to understand the program logic. Also remember that different from the former applications, this application is compiled to be 64-bit.
  • The flag is stored in the file /flag.
  • Well... no more hints. Good luck!