mirror of
https://github.com/maxDcb/C2TeamServer
synced 2026-06-06 16:14:27 +00:00
+8
-1
@@ -1,4 +1,11 @@
|
||||
Tests
|
||||
C2Client/build/
|
||||
.vscode
|
||||
build/
|
||||
build/
|
||||
C2Client/.cmdHistory
|
||||
C2Client/.termHistory
|
||||
C2Client/Beacon.exe
|
||||
C2Client/C2Client/Scripts/__init__.py
|
||||
C2Client/C2Client/TerminalModules/__pycache__/
|
||||
C2Client/loader.bin
|
||||
updateRelease.sh
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
c2client = "C2Client.GUI:main" # Entry point for CLI tool
|
||||
|
||||
@@ -11,3 +11,4 @@ openai==1.102.0
|
||||
pytest==8.4.1
|
||||
pytest-qt==4.5.0
|
||||
donut-shellcode
|
||||
markdown
|
||||
|
||||
@@ -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 <domain>" >&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 <<EOF
|
||||
cat <<EOF > 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 <<EOF > 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 <<EOF
|
||||
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
basicConstraints=CA:FALSE
|
||||
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
[ alt_names ]
|
||||
DNS.1 = ${DOMAIN}
|
||||
|
||||
DNS.2 = www.${DOMAIN}
|
||||
IP.1 = 192.168.1.2
|
||||
IP.2 = 192.168.1.3
|
||||
EOF
|
||||
|
||||
# Create SSl with self signed CA
|
||||
openssl x509 -req -in "${DOMAIN}.csr" -CA rootCA.crt -CAkey rootCA.key \
|
||||
-CAcreateserial -out "${DOMAIN}.crt" -days 365 -sha256 -extfile cert.ext
|
||||
|
||||
openssl x509 -req \
|
||||
-in ${DOMAIN}.csr \
|
||||
-CA rootCA.crt -CAkey rootCA.key \
|
||||
-CAcreateserial -out ${DOMAIN}.crt \
|
||||
-days 365 \
|
||||
-sha256 -extfile cert.conf
|
||||
rm -f csr.conf cert.ext "${DOMAIN}.csr" rootCA.srl
|
||||
|
||||
popd >/dev/null
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"signing": {
|
||||
"profiles": {
|
||||
"default": {
|
||||
"usages": ["signing", "key encipherment", "server auth", "client auth"],
|
||||
"expiry": "8760h"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"CN": "Example CA",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "US",
|
||||
"L": "San Francisco",
|
||||
"O": "Example",
|
||||
"OU": "CertificateAuthority",
|
||||
"ST": "California"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"CN": "TestClient",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "US",
|
||||
"L": "San Francisco",
|
||||
"O": "Example",
|
||||
"OU": "SRE-Operations",
|
||||
"ST": "California"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
Submodule core updated: 4cd9ad35c3...2f6652ddbb
@@ -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;
|
||||
|
||||
+2106
-1677
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,7 @@
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#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<teamserverapi::Listener>* 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<teamserverapi::Session>* 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<teamserverapi::CommandResponse>* 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<teamserverapi::CommandResponse>* 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<spdlog::logger> m_logger;
|
||||
@@ -87,4 +95,11 @@ private:
|
||||
std::unordered_map<std::string, std::vector<int>> m_sentResponses;
|
||||
|
||||
std::vector<C2Message> m_sentC2Messages;
|
||||
|
||||
std::string m_authCredentialsFile;
|
||||
std::unordered_map<std::string, std::string> m_userPasswordHashes;
|
||||
bool m_authEnabled;
|
||||
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_activeTokens;
|
||||
std::chrono::minutes m_tokenValidityDuration;
|
||||
mutable std::mutex m_authMutex;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"ServCrtFile": "server.crt",
|
||||
"ServKeyFile": "server.key",
|
||||
"RootCA": "rootCA.crt",
|
||||
"AuthCredentialsFile": "auth_credentials.json",
|
||||
"xorKey": "dfsdgferhzdzxczevre5595485sdg",
|
||||
"ListenerHttpConfig": {
|
||||
"uri": [
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"token_ttl_minutes": 60,
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"password_hash": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"
|
||||
},
|
||||
{
|
||||
"username": "analyst",
|
||||
"password_hash": "f44ceb062e35dfeea6ed7f8524d53bb0bff19f553e25cae7ef4850e4185ccbba"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user