Merge pull request #44 from maxDcb/develop

Develop
This commit is contained in:
Maxime dcb
2025-10-15 14:48:57 +02:00
committed by GitHub
19 changed files with 2520 additions and 1840 deletions
+8 -1
View File
@@ -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
+79 -4
View File
@@ -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:
+40 -2
View File
@@ -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,
+3 -2
View File
@@ -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
+1
View File
@@ -11,3 +11,4 @@ openai==1.102.0
pytest==8.4.1
pytest-qt==4.5.0
donut-shellcode
markdown
+49 -50
View File
@@ -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
-11
View File
@@ -1,11 +0,0 @@
{
"signing": {
"profiles": {
"default": {
"usages": ["signing", "key encipherment", "server auth", "client auth"],
"expiry": "8760h"
}
}
}
}
-16
View File
@@ -1,16 +0,0 @@
{
"CN": "Example CA",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "San Francisco",
"O": "Example",
"OU": "CertificateAuthority",
"ST": "California"
}
]
}
-17
View File
@@ -1,17 +0,0 @@
{
"CN": "TestClient",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "San Francisco",
"O": "Example",
"OU": "SRE-Operations",
"ST": "California"
}
]
}
+107 -8
View File
@@ -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
-17
View File
@@ -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
+19 -3
View File
@@ -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;
File diff suppressed because it is too large Load Diff
+29 -14
View File
@@ -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"
}
]
}