Skip to content

Commit a0cab06

Browse files
authored
Merge pull request #30 from maxDcb/codex/assess-and-improve-c2client-code-quality
Refactor C2 client imports and add tests
2 parents b0a8a7d + 6c966e4 commit a0cab06

19 files changed

+558
-241
lines changed

C2Client/C2Client/AssistantPanel.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,19 @@
55
from datetime import datetime
66

77
from threading import Thread, Lock, Semaphore
8-
from PyQt5.QtWidgets import *
9-
from PyQt5.QtGui import *
10-
from PyQt5.QtCore import *
118

12-
from grpcClient import *
9+
from PyQt5.QtCore import Qt, QEvent, QTimer, pyqtSignal
10+
from PyQt5.QtGui import QFont, QTextCursor, QStandardItem, QStandardItemModel
11+
from PyQt5.QtWidgets import (
12+
QCompleter,
13+
QLineEdit,
14+
QPlainTextEdit,
15+
QShortcut,
16+
QVBoxLayout,
17+
QWidget,
18+
)
19+
20+
from .grpcClient import TeamServerApi_pb2
1321

1422
import openai
1523
from openai import OpenAI

C2Client/C2Client/ConsolePanel.py

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,27 @@
44
import re, html
55
from datetime import datetime
66
from threading import Thread, Lock
7-
from PyQt5.QtWidgets import *
8-
from PyQt5.QtGui import *
9-
from PyQt5.QtCore import *
107

11-
from grpcClient import *
12-
13-
from TerminalPanel import *
14-
from ScriptPanel import *
15-
from AssistantPanel import *
16-
17-
sys.path.insert(1, './Credentials')
18-
import credentials
8+
from PyQt5.QtCore import QObject, Qt, QEvent, QThread, QTimer, pyqtSignal, pyqtSlot
9+
from PyQt5.QtGui import QFont, QStandardItem, QStandardItemModel, QTextCursor
10+
from PyQt5.QtWidgets import (
11+
QWidget,
12+
QTabWidget,
13+
QVBoxLayout,
14+
QHBoxLayout,
15+
QTextEdit,
16+
QLineEdit,
17+
QShortcut,
18+
QCompleter,
19+
QTableWidget,
20+
QTableWidgetItem,
21+
)
22+
23+
from .grpcClient import GrpcClient, TeamServerApi_pb2
24+
from .TerminalPanel import Terminal
25+
from .ScriptPanel import Script
26+
from .AssistantPanel import Assistant
27+
from .TerminalModules.Credentials import credentials
1928

2029

2130
#
@@ -570,15 +579,13 @@ def runCommand(self):
570579
self.printInTerminal("", "", "")
571580

572581
else:
573-
cmdHistoryFile = open(CmdHistoryFileName, 'a')
574-
cmdHistoryFile.write(commandLine)
575-
cmdHistoryFile.write('\n')
576-
cmdHistoryFile.close()
582+
with open(CmdHistoryFileName, 'a') as cmdHistoryFile:
583+
cmdHistoryFile.write(commandLine)
584+
cmdHistoryFile.write('\n')
577585

578-
logFile = open(logsDir+"/"+self.logFileName, 'a')
579-
logFile.write('[+] send: \"' + commandLine + '\"')
580-
logFile.write('\n')
581-
logFile.close()
586+
with open(os.path.join(logsDir, self.logFileName), 'a') as logFile:
587+
logFile.write('[+] send: \"' + commandLine + '\"')
588+
logFile.write('\n')
582589

583590
self.commandEditor.setCmdHistory()
584591
instructions = commandLine.split()
@@ -612,11 +619,10 @@ def displayResponse(self):
612619
self.printInTerminal("", response.instruction + " " + response.cmd, response.response.decode('utf-8', 'replace'))
613620
self.setCursorEditorAtEnd()
614621

615-
logFile = open(logsDir+"/"+self.logFileName, 'a')
616-
logFile.write('[+] result: \"' + response.instruction + " " + response.cmd + '\"')
617-
logFile.write('\n' + response.response.decode('utf-8', 'replace') + '\n')
618-
logFile.write('\n')
619-
logFile.close()
622+
with open(os.path.join(logsDir, self.logFileName), 'a') as logFile:
623+
logFile.write('[+] result: \"' + response.instruction + " " + response.cmd + '\"')
624+
logFile.write('\n' + response.response.decode('utf-8', 'replace') + '\n')
625+
logFile.write('\n')
620626

621627
def setCursorEditorAtEnd(self):
622628
cursor = self.editorOutput.textCursor()
@@ -625,32 +631,36 @@ def setCursorEditorAtEnd(self):
625631

626632

627633
class GetSessionResponse(QObject):
634+
"""Background worker querying session responses."""
635+
628636
checkin = pyqtSignal()
629637

630-
exit=False
638+
def __init__(self) -> None:
639+
super().__init__()
640+
self.exit = False
631641

632-
def run(self):
633-
while self.exit==False:
642+
def run(self) -> None:
643+
while not self.exit:
634644
self.checkin.emit()
635645
time.sleep(1)
636646

637-
def quit(self):
638-
self.exit=True
647+
def quit(self) -> None:
648+
self.exit = True
639649

640650

641651
class CommandEditor(QLineEdit):
642652
tabPressed = pyqtSignal()
643-
cmdHistory = []
644-
idx = 0
645653

646-
def __init__(self, parent=None):
654+
def __init__(self, parent: QWidget | None = None) -> None:
647655
super().__init__(parent)
648656

649-
if(os.path.isfile(CmdHistoryFileName)):
650-
cmdHistoryFile = open(CmdHistoryFileName)
651-
self.cmdHistory = cmdHistoryFile.readlines()
652-
self.idx=len(self.cmdHistory)-1
653-
cmdHistoryFile.close()
657+
self.cmdHistory: list[str] = []
658+
self.idx: int = 0
659+
660+
if os.path.isfile(CmdHistoryFileName):
661+
with open(CmdHistoryFileName) as cmdHistoryFile:
662+
self.cmdHistory = cmdHistoryFile.readlines()
663+
self.idx = len(self.cmdHistory) - 1
654664

655665
QShortcut(Qt.Key_Up, self, self.historyUp)
656666
QShortcut(Qt.Key_Down, self, self.historyDown)
@@ -686,11 +696,10 @@ def historyDown(self):
686696
cmd = self.cmdHistory[self.idx%len(self.cmdHistory)]
687697
self.setText(cmd.strip())
688698

689-
def setCmdHistory(self):
690-
cmdHistoryFile = open(CmdHistoryFileName)
691-
self.cmdHistory = cmdHistoryFile.readlines()
692-
self.idx=len(self.cmdHistory)-1
693-
cmdHistoryFile.close()
699+
def setCmdHistory(self) -> None:
700+
with open(CmdHistoryFileName) as cmdHistoryFile:
701+
self.cmdHistory = cmdHistoryFile.readlines()
702+
self.idx = len(self.cmdHistory) - 1
694703

695704
def clearLine(self):
696705
self.clear()

C2Client/C2Client/GUI.py

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
import sys
2-
import os
3-
import signal
41
import argparse
52
import logging
6-
7-
from PyQt5.QtWidgets import *
8-
from PyQt5.QtGui import *
9-
from PyQt5.QtCore import *
10-
11-
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
12-
13-
from grpcClient import *
14-
from ListenerPanel import *
15-
from SessionPanel import *
16-
from ConsolePanel import *
17-
from GraphPanel import *
3+
import signal
4+
import sys
5+
from typing import Optional
6+
7+
from PyQt5.QtWidgets import (
8+
QApplication,
9+
QGridLayout,
10+
QHBoxLayout,
11+
QMainWindow,
12+
QPushButton,
13+
QTabWidget,
14+
QWidget,
15+
)
16+
17+
from .grpcClient import GrpcClient
18+
from .ListenerPanel import Listeners
19+
from .SessionPanel import Sessions
20+
from .ConsolePanel import ConsolesTab
21+
from .GraphPanel import Graph
1822

1923
import qdarktheme
2024

@@ -24,8 +28,9 @@
2428

2529

2630
class App(QMainWindow):
31+
"""Main application window for the C2 client."""
2732

28-
def __init__(self, ip, port, devMode):
33+
def __init__(self, ip: str, port: int, devMode: bool) -> None:
2934
super().__init__()
3035

3136
self.ip = ip
@@ -37,7 +42,7 @@ def __init__(self, ip, port, devMode):
3742
except ValueError as e:
3843
raise e
3944

40-
self.createPayloadWindow = None
45+
self.createPayloadWindow: Optional[QWidget] = None
4146

4247
self.title = 'Exploration C2'
4348
self.left = 0
@@ -68,19 +73,17 @@ def __init__(self, ip, port, devMode):
6873
self.sessionsWidget.interactWithSession.connect(self.consoleWidget.addConsole)
6974

7075
self.consoleWidget.script.mainScriptMethod("start", "", "", "")
71-
72-
self.show()
73-
7476

75-
def topLayout(self):
77+
def topLayout(self) -> None:
78+
"""Initialise the upper part of the main window."""
7679

7780
self.topWidget = QTabWidget()
7881

7982
self.m_main = QWidget()
8083

8184
self.m_main.layout = QHBoxLayout(self.m_main)
8285
self.m_main.layout.setContentsMargins(0, 0, 0, 0)
83-
86+
8487
self.sessionsWidget = Sessions(self, self.grpcClient)
8588
self.listenersWidget = Listeners(self, self.grpcClient)
8689

@@ -96,24 +99,33 @@ def topLayout(self):
9699
self.mainLayout.addWidget(self.topWidget, 1, 1, 1, 1)
97100

98101

99-
def botLayout(self):
102+
def botLayout(self) -> None:
103+
"""Initialise the bottom console area."""
100104

101105
self.consoleWidget = ConsolesTab(self, self.grpcClient)
102106
self.mainLayout.addWidget(self.consoleWidget, 2, 0, 1, 2)
103107

104108

105-
def __del__(self):
109+
def __del__(self) -> None:
110+
"""Ensure scripts are stopped when the window is destroyed."""
106111
if hasattr(self, 'consoleWidget'):
107112
self.consoleWidget.script.mainScriptMethod("stop", "", "", "")
108113

109114

110-
def payloadForm(self):
115+
def payloadForm(self) -> None:
116+
"""Display the payload creation window."""
111117
if self.createPayloadWindow is None:
118+
try:
119+
from .ScriptPanel import CreatePayload # type: ignore
120+
except Exception:
121+
CreatePayload = QWidget # fallback to simple widget
112122
self.createPayloadWindow = CreatePayload()
113123
self.createPayloadWindow.show()
114124

115125

116-
def main():
126+
def main() -> None:
127+
"""Entry point used by the project script."""
128+
117129
parser = argparse.ArgumentParser(description='TeamServer IP and port.')
118130
parser.add_argument('--ip', default='127.0.0.1', help='IP address (default: 127.0.0.1)')
119131
parser.add_argument('--port', type=int, default=50051, help='Port number (default: 50051)')
@@ -125,11 +137,13 @@ def main():
125137
app.setStyleSheet(qdarktheme.load_stylesheet())
126138

127139
try:
128-
ex = App(args.ip, args.port, args.dev)
129-
except ValueError as e:
140+
window = App(args.ip, args.port, args.dev)
141+
window.show()
142+
sys.exit(app.exec_())
143+
except ValueError:
130144
sys.exit(1)
131145
sys.exit(app.exec_())
132146

133147

134148
if __name__ == "__main__":
135-
main()
149+
main()

C2Client/C2Client/GraphPanel.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
import os
33
import time
44
from threading import Thread, Lock
5-
from PyQt5.QtWidgets import *
6-
from PyQt5.QtGui import *
7-
from PyQt5.QtCore import *
8-
from PyQt5.QtGui import QPixmap, QTransform
95

10-
from grpcClient import *
6+
from PyQt5.QtCore import QObject, Qt, QThread, QLineF, pyqtSignal
7+
from PyQt5.QtGui import QColor, QFont, QPainter, QPen, QPixmap
8+
from PyQt5.QtWidgets import (
9+
QGraphicsLineItem,
10+
QGraphicsPixmapItem,
11+
QGraphicsScene,
12+
QGraphicsView,
13+
QVBoxLayout,
14+
QWidget,
15+
QGraphicsItem,
16+
)
1117

1218

1319
#

C2Client/C2Client/ListenerPanel.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import time
22
import logging
33

4-
from PyQt5.QtWidgets import *
5-
from PyQt5.QtGui import *
6-
from PyQt5.QtCore import *
7-
8-
from grpcClient import *
4+
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject
5+
from PyQt5.QtWidgets import (
6+
QComboBox,
7+
QFormLayout,
8+
QGridLayout,
9+
QLabel,
10+
QLineEdit,
11+
QMenu,
12+
QPushButton,
13+
QTableView,
14+
QTableWidget,
15+
QTableWidgetItem,
16+
QWidget,
17+
QHeaderView,
18+
)
19+
20+
from .grpcClient import TeamServerApi_pb2
921

1022

1123
#

0 commit comments

Comments
 (0)