Skip to content

Commit b1cc93f

Browse files
authored
Merge pull request #12 from maxDcb/develop
Develop
2 parents ec82b61 + 2969751 commit b1cc93f

File tree

19 files changed

+439
-100
lines changed

19 files changed

+439
-100
lines changed

client/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ foreach(ClientFile ${ClientFiles})
77
copy ${ClientFile} ${CMAKE_SOURCE_DIR}/Release/Client/)
88
endforeach()
99

10-
file(COPY Batcave DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
1110
file(COPY images DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
11+
file(COPY Batcave DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
12+
13+
file(COPY Credentials DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
1214

1315
file(COPY PowershellWebDelivery DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
1416
file(COPY PeDropper DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)

client/ConsolePanel.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
from TerminalPanel import *
1313

14+
sys.path.insert(1, './Credentials')
15+
import credentials
1416

1517
#
1618
# Constant
@@ -49,6 +51,7 @@
4951
WmiInstruction = "wmiExec"
5052
SpawnAsInstruction = "spawnAs"
5153
EvasionInstruction = "evasion"
54+
KeyLoggerInstruction = "keyLogger"
5255

5356
StartInstruction = "start"
5457
StopInstruction = "stop"
@@ -194,6 +197,7 @@
194197
(EvasionInstruction, []),
195198
(SpawnAsInstruction, []),
196199
(WmiInstruction, []),
200+
(KeyLoggerInstruction, []),
197201
]),
198202
(KerberosUseTicketInstruction,[]),
199203
(PowershellInstruction,[
@@ -232,6 +236,11 @@
232236
('CheckHooks', []),
233237
('Unhook', []),
234238
]),
239+
(KeyLoggerInstruction,[
240+
('start', []),
241+
('stop', []),
242+
('dump', []),
243+
]),
235244
(LoadModuleInstruction,[
236245
('AssemblyExec', []),
237246
('ChangeDirectory', []),
@@ -256,6 +265,7 @@
256265
('Tree', []),
257266
('Evasion', []),
258267
('WmiExec', []),
268+
('KeyLogger', []),
259269
]),
260270
]
261271

@@ -434,6 +444,9 @@ def displayResponse(self):
434444
responses = self.grpcClient.getResponseFromSession(session)
435445
for response in responses:
436446
self.setCursorEditorAtEnd()
447+
# check the response for mimikatz and not the cmd line ???
448+
if "-e mimikatz.exe" in response.cmd:
449+
credentials.handleMimikatzCredentials(response.response.decode(encoding="latin1", errors="ignore"), self.grpcClient, TeamServerApi_pb2)
437450
self.printInTerminal("", response.instruction + " " + response.cmd, response.response.decode(encoding="latin1", errors="ignore"))
438451
self.setCursorEditorAtEnd()
439452

@@ -548,4 +561,4 @@ def addItems(parent, elements, t=""):
548561
addItems(item, children, data)
549562
model = QStandardItemModel(self)
550563
addItems(model, data)
551-
self.setModel(model)
564+
self.setModel(model)

client/Credentials/credentials.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import json
2+
from grpcClient import GrpcClient
3+
import re
4+
5+
GetCredentialsInstruction = "getCred"
6+
AddCredentialsInstruction = "addCred"
7+
8+
9+
def getCredentials(grpcClient: GrpcClient, TeamServerApi_pb2):
10+
commandTeamServer = GetCredentialsInstruction
11+
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=b"")
12+
resultTermCommand = grpcClient.sendTermCmd(termCommand)
13+
result = resultTermCommand.result
14+
return result
15+
16+
17+
def addCredentials(grpcClient: GrpcClient,TeamServerApi_pb2, cred: str):
18+
currentcredentials = json.loads(getCredentials(grpcClient, TeamServerApi_pb2))
19+
credjson = json.loads(cred)
20+
21+
if credjson in currentcredentials:
22+
return
23+
24+
commandTeamServer = AddCredentialsInstruction
25+
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=cred.encode())
26+
resultTermCommand = grpcClient.sendTermCmd(termCommand)
27+
result = resultTermCommand.result
28+
return result
29+
30+
31+
def handleSekurlsaLogonPasswords(mimikatzOutput: str, grpcClient: GrpcClient,TeamServerApi_pb2):
32+
auth_block_pattern = r"Authentication Id : .*?\n(.*?)(?=\nAuthentication Id :|\Z)"
33+
user_domain_pattern = r"User Name\s*:\s*(.*?)\s*Domain\s*:\s*(.*?)\n"
34+
ntlm_pattern = r"\*\s*NTLM\s*:\s*([a-fA-F0-9]{32})"
35+
password_pattern = r"\*\s*Password\s*:\s*(.+)"
36+
37+
auth_blocks = re.findall(auth_block_pattern, mimikatzOutput, re.DOTALL)
38+
for block in auth_blocks:
39+
user_domain_match = re.search(user_domain_pattern, block)
40+
if user_domain_match:
41+
username = user_domain_match.group(1).strip()
42+
domain = user_domain_match.group(2).strip()
43+
else:
44+
username = "N/A"
45+
domain = "N/A"
46+
47+
matchs = re.findall(ntlm_pattern, block)
48+
matchs = list(dict.fromkeys(matchs))
49+
for ntlm in matchs:
50+
ntlm = ntlm.strip()
51+
if ntlm:
52+
cred = {}
53+
cred["username"] = username
54+
cred["domain"] = domain
55+
cred["ntlm"] = ntlm
56+
addCredentials(grpcClient, TeamServerApi_pb2, json.dumps(cred))
57+
58+
matchs = re.findall(password_pattern, block)
59+
matchs = list(dict.fromkeys(matchs))
60+
for password in matchs:
61+
password = password.strip()
62+
if password and password != "(null)":
63+
cred = {}
64+
cred["username"] = username
65+
cred["domain"] = domain
66+
cred["password"] = password
67+
addCredentials(grpcClient, TeamServerApi_pb2, json.dumps(cred))
68+
69+
70+
def handleLsaDumpSAM(mimikatzOutput: str, grpcClient: GrpcClient,TeamServerApi_pb2):
71+
domain_block_pattern = r"(Domain :.*?)(?=\nDomain :|\Z)"
72+
domain_pattern = r"Domain : (.*)"
73+
rid_block_pattern = r"(RID\s*:.*?)(?=\nRID\s*:|\Z)"
74+
user_hash_pattern = r"User\s*:\s*(\S+)\r?\n\s+Hash NTLM:\s*([a-fA-F0-9]+)"
75+
76+
domain_blocks = re.findall(domain_block_pattern, mimikatzOutput, re.DOTALL)
77+
for block in domain_blocks:
78+
domain_match = re.search(domain_pattern, block)
79+
if domain_match:
80+
domain = domain_match.group(1).strip()
81+
else:
82+
continue
83+
84+
rid_blocks = re.findall(rid_block_pattern, block, re.DOTALL)
85+
for rid_block in rid_blocks:
86+
matches = re.findall(user_hash_pattern, rid_block)
87+
for user, hash_ntlm in matches:
88+
cred = {}
89+
cred["username"] = user
90+
cred["domain"] = domain
91+
cred["ntlm"] = hash_ntlm
92+
addCredentials(grpcClient, TeamServerApi_pb2, json.dumps(cred))
93+
94+
95+
def handleMimikatzCredentials(mimikatzOutput: str, grpcClient: GrpcClient,TeamServerApi_pb2):
96+
# check if "sekurlsa::logonpasswords"
97+
handleSekurlsaLogonPasswords(mimikatzOutput, grpcClient,TeamServerApi_pb2)
98+
# check if "lsadump::sam"
99+
handleLsaDumpSAM(mimikatzOutput, grpcClient, TeamServerApi_pb2)
100+
# check if "sekurlsa::ekeys"
101+
# extract Password / aies256_hmac / rc4_md4

client/GUI.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,4 @@ def payloadForm(self):
106106
app.setStyleSheet(qdarktheme.load_stylesheet())
107107

108108
ex = App(args.ip, args.port, args.dev)
109-
sys.exit(app.exec_())
109+
sys.exit(app.exec_())

client/GraphPanel.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
PrimaryListenerImage = "images/firewall.svg"
2020
WindowsSessionImage = "images/pc.svg"
21+
WindowsHighPrivSessionImage = "images/windowshighpriv.svg"
2122
LinuxSessionImage = "images/linux.svg"
23+
LinuxRootSessionImage = "images/linuxhighpriv.svg"
2224

2325

2426
#
@@ -36,10 +38,10 @@ class NodeItem(QGraphicsPixmapItem):
3638
# Signal to notify position changes
3739
signaller = Signaller()
3840

39-
def __init__(self, type, hash, os="", privilege="", parent=None):
41+
def __init__(self, type, hash, os="", privilege="", hostname="", parent=None):
4042
if type == ListenerNodeItemType:
4143
self.type = ListenerNodeItemType
42-
pixmap = QPixmap(PrimaryListenerImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
44+
pixmap = self.addImageNode(PrimaryListenerImage, "")
4345
self.beaconHash = ""
4446
self.connectedListenerHash = ""
4547
self.listenerHash = []
@@ -48,12 +50,19 @@ def __init__(self, type, hash, os="", privilege="", parent=None):
4850
self.type = BeaconNodeItemType
4951
# print("NodeItem beaconHash", hash, "os", os, "privilege", privilege)
5052
if "linux" in os.lower():
51-
pixmap = QPixmap(LinuxSessionImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
53+
if privilege == "root":
54+
pixmap = self.addImageNode(LinuxRootSessionImage, hostname)
55+
else:
56+
pixmap = self.addImageNode(LinuxSessionImage, hostname)
5257
elif "windows" in os.lower():
53-
pixmap = QPixmap(WindowsSessionImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
58+
if privilege == "HIGH":
59+
pixmap = self.addImageNode(WindowsHighPrivSessionImage, hostname)
60+
else:
61+
pixmap = self.addImageNode(WindowsSessionImage, hostname)
5462
else:
5563
pixmap = QPixmap(LinuxSessionImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
5664
self.beaconHash=hash
65+
self.hostname = hostname
5766
self.connectedListenerHash = ""
5867
self.listenerHash=[]
5968

@@ -79,6 +88,38 @@ def mousePressEvent(self, event):
7988
def mouseReleaseEvent(self, event):
8089
super().mouseReleaseEvent(event)
8190
self.setCursor(Qt.ArrowCursor)
91+
92+
def addImageNode(self, image_path, legend_text, font_size=9, padding=5, text_color=Qt.white):
93+
# Load and scale the image
94+
pixmap = QPixmap(image_path).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
95+
96+
# Create a new QPixmap larger than the original for the image and text
97+
legend_height = font_size + padding * 2
98+
legend_width = len(legend_text) * font_size + padding * 2
99+
combined_pixmap = QPixmap(max(legend_width, pixmap.width()), pixmap.height() + legend_height)
100+
combined_pixmap.fill(Qt.transparent) # Transparent background
101+
102+
# Paint the image and the legend onto the combined pixmap
103+
painter = QPainter(combined_pixmap)
104+
image_x = (combined_pixmap.width() - pixmap.width()) // 2
105+
painter.drawPixmap(image_x, 0, pixmap) # Draw the image
106+
107+
pen = QPen()
108+
pen.setColor(text_color) # Set the desired text color
109+
painter.setPen(pen)
110+
# Set font for the legend
111+
font = QFont()
112+
font.setPointSize(font_size)
113+
painter.setFont(font)
114+
115+
# Draw the legend text centered below the image
116+
text_rect = painter.boundingRect(
117+
0, pixmap.height(), combined_pixmap.width(), legend_height, Qt.AlignCenter, legend_text
118+
)
119+
painter.drawText(text_rect, Qt.AlignCenter, legend_text)
120+
121+
painter.end()
122+
return combined_pixmap
82123

83124

84125
class Connector(QGraphicsLineItem):
@@ -184,7 +225,7 @@ def updateGraph(self):
184225
if session.beaconHash == nodeItem.beaconHash:
185226
inStore=True
186227
if not inStore:
187-
item = NodeItem(BeaconNodeItemType, session.beaconHash, session.os, session.privilege)
228+
item = NodeItem(BeaconNodeItemType, session.beaconHash, session.os, session.privilege, session.hostname)
188229
item.connectedListenerHash = session.listenerHash
189230
item.signaller.signal.connect(self.updateConnectors)
190231
self.scene.addItem(item)

0 commit comments

Comments
 (0)