This is an individual project of CS3273 – Data Protection and System Security. I did the project in my year 2 2021/22 Semester B.
Course Instructor: Dr. Jun HUANG
Project Instruction
1 Overview
Buffer overflow is defined as the condition in which a program attempts to write data beyond the boundary of a buffer. This vulnerability can be used by a malicious user to alter the flow control of the program, leading to the execution of malicious code. The objective of this lab is to gain practical insights into this type of vulnerability, and learn how to exploit the vulnerability in attacks. You will be given a bank server/ client program with a serious buffer-overflow vulnerability. Your task is to develop schemes to exploit the vulnerability and finally 1) remotely run your malicious code on the server; and 2) redirect the control flow of the server to hack into an arbitrary bank account. In addition to the attacks, you will also experiment with several buffer overflow countermeasures and discuss solutions to contain a successful attack.
This lab covers the following topics:
• x86 stack layout and calling convention
• Buffer overflow vulnerability and attack
• Address randomization, Non-executable stack, and StackGuard
• Linux privilege and access control of file system
2 The Bank Server/Client Program
The bank server and client are implemented in server.c, client.c, and bank.h. The server operates on a binary database saved in db.bin. The bank.h defines basic structures and operation interfaces. The server.c works as an entry point of the server which initializes the server, accepts user connection, authenticates user, and then starts a request handler for the connected user if authetnication is successful. The client.c implements a benign client for your reference. It sends a login request with correct username and passcode, and then queries account status, changes passcode, and transfers some money from the instructor’s account to the TA’s account.
2.1 bank.h
The bank.h defines the structure of bank account, bank database, and the format of request messages. It
also implements interfaces for the client to send different requests, as well as request handlers for the server to process and reply these requests. Each bank account in db.bin has three fields, including a username defined as an array of 32 chars, a passcode defined as an array of 16 chars, and a balance field defined as an unsigned int. There are a total of 71 accounts. Each of you has an account, with your EID as the username and your Student ID as the passcode. Your default balance is 1000. Three account operations have been implemented, including query which allows you to check your account status, changePasscode which allows you to change your passcode, and transfer which allows you to transfer money to another account. Before performing these operations, the user must provide the correct username and passcode in order to login into the account.
You need to know the format/size of the login message and be aware that the bank accounts are saved in an array. Unless otherwise stated, it is not necessary to understand other codes in this file.
2.2 server.c
The server.c initializes the server and provides an entry point for connected users. It has two procedures
namely main and auth. You need to analyze all code in this file in detail but do not need to know how
procedures invoked by main and auth are implemented, unless otherwise stated.
The main procedure loads the binary database (db.bin) into memory, creates a TCP server, and then starts
a loop to accept users’ connections and handle their requests. After a connection is established between
the server and the client, the user must provide the correct username and password in a ReqLogin
message (the format is defined in bank.h) in order to perform subsequent operations (e.g., query, change
passcode, and transfer) on the account.
After a user connection is established, main will call auth, which waits for and handles the user’s login
request. The auth procedure takes one parameter, i.e., the descriptor of the network connection which is
used to receive the user’s message. It returns the id of the user (i.e., the index of the user’s account in the
account array) to main. For example, the id of the TA’s account (username = zihaowen2, passcode =
000000) is 71, because it is the last account in the database. If authentication failed, the auth procedure
will return an error code, which is a negative value that indicates the reason of login fail.
If authentication is successful, a reqHandler procedure will be started to serve the user’s other requests.
The reqHandler takes two parameters, including the id of the user returned by auth, and the descriptor
of the network connection used to interact with the client.
2.3 client.c
A benign client is implemented in client.c for your reference. It shows you how to construct a login mes-
sage. After successful login, the client.c starts an interactive command line procedure, in which you can
type command such as query, change 123456 which changes passcode to 123456, and transfer
jhuan9 100 which transfers 100 to jhuan9’s account.
3 Task 1: Identifying Buffer Overflow Vulnerability
The auth procedure of main.c has a serious buffer overflow vulnerability. Specifically, the receiveMessage
procedure receives a message from the network socket connection and saves it to msg. To facilitate pro-
cessing, the server will further copy msg into the local variable req so that it can access it based on the
structure of ReqLogin. Please answering the following questions in lab report.
Question 1.1: The receiveMessage procedure in auth receives client requests from a network connection specified by the descriptor (i.e., the first parameter). Read the code of receiveMessage in bank.h and study read and memcpy by searching them in Linux man pages (man7.org/linux/man-pages/index.html). In the lab report, please explain how the invocation of memcpy in auth may cause a buffer overflow.
My Answer:
- We can start explain the problem from function of receiveMessage, the read() function inside it attempts to read up to MSGLEN bytes from file descriptor connfd into the buffer starting at msg. Although this function allow the system to get the length of message, there is a chance that n is not equal to msg when it is trying to use the memcpy() to copies n bytes from memory area msg to memory area req.
- As function of memcpy() in auth.c doesn’t check the bounds. There is no argument specify what to do if the length of memory is incorrect. It will lead to the system can’t assign the correct space to the buffer. In simple word, we are only copying the entered data into the buffer directly via the memcpy(). The program would be confused and can’t handle such abundant data.
- So, it may lead to problem like there is not enough space in the target buffer for all the data we want to copy from the source buffer, it will cause a buffer overflow.
4 Task 2: Remote Code Execution Attack
Attack goal. In this task, you are required to exploit the buffer overflow vulnerability identified in Task 1 to
realize a remote code execution attack. Your objective is to remotely shut down the bank server to make a
Denial-of-Service (DoS) attack.
Compilation. To realize remote code execution, you need to turn off buffer overflow countermeasures of the compiler. In addition to disabling ASLR as described in Section 3, you need to turn off the non-executable stack and StackGuard schemes. To do so, you can compile server.c as follows,
$ gcc -z execstack -fno-stack-protector server.c -o server
Making server a privileged program. Shutting down a computer requires a privileged system call. In order to make the server a privileged program, you need to change its owner to root and turn on the Set-UID bit using the following commands.
$ sudo chown root server
$ sudo chmod 4755 server
Programming 2.1: Please write a program that can send a bad login request to remotely shut down the server. A template program named attack i.c is available on Canvas. A piece of shellcode for shutting down a computer is provided as an unsigned char array named sh in attack i.c. Please complete attack i.c by embedding sh and properly structuring and constructing the content of the bad login request. You can also program in python if you feel more comfortable. However, you may need to write the client socket in python by yourself. If you do so, please name your python source code as attack i.py.
Question 2.1: In the lab report, please describe how you determine the structure and content
of the bad login request. Your answer should be clear and provide sufficient explanations.
My Answer:
- First line, we will construct the payload with an unsigned 128-byte value.
- The second line will be the return address, we get this address by using gbp from the function auth’s ebp+0x8. For example, if the auth’s ebp is 0xbfffefe8, then the return address will be 0xbfffefe8+0x8= 0xBFFFEFF0.
- The memcpy() function allows us to copy 4 byte of the return address to the payload address. Then we will embed the shell code in the payload and send to the server which will cause the program to overflow the destination buffer.
- Result of the server: Being Shut Down and the entire ubuntu virtual box even crashed!
Question 2.2: Re-compile the server program without making it a privileged program. Specifically, do not change the owner of server to root after compilation. Meanwhile, do not turn on the Set-UID bit. Repeat the experiment by sending the bad login request again. Write down the output of the server and explain your observation.
My Answer:
Let’s try to figure it out by understanding the on-executable stack, StackGuard schemes, and a privileged program.
First test, don’t set the server as a privileged program and turn off the stack protector and on-executable stack counter measurement and run the attack.
Output Result:
Explanation: In the first line of output, the connection between me and the server is being reset. I believe the shell code doesn’t contain sufficient to do its operation. It can’t send messages to announce maintenance and ask users to log out and close all open programs by the wall message. It is supposed the messages will be shown to all logged-in users with a terminal open. Because of the server is not a privileged program which means it isn’t a root or sudo. Even the server is trying to run the shell code as the attacker successfully embed it by buffer overflow, it can’t do many operations which requires sudo privileged. A system shutdown will definitely need it or some of the other special permissions (usually handled by polkit and/or system etc).
Second test, set the server to privileged program and turn on the stack protector by not running -fno-stack-protector nor on-executable stack counter measurement while compiling the server.
Output Result:
Explanation: By enabling the stack protector on default, it enables run-time stack overflow verification using a stack canary and stack corruption checking. So, the server is added canary value into it, whenever the value is being changed, it means there is a buffer overflow vulnerable detected. By verifying it, it may terminate the execution of the affected program, and prevent it running the shell code embed from the attacker.
5 Task 3: Control Flow Hijacking Attack
Attack goal. In this task, you are required to exploit the buffer overflow vulnerability identified in Task 1
to realize a control flow hijacking attack. Your objective as an attacker is to hack into an arbitrary account
specified by a user ID (i.e., the user’s index in the account array of bank database) without providing the
correct username and passcode.
As an important requirement, you must ensure that the server can continue operation normally (i.e., correctly accept and handle legal user’s connections and requests) after the disconnection of attacker.
Hints. To hack into an arbitrary bank account specified by a given id, you need to redirect the control
flow of server after the call of auth to invoke reqHandler with correct id (i.e., the index of the victim
user’s account in database array) and connfd (i.e., the network connection descriptor used to interact with
the attacker’s client), even if auth returns a negative result. The first parameter of reqHandler, i.e., the
pointer of bank database, will not be affected by your stack overflow attack because it is a global variable
and is stored in data segment.
Compilation. Since this attack does not require executing any malicious code on the vulnerable server, you can leave the non-executable stack countermeasure on. However, StackGuard needs to be disabled. To do this, please compile server.c using the following command.
$ gcc -fno-stack-protector server.c -o server
Question 3.1: In the lab report, please draw the stack frame layout of main and auth before
CPU returns from auth to main after processing a *benign* login request. Clearly label the stack with
memory addresses in hex. As example is given in the page 13 of Tutorial 04’s slides. Please be reminded
again that the starting address of stack frame you observed in gdb is different from the true address while the server is running. However, the stack frame structure is the same, i.e., only the starting address of stack frame is different. To help you solve this problem, we have added some codes in the server program to print out the actual ebp values of main and auth.
My Answer:
Question 3.2: Please use objdump to disassemble the binary executable of server. You can do
this using the following command.
$ objdump -D server > server.dump
Read the binary instructions of main and auth in server.dump, and then describe in the lab report that
which instruction address in main should be set as the return point after the call of auth.
My Answer:
The 804990b instruction address in main should be set as the return point after the call of auth. The return address is what memory address the EIP (Instruction Pointer) should be set to after the function’s retn statement is executed. It is the offset address of reqHandler function. By using the buffer overflow attack, the return address will be overwritten, so the attacker can bypass the original authentication of the server, and logic to the bank directly.
Programming 3.1: Please write a program that can send a bad login request to realize this
control flow hijacking attack. A template program named attack ii.c is available on Canvas. Please
complete attack ii.c by properly structuring and constructing content of the bad login request. You
can also program in python if you feel more comfortable. However, you may need to write the client socket in python by yourself. If you do so, please name your python source code as attack ii.py.
Question 3.3: Note that as a requirement of a successful attack, you must make sure that the
server can continue operation correctly after the disconnection of the attacker. After hacking into an account and disconnecting from the server, please keep the server running and then send a second bad login request with a different target user ID. For example, you can specify the target ID as 70 (i.e., the account of the instructor jhuan9) in the first attempt, and then set it as 71 (i.e., the account of the TA zihaowen2) in the second attempt. Note that the ID of the second attempt must be different from the one in the first attempt. In the lab report, please (1) describe which account you hacked into in the second attempt, is it the target account you specified? (2) explain your observation.
Programming 3.2: Add a procedure into attack ii.c (or attack ii.py if you program
using python) that can correctly structure and construct a bad login request so that the attacker can hack into a specified account in the n-th attempt, where n should be the input parameter of your procedure.
My Answer:
#include "bank.h"
void main()
{
// connect to the server
int fd = createClient("127.0.0.1");
if (fd < 0) {
exit(EXIT_FAILURE);
}
// construct bad login request in below and name it as payload
unsigned char payload[128];
int ret = 0x080494d9;
int id = 71;
int ebp = 0xbffff028;
memcpy(&payload[68], &ret, 4);
memcpy(&payload[44], id, 4);
memcpy(&payload[64], &ebp, 4);
// send the payload to the server
write(fd, payload, sizeof(payload));
// wait for and receive the server's reply
bool success = receiveReply(fd);
if (success) {
cmdlineClient(fd);
}
}
6 Task 4: Fixing the Identified Vulnerability
Question 4.1: Please describe in the lab report how to fix the buffer overflow vulnerability in
the server program.
My Answer:
In order to fix the buffer overflow vulnerability in the program, we can change the memcpy function to memcpy_s, which requires the programmer to specify the maximum length of the target first. So further speaking, it acts as a barrier to prevent buffer overflow when that number is provided independent of the number of bytes to copy.
Question 4.2: Please restart the VM without turning off ASLR. Run the server program again
for five times and write down the ebp addresses of main you observed. Explain how ASLR makes buffer
overflow attacks more difficult. Discuss possible countermeasures that an attacker can take to defeat ASLR.
My Answer:
- ebp address: bffff028
- ebp address: bff43ad8
- ebp address: bfec7228
- ebp address: bffff000
- ebp address: bf99a058
By using ASLR, it randomizes the memory address space, making attacker difficult to predict the memory address layout of the program as all the entry point of the program is being randomized. But the attacker can still probe the memory by brute-forcing until they find the proper location where another app runs and then modify their code to target that memory address space.
Question 4.3: Re-compile the server program with all countermeasures on. You can do it by,
$ gcc server.c -o server
In the lab report, please write down the size of gap between auth’s old ebp field and local variables. Explain in more detail how you get this number. Please write down the content placed in this gap and explain how it prevents buffer overflow attacks.
My Answer:
This gap is called Stack Canary. It detects modification of return address on stack before it is used by RET. Compiler generates code that pushes a “canary” value on stack at function entry, pops and checks values before return.
7 Task 5: Containing Successful Attacks
Question 5.1: Privilege separation and isolation are two key principles to make system robust
against successful attacks. Similar with the Google security architecture, OKWS is a web service system that practices these two principles. Please read the paper of OKWS and watch the online video of MIT 6.858 2020 Lecture 5 at tinyurl.com/sr3jm2bt if necessary. In the lab report, please briefly explain the Figure 1 in the OKWS paper and discuss what is its security benefits.
My Answer:
The main feature of OKWS is to split the web server process into multiple processes, each with different, minimal privileges, running as different user IDs. It uses UNIX isolation mechanisms to prevent subsystems from reading or modifying each other’s data. In the figure 1 of the OKWS paper, ‘okd’ process parses user input, holds no sensitive data. ‘svci’ process parses user input for one service and it runs in chroot()ed “jail”. The chroot()ed jail has no access to any of filesystem, like it can’t access to the many UNIX setuid-root programs, or any sensitive data elsewhere on disk. It has to priori set up all system files needed by process in directory, e.g., shared libraries, etc. The OKWS database proxy(data2) only accepts authenticated requests for subset of narrow RPC interface which can read sensitive data. All of the above feature can both enhance the privilege separation and isolation among the web process. Compared to one process per user, OKWS uses one process per service only which can balance the performance and security at the same time. So even any small component in the system contains vulnerability, attacker has to take a lot of time to exploit other process as all subsystem in child process are using minimal privileges so it can make sure that even if a non-privileged process is compromised, there is nothing for the malware/attacker to do and nowhere else for it to go as well.
Question 5.2: The bank server program used in this lab is vulnerable because all services (i.e.,
login and other request handlers) are implemented in one program. Once one vulnerability is exploited by the attacker, the whole system corrupts. Based on your understanding of the Google security architecture and the OKWS paper, describe a solution to ensure the security of the bank server/client even if the login service has vulnerabilities that can be exploited by the attacker. You answer should include sufficient explanations and provide a figure of the architecture of the modified system (i.e., how the system is divided into isolated services, how they connect and interact with each other, etc.).
My Answer:
The entire server isolation design can be divided into two parts. One is Encryption Zone and Isolated Server Zone. All server includes the database is under the network access group. It is a combined protection partially studied from OKWS and Google security architecture.
All the data will be encrypted in the Encryption Zone. In every secure TLS/SSL connection, information sent back and forth between the client and the front-end server will be encrypted using a special secret key that is generated by the client during the TLS handshake. Without this secret key, neither side can decrypt any messages that are encrypted by the other side. So, it can reduce the chance that packet is being eavesdropping or altered when entering the encryption zone. Apart from that, the firewall is different from traditional perimeter one. It is network-based firewalls which provide a richer environment for filtering high-capacity attacks and unauthorized packets from the outside network. The firewall rules will be as tight as possible. Only allow well-documented and required traffic (ingress and egress) from the client computer, and deny all others.
The front-end server and back-end server work together as a reverse proxy network which is a bit similar to content delivery network (CDN) in Google, but not exactly same. With a reverse proxy, all requests from clients’ computer will go directly to front-end server first, and front-end server will send its requests to and receive responses from back-end server. Front-end server will then pass along the appropriate responses to clients’ computer. Also, only the back-end server has the right to read and edit the database. This will be similar to the OKWS setup, first layer of the network has just enough access privileges to receive and communicate to the deeper layer. The front-end server and clients’ computer can never directly access to the database and lack the privileges to do so. For example, an attacker who gained access to front-end server cannot access the contents of the database either. Because the front-end server acts like a communication channel instead of a processor service.
In conclusion, all the authorization is determined by membership in a network access group. Access to the database server must be restricted to only those clients that have a business requirement to access the data. This includes the service accounts that are used by the front-end servers, and administrators of the database. In addition, access is only granted when it is sent from an authorized computer and all the action will be recorded and stored when investigation is needed. All network traffic from and to the database must be encrypted or it will be rejected. As client computers and front-end server are not members of the network access group, they cannot access the isolated servers directly.