ReversePortForward to test

This commit is contained in:
maxdcb
2026-04-30 14:36:58 +02:00
parent 2458165b9f
commit 6b0a78b191
5 changed files with 580 additions and 244 deletions
+58 -58
View File
@@ -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)
@@ -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
$<TARGET_FILE:ReversePortForward> "${C2_RUNTIME_MODULE_OUTPUT_DIR}/$<TARGET_FILE_NAME:ReversePortForward>")
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
$<TARGET_FILE:testsReversePortForward> "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsReversePortForward>")
add_test(NAME testsReversePortForward COMMAND "${C2_TEST_BIN_OUTPUT_DIR}/$<TARGET_FILE_NAME:testsReversePortForward>")
@@ -10,10 +10,7 @@
#include <memory>
#include <vector>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#ifndef _WIN32
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
@@ -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<std::mutex> 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<std::shared_ptr<RemoteConnection>> connections;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<RemoteConnection> 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<int>(toSend - totalSent), 0);
if (sent <= 0)
{
connection->active = false;
enqueueChunk(connectionId, std::string(), true);
break;
}
totalSent += static_cast<size_t>(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<std::mutex> 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<std::mutex> 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
}
@@ -2,6 +2,14 @@
#include "ModuleCmd.hpp"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include <atomic>
#include <condition_variable>
#include <cstdint>
@@ -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<int, SocketHandle> m_localConnections;
// #else
struct RemoteConnection
{
int id;
@@ -89,7 +98,6 @@ private:
std::mutex m_connectionsMutex;
std::unordered_map<int, std::shared_ptr<RemoteConnection>> m_connections;
std::condition_variable m_queueCv;
// #endif
int m_remotePort;
bool m_socketLayerReady;
@@ -1,120 +1,463 @@
#include "../ReversePortForward.hpp"
#include <iostream>
#include <memory>
#include "../../tests/TestHelpers.hpp"
bool testInit();
bool testInvalidArguments();
bool testErrorMessages();
bool test();
#include <chrono>
#include <functional>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#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<const char*>(&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<unsigned short>(port));
if (::bind(listener, reinterpret_cast<sockaddr*>(&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<sockaddr*>(&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<int>(data.size() - sentTotal), 0);
if (sent <= 0)
return false;
sentTotal += static_cast<size_t>(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<unsigned short>(port));
if (::connect(socket, reinterpret_cast<sockaddr*>(&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<long>(timeout.count() / 1000);
tv.tv_usec = static_cast<long>((timeout.count() % 1000) * 1000);
int ready = ::select(static_cast<int>(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<bool(const C2Message&)>& 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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;
}