Project 2: Hack into a server

Overview

The objective of this project is to compromise an Ubuntu Linux account running a vulnerable Web server process. You can access a full copy of the server program C source code in the course web or in your git repo. You should download the source, compile it, and analyze the program's behavior.

You will use a shellcode to compromise the server, which is described in the links below. Once you have compromised your server, you should modify the files hosted by the server as proof. As you start trying to compromise your server, it will likely seg fault. However, servers will restart after they crash. In particular, you will need to use a reverse shellcode so that you can remotely send commands to the server you take over.

There are five stages to completing the project

1. Find the stack-smash vulnerability in the server code.

2. Run a local copy of the server and use gdb to identify where the relevant variables are stored in memory.

3. Construct a request string that will exploit the vulnerability by overwriting variables stored on the stack. Choose a shellcode and incorporate it into the request string. Suggested shellcode for P2

4. Mount an attack to "pwn" your local webserver process. Make sure that you can reliably exploit a server process running on your local machine before trying to exploit your remote server.

5. Pwn your assigned remote webserver process and create a new file or modify index.html. This may take many attempts since you cannot be exactly sure where the remote server's stack is located. Most groups write scripts to automate and vary repeated attacks until they succeed.

To submit your project, push the following to your git repo: all files (including source/scripting) used in your exploit, and a text file called README.txt with a short explanation of your method of attack and any experience that might be useful or interesting.

Building and running your local server

You can run a local copy of your server on any sufficiently safe Linux system. You want it to run as a 32-bit process, even if it is a 64-bit system. Just build the program with the -m32 compiler option. See the example and other notes below. That produces a 32-bit executable program file. A 64-bit Linux system runs a 32-bit program as a 32-bit process with a 32-bit virtual address space.

If you are running your own Linux system under Docker or a VM, you might need to install some packages to enable 32-bit compilation. On Ubuntu/Debian: apt-get install libc6-dev-i386

Safe stacks: if you choose to smash, smash responsibly. This activity is fun and healthy but it can be dangerous. Practice good hygiene: please do not leave these processes running when they are not in active use. Use protection: run your processes in a contained environment (container or VM). Don't use public machines: if someone else hacks one of your processes (easy by design) they will control your user account. OIT Security and CS Staff are aware of 310test, but they should not be finding vulnerable processes lying around the network. Have fun, but stay safe!

Lower your shields

Make sure that your local web server process has its shields down before you try to attack it.

1. Disable Address Space Layout Randomization (ASLR) for your server process. You can do this by first starting a shell with the command setarch `uname -m` -R /bin/bash. Then run your server under that shell. The setarch command sets a process attribute that is inherited by all child processes. The -R option sets an attribute to disable ASLR.

2. Disable No-Execute (NX) and "stack canary" defenses for your server process. To do this, compile using the proper flags: -z execstack -fno-stack-protector.

For example, to compile webserver.c to run as an unshielded 32-bit process: gcc -m32 -z execstack -fno-stack-protector webserver.c -o webserver

Note: if you run your local server in a Docker Desktop container, you need to run that container with some special options to allow you to disable ASLR. See the notes on using Docker Desktop for the labs.

Connecting to the server

You can connect to your server (local or remote) using a web browser. It returns a short default web page (index.html) announcing itself. However, this web server is very simple, and it is unstable under attack, and browsers may expect too much from it. Some browsers behave erratically with this lab.

It is easier to use the "netcat" utility (i.e., the nc command) to send messages to your server. Netcat connects to the server and sends its stdin to the server over the socket, and outputs whatever it receives from the socket to its stdout.

For example, to request the file index.html from your server using HTTP, you could type in echo -e "GET /index.html HTTP" | nc YOUR_SERVER YOUR_PORT, where YOUR_SERVER is replaced with your server's hostname and YOUR_PORT is replaced with your server's port.

You can also run a bare netcat and type directly into its stdin. This might be useful when your attack is a portbind shell and you want to connect to the attack port and type shell commands. The verbose option nc -v is useful to see connection status. For example, type nc -v YOUR_SERVER YOUR_PORT, hit return, and if you get a successful connection type: GET /index.html HTTP. You should receive the HTML source for the default web page. If the webserver returns an HTTP 404 error (not found), make sure you are running the webserver with the correct current directory. See the source code for other reasons it might return a 404.

If the connection fails, it may be because your network provider disables connections to your server from outside of the local host. For example, OIT appears to block incoming network connections to unauthorized ports on its VM service. But that's OK: you can attack your local webserver locally (as localhost) and nobody will know.

localhost. You can connect to your local server from a netcat or other process running on the same machine, e.g., under a different shell. Use the pseudo-hostname localhost in place of YOUR_SERVER in the commands above.

Where's the stack?

Your remote server runs on classic 32-bit Linux, where stack addresses start at 0xc0000000. In classic 32-bit Linux, the top (highest) gigabyte of the 32-bit address space is reserved for the kernel. The process stack starts just below that (0xc0000000) and grows down (toward lower addresses).

But the stack might show up at those reserved addresses when you run your local server as a 32-bit process on a 64-bit system. In that scenario the (64-bit) kernel is not mapped into the (32-bit) process virtual address space. The process 32-bit process therefore has the whole 4GB for its own use, and stack addresses are chosen accordingly. You can force the classic 3GB layout by adding the -3 option to setarch command above. See this stackoverflow post.

Note that stack addresses might vary based on the size of the process environment variables, and running under gdb can also move the stack lower to make room for additional data at the base. See this stackoverflow post with more info and a way to make sure that the execution environment is the same with and without debugging.

Note: gdb outputs assembly in the AT&T assembly syntax, which means mov %eax, %ebx will copy the value of eax into ebx. Intel syntax switches the operands.

Launching your attack

Your objective is to send the webserver a request string with embedded shellcode instructions that causes the webserver process to execute your shellcode. The shellcode takes over the process and lets the attacker use the process to attack the system.

"Shell storm" is a great place to find shell codes. Suggested shellcode for P2.

The suggested shellcode will "portbind a shell in port 5074". That means that the shellcode does a bind() system call for port 5074, does an accept() syscall to accept an incoming connection, dups the connection socket to its stdin and stdout, and then execs a shell program from a standard file path on the victim system.

So: after the webserver is hacked, the attacker can connect to the bind port (e.g., with nc -v) and input the attacker's chosen shell commands. The hacker lingo for this concept is bindshell. Be sure you understand how those syscalls work to construct the attack.

But: please do not use the default port 5074 as your bind port! You must modify the shell code to use a different port. Instead, please use a standard base + your group number as your bind port. For the base, choose one of 10000, 11000, 12000, and so on. Stay below 32K.

Why change the port? Because your attack fails if you pick a port that is already bound by some other process. You should understand why. The bind() syscall in the shellcode fails with "port is already in use" (EADDRINUSE). But the shellcode does not check the error return. It keeps going and tries to accept() on the socket, which also fails (because the socket is unbound), and so on. Eventually the process either generates an unrecoverable exception or succeeds in exec'ing a shell whose stdin and stdout are duped to an unconnected socket. Either way the pwned process exits and the attack fails.

You must modify the shellcode to embed your chosen port number. Convert it to hex and put it in the right place. For example, 5074 is 0x13d2, the number that appears as a constant in the suggested shellcode. It is not hard to find.

Notes and hints

Resources

Please post to Piazza with any difficulties you are having.

The instructor and TA can post tips that may point you and others in the right direction.

Read the document "Smashing the Stack for Fun and Profit" for an introduction to call stack vulnerabilities.

Look at "Shellcoding for Linux and Windows Tutorial" for shellcode samples for Linux. Past students have found this to be extremely useful.