Skip to content

Commit e25ee75

Browse files
authored
Merge pull request #44 from maxDcb/develop
Develop
2 parents b37c452 + 7daf779 commit e25ee75

File tree

19 files changed

+2737
-2057
lines changed

19 files changed

+2737
-2057
lines changed

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
Tests
22
C2Client/build/
33
.vscode
4-
build/
4+
build/
5+
C2Client/.cmdHistory
6+
C2Client/.termHistory
7+
C2Client/Beacon.exe
8+
C2Client/C2Client/Scripts/__init__.py
9+
C2Client/C2Client/TerminalModules/__pycache__/
10+
C2Client/loader.bin
11+
updateRelease.sh

C2Client/C2Client/GUI.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
import argparse
22
import logging
3+
import os
34
import signal
45
import sys
5-
from typing import Optional
6+
from typing import Optional, Tuple
67

78
from PyQt6.QtWidgets import (
89
QApplication,
10+
QDialog,
11+
QDialogButtonBox,
912
QGridLayout,
1013
QHBoxLayout,
14+
QLabel,
15+
QLineEdit,
1116
QMainWindow,
1217
QPushButton,
1318
QTabWidget,
19+
QVBoxLayout,
1420
QWidget,
1521
)
1622

@@ -27,18 +33,75 @@
2733
signal.signal(signal.SIGINT, signal.SIG_DFL)
2834

2935

36+
class CredentialDialog(QDialog):
37+
"""Prompt for credentials when environment variables are absent."""
38+
39+
def __init__(self, parent: Optional[QWidget] = None, default_username: str = "") -> None:
40+
super().__init__(parent)
41+
self.setWindowTitle("Login")
42+
self.setModal(True)
43+
44+
layout = QVBoxLayout(self)
45+
description = QLabel("Login:")
46+
description.setWordWrap(True)
47+
layout.addWidget(description)
48+
49+
self.username_input = QLineEdit(self)
50+
self.username_input.setPlaceholderText("Username")
51+
if default_username:
52+
self.username_input.setText(default_username)
53+
layout.addWidget(self.username_input)
54+
55+
self.password_input = QLineEdit(self)
56+
self.password_input.setPlaceholderText("Password")
57+
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
58+
layout.addWidget(self.password_input)
59+
60+
self.error_label = QLabel("Username and password are required.")
61+
self.error_label.setStyleSheet("color: red;")
62+
self.error_label.setVisible(False)
63+
layout.addWidget(self.error_label)
64+
65+
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
66+
buttons.accepted.connect(self._handle_accept)
67+
buttons.rejected.connect(self.reject)
68+
layout.addWidget(buttons)
69+
70+
def _handle_accept(self) -> None:
71+
username = self.username_input.text().strip()
72+
password = self.password_input.text()
73+
if not username or not password:
74+
self.error_label.setVisible(True)
75+
return
76+
self.accept()
77+
78+
def credentials(self) -> Tuple[str, str]:
79+
return self.username_input.text().strip(), self.password_input.text()
80+
81+
3082
class App(QMainWindow):
3183
"""Main application window for the C2 client."""
3284

33-
def __init__(self, ip: str, port: int, devMode: bool) -> None:
85+
def __init__(self, ip: str, port: int, devMode: bool, credentials: Optional[Tuple[str, str]] = None) -> None:
3486
super().__init__()
3587

3688
self.ip = ip
3789
self.port = port
3890
self.devMode = devMode
3991

92+
username: Optional[str] = None
93+
password: Optional[str] = None
94+
if credentials:
95+
username, password = credentials
96+
4097
try:
41-
self.grpcClient = GrpcClient(self.ip, self.port, self.devMode)
98+
self.grpcClient = GrpcClient(
99+
self.ip,
100+
self.port,
101+
self.devMode,
102+
username=username,
103+
password=password,
104+
)
42105
except ValueError as e:
43106
raise e
44107

@@ -136,8 +199,20 @@ def main() -> None:
136199
app = QApplication(sys.argv)
137200
app.setStyleSheet(qdarktheme.load_stylesheet())
138201

202+
username = os.getenv("C2_USERNAME")
203+
password = os.getenv("C2_PASSWORD")
204+
205+
credentials: Optional[Tuple[str, str]] = None
206+
if username and password:
207+
credentials = (username, password)
208+
else:
209+
dialog = CredentialDialog(default_username=username or "")
210+
if dialog.exec() != QDialog.DialogCode.Accepted:
211+
sys.exit(1)
212+
credentials = dialog.credentials()
213+
139214
try:
140-
window = App(args.ip, args.port, args.dev)
215+
window = App(args.ip, args.port, args.dev, credentials)
141216
window.show()
142217
sys.exit(app.exec())
143218
except ValueError:

C2Client/C2Client/grpcClient.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import os
1010
import sys
1111
import uuid
12-
from typing import Any, Iterable, List, Tuple
12+
from typing import Any, Iterable, List, Tuple, Optional
1313

1414
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/libGrpcMessages/build/py/')
1515

@@ -34,9 +34,21 @@ class GrpcClient:
3434
If ``True`` the SSL hostname check is disabled.
3535
token:
3636
Bearer token used for authentication metadata.
37+
username:
38+
Username to authenticate with. If omitted, environment variables are used.
39+
password:
40+
Password to authenticate with. If omitted, environment variables are used.
3741
"""
3842

39-
def __init__(self, ip: str, port: int, devMode: bool, token: str = "my-secret-token") -> None:
43+
def __init__(
44+
self,
45+
ip: str,
46+
port: int,
47+
devMode: bool,
48+
token: Optional[str] = None,
49+
username: Optional[str] = None,
50+
password: Optional[str] = None,
51+
) -> None:
4052
env_cert_path = os.getenv('C2_CERT_PATH')
4153

4254
if env_cert_path and os.path.isfile(env_cert_path):
@@ -91,11 +103,37 @@ def __init__(self, ip: str, port: int, devMode: bool, token: str = "my-secret-to
91103
raise ValueError("grpcClient: unable to connect") from exc
92104

93105
self.stub = TeamServerApi_pb2_grpc.TeamServerApiStub(self.channel)
106+
107+
if token is None:
108+
if username is None or password is None:
109+
username, password = self._load_credentials_from_env()
110+
token = self._authenticate(username, password)
111+
94112
self.metadata: MetadataType = [
95113
("authorization", f"Bearer {token}"),
96114
("clientid", str(uuid.uuid4())[:16]),
97115
]
98116

117+
def _load_credentials_from_env(self) -> Tuple[str, str]:
118+
username = os.getenv("C2_USERNAME")
119+
password = os.getenv("C2_PASSWORD")
120+
if not username or not password:
121+
raise ValueError(
122+
"grpcClient: missing C2_USERNAME or C2_PASSWORD environment variables for authentication",
123+
)
124+
return username, password
125+
126+
def _authenticate(self, username: str, password: str) -> str:
127+
request = TeamServerApi_pb2.AuthRequest(username=username, password=password)
128+
response = self.stub.Authenticate(request)
129+
if response.status != TeamServerApi_pb2.OK or not response.token:
130+
message = response.message or "unknown authentication error"
131+
logging.error("Authentication failed for user %s: %s", username, message)
132+
raise ValueError(f"grpcClient: authentication failed: {message}")
133+
134+
logging.info("Authenticated against TeamServer as %s", username)
135+
return response.token
136+
99137
def getListeners(self) -> Any:
100138
"""Return the list of listeners registered on the TeamServer."""
101139

C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py

Lines changed: 21 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ def __init__(self, channel):
1515
Args:
1616
channel: A grpc.Channel.
1717
"""
18+
self.Authenticate = channel.unary_unary(
19+
'/teamserverapi.TeamServerApi/Authenticate',
20+
request_serializer=TeamServerApi__pb2.AuthRequest.SerializeToString,
21+
response_deserializer=TeamServerApi__pb2.AuthResponse.FromString,
22+
_registered_method=True)
1823
self.GetListeners = channel.unary_stream(
1924
'/teamserverapi.TeamServerApi/GetListeners',
2025
request_serializer=TeamServerApi__pb2.Empty.SerializeToString,
@@ -66,6 +71,12 @@ class TeamServerApiServicer(object):
6671
"""Interface exported by the server.
6772
"""
6873

74+
def Authenticate(self, request, context):
75+
"""Missing associated documentation comment in .proto file."""
76+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
77+
context.set_details('Method not implemented!')
78+
raise NotImplementedError('Method not implemented!')
79+
6980
def GetListeners(self, request, context):
7081
"""Missing associated documentation comment in .proto file."""
7182
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@@ -123,6 +134,11 @@ def SendTermCmd(self, request, context):
123134

124135
def add_TeamServerApiServicer_to_server(servicer, server):
125136
rpc_method_handlers = {
137+
'Authenticate': grpc.unary_unary_rpc_method_handler(
138+
servicer.Authenticate,
139+
request_deserializer=TeamServerApi__pb2.AuthRequest.FromString,
140+
response_serializer=TeamServerApi__pb2.AuthResponse.SerializeToString,
141+
),
126142
'GetListeners': grpc.unary_stream_rpc_method_handler(
127143
servicer.GetListeners,
128144
request_deserializer=TeamServerApi__pb2.Empty.FromString,
@@ -180,6 +196,33 @@ class TeamServerApi(object):
180196
"""Interface exported by the server.
181197
"""
182198

199+
@staticmethod
200+
def Authenticate(request,
201+
target,
202+
options=(),
203+
channel_credentials=None,
204+
call_credentials=None,
205+
insecure=False,
206+
compression=None,
207+
wait_for_ready=None,
208+
timeout=None,
209+
metadata=None):
210+
return grpc.experimental.unary_unary(
211+
request,
212+
target,
213+
'/teamserverapi.TeamServerApi/Authenticate',
214+
TeamServerApi__pb2.AuthRequest.SerializeToString,
215+
TeamServerApi__pb2.AuthResponse.FromString,
216+
options,
217+
channel_credentials,
218+
insecure,
219+
call_credentials,
220+
compression,
221+
wait_for_ready,
222+
timeout,
223+
metadata,
224+
_registered_method=True)
225+
183226
@staticmethod
184227
def GetListeners(request,
185228
target,

C2Client/pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ dependencies = [
1717
"pwn==1.0",
1818
"pefile==2024.8.26",
1919
"openai==1.102.0",
20-
"donut-shellcode"
20+
"donut-shellcode",
21+
"markdown"
2122
]
2223

2324
[project.optional-dependencies]
@@ -41,4 +42,4 @@ C2Client = [
4142
]
4243

4344
[project.scripts]
44-
c2client = "C2Client.GUI:main" # Entry point for CLI tool
45+
c2client = "C2Client.GUI:main" # Entry point for CLI tool

C2Client/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ openai==1.102.0
1111
pytest==8.4.1
1212
pytest-qt==4.5.0
1313
donut-shellcode
14+
markdown

0 commit comments

Comments
 (0)