Introduction
The MSDN documentation states that Winsock Helper Functions (WSHX) are “obsolete for Windows Server 2003, Windows Vista, and later…”. However, Helper DLLs continue to be used by the latest build of Windows 10 to implement sockets for TCP, Infrared, Bluetooth, Hyper-V, and in more recent years, the unix socket address family. The MSDN already describes in sufficient detail the Helper DLL architecture, so this post will only focus on one of the many ways Helper DLLs can be used for code redirection/process injection. The configuration for a specific Helper DLL has been misused in the past as a persistence method, although that will not be discussed here.
Winsock Helper DLL Info
A list of transports can be found under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Winsock\Parameters
as shown in figure 1.

Figure 1: Transports supported by Windows 10 VM.
Each string corresponds with a Helper DLL, the details of which can be found under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\Parameters\Winsock
. The Tcpip transport is shown in figure 2.

Figure 2. Transport configuration.
When a socket is created with a call to socket() or WSASocket(), a WINSOCK_HELPER_DLL_INFO
structure is populated with information from the registry and the address of exported DLL functions before being inserted into the mswsock!SockHelperDllListHead
structure. The WINSOCK_HELPER_DLL_INFO
isn’t officially documented by Microsoft, however, you can find an earlier version of it online. A few fields for the latest version had to be reverse engineered, so I can only say this is valid for the latest build of Windows 10.
typedef struct _WINSOCK_HELPER_DLL_INFO { LIST_ENTRY HelperDllListEntry; DWORD Unknown; HANDLE DllHandle; INT MinSockaddrLength; INT MaxSockaddrLength; INT MinTdiAddressLength; INT MaxTdiAddressLength; ULONG64 UseDelayedAcceptance; PWINSOCK_MAPPING Mapping; GUID ProviderGUID; PWSH_OPEN_SOCKET WSHOpenSocket; PWSH_OPEN_SOCKET2 WSHOpenSocket2; PWSH_JOIN_LEAF WSHJoinLeaf; PWSH_NOTIFY WSHNotify; PWSH_GET_SOCKET_INFORMATION WSHGetSocketInformation; PWSH_SET_SOCKET_INFORMATION WSHSetSocketInformation; PWSH_GET_SOCKADDR_TYPE WSHGetSockaddrType; PWSH_GET_WILDCARD_SOCKADDR WSHGetWildcardSockaddr; PWSH_GET_BROADCAST_SOCKADDR WSHGetBroadcastSockaddr; PWSH_ADDRESS_TO_STRING WSHAddressToString; PWSH_STRING_TO_ADDRESS WSHStringToAddress; PWSH_IOCTL WSHIoctl; } WINSOCK_HELPER_DLL_INFO, *PWINSOCK_HELPER_DLL_INFO;
Locating SockHelperDllListHead
mswsock!SockHelperDllListHead
is a LIST_ENTRY structure declared as a global variable. The simplest and quickest way to locate the structure is by searching the .data
segment for pointers to heap memory. Before conducting the search, a “dummy” TCP/IP socket should be created locally which initializes the structure. Once the structure is found, the address of the heap pointer is saved as a Relative Virtual Address (RVA) to be added with the base address of mswsock.dll in a remote process. You can try using SymFromName() or IDebugSymbols::GetOffsetByName to locate the exact address, but the PoC searches the .data
segment thus avoiding any dependency on debugging symbols.
Locating Processes
Any process that uses Winsock functions is a potential vector for injection. However, to keep it simple, only processes that listen for incoming connections on TCP/IPv4 ports are selected by the PoC. The process must also allow us to open it for reading and writing Virtual Memory necessary for overwriting the function pointers. Figure 3. shows a list of available processes on a Windows 10 VM using this criteria.

Figure 3. TCP/IPv4 ports listening on Windows 10
The spooler service shown in figure 4 seems to use infrared sockets.

Figure 4. IrDA socket functions for the Print Spooler.
The Remote Procedure Call (RPC) and RPC Endpoint Mapper that are critical to Component Object Model (COM) and Distributed COM (DCOM) seem to use Hyper-V sockets, as shown in figure 5.

Figure 5. Hyper-V socket functions for the RPC service and RPC Endpoint Mapper.
A number of ways to trigger execution of the Winsock functions exist, but by far the simplest way is connecting to a listening port that results in a call to Tcpip4_WSHGetSocketInformation. This function is used for both IPv4 and IPv6, so it doesn’t matter what version of the protocol a process uses. Another way worth mentioning, but only affecting the Windows DNS Cache service requires using DnsQuery_A to resolve an IP address. The query results in a call to one of the WSHOpenSocket* functions that try and establish a connection to a remote DNS server. Figure 6. shows TCP/IPv4 functions.

Figure 6. TCP/IPv4 functions in mswsock.dll
Process Injection
In figure 7 you can see notepad running under the spooler service. I used this as a quick demo to show that the injection works.

Figure 7. Notepad running under the Print Spooler service after injection.
Below is the main injection code. See the full PoC here for more details about how it works.
VOID inject(DWORD pid, WORD port, LPVOID payload, DWORD payloadSize) { DWORD rva, r; HANDLE hp; WINSOCK_HELPER_DLL_INFO hdi; LPVOID cs, addr; SIZE_T wr; SOCKET s; struct sockaddr_in sin; // 1. Try open process for reading/writing VM hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if(hp == NULL) { printf("Unable to open PID : %i\n", pid); return; } // 2. Get helper DLL entry for TCP v4 addr = GetHelperDLLInfo(hp, pid, &hdi); if(addr != NULL) { // 3. Create a windows socket and write the payload to remote process s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); cs = VirtualAllocEx(hp, NULL, payloadSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hp, cs, payload, payloadSize, &wr); // 4. Update the function pointer with pointer to payload WriteProcessMemory( hp, (PBYTE)addr + offsetof(WINSOCK_HELPER_DLL_INFO, WSHGetSocketInformation), &cs, sizeof(ULONG_PTR), &wr); // 5. Trigger it with connection to the port on localhost sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = inet_addr("127.0.0.1"); connect(s, (struct sockaddr*)&sin, sizeof(sin)); // wait a moment before restoring pointer Sleep(0); // 6. Restore function pointer and clean up WriteProcessMemory( hp, (PBYTE)addr + offsetof(WINSOCK_HELPER_DLL_INFO, WSHGetSocketInformation), &hdi.WSHGetSocketInformation, sizeof(ULONG_PTR), &wr); VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE); closesocket(s); } CloseHandle(hp); }
Summary
It’s possible to modify Winsock Helper Functions in a remote process to execute a payload. The PoC exploits listening services simply because they are the easiest to use for injection. You may argue these services run with a high-integrity level and are therefore unlikely to be exploited by a real attacker. Many low-integrity processes use Winsock helper functions for outgoing connections and there are ways via other I/O mechanisms such as RPC, ALPC, and Window Messages that can lead to execution of the same helper functions. There are also interfaces made available to administrators that when accessed will also use Winsock helper functions to perform certain tasks. I’ve only scratched the surface on this, but it’s not just TCP/IP listeners that are affected.