Skip to content

Commit

Permalink
Merge pull request #12 from maxDcb/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
maxDcb authored Jan 7, 2025
2 parents ec82b61 + 2969751 commit b1cc93f
Show file tree
Hide file tree
Showing 19 changed files with 439 additions and 100 deletions.
4 changes: 3 additions & 1 deletion client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ foreach(ClientFile ${ClientFiles})
copy ${ClientFile} ${CMAKE_SOURCE_DIR}/Release/Client/)
endforeach()

file(COPY Batcave DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
file(COPY images DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
file(COPY Batcave DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)

file(COPY Credentials DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)

file(COPY PowershellWebDelivery DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
file(COPY PeDropper DESTINATION ${CMAKE_SOURCE_DIR}/Release/Client/)
Expand Down
15 changes: 14 additions & 1 deletion client/ConsolePanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

from TerminalPanel import *

sys.path.insert(1, './Credentials')
import credentials

#
# Constant
Expand Down Expand Up @@ -49,6 +51,7 @@
WmiInstruction = "wmiExec"
SpawnAsInstruction = "spawnAs"
EvasionInstruction = "evasion"
KeyLoggerInstruction = "keyLogger"

StartInstruction = "start"
StopInstruction = "stop"
Expand Down Expand Up @@ -194,6 +197,7 @@
(EvasionInstruction, []),
(SpawnAsInstruction, []),
(WmiInstruction, []),
(KeyLoggerInstruction, []),
]),
(KerberosUseTicketInstruction,[]),
(PowershellInstruction,[
Expand Down Expand Up @@ -232,6 +236,11 @@
('CheckHooks', []),
('Unhook', []),
]),
(KeyLoggerInstruction,[
('start', []),
('stop', []),
('dump', []),
]),
(LoadModuleInstruction,[
('AssemblyExec', []),
('ChangeDirectory', []),
Expand All @@ -256,6 +265,7 @@
('Tree', []),
('Evasion', []),
('WmiExec', []),
('KeyLogger', []),
]),
]

Expand Down Expand Up @@ -434,6 +444,9 @@ def displayResponse(self):
responses = self.grpcClient.getResponseFromSession(session)
for response in responses:
self.setCursorEditorAtEnd()
# check the response for mimikatz and not the cmd line ???
if "-e mimikatz.exe" in response.cmd:
credentials.handleMimikatzCredentials(response.response.decode(encoding="latin1", errors="ignore"), self.grpcClient, TeamServerApi_pb2)
self.printInTerminal("", response.instruction + " " + response.cmd, response.response.decode(encoding="latin1", errors="ignore"))
self.setCursorEditorAtEnd()

Expand Down Expand Up @@ -548,4 +561,4 @@ def addItems(parent, elements, t=""):
addItems(item, children, data)
model = QStandardItemModel(self)
addItems(model, data)
self.setModel(model)
self.setModel(model)
101 changes: 101 additions & 0 deletions client/Credentials/credentials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import json
from grpcClient import GrpcClient
import re

GetCredentialsInstruction = "getCred"
AddCredentialsInstruction = "addCred"


def getCredentials(grpcClient: GrpcClient, TeamServerApi_pb2):
commandTeamServer = GetCredentialsInstruction
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=b"")
resultTermCommand = grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
return result


def addCredentials(grpcClient: GrpcClient,TeamServerApi_pb2, cred: str):
currentcredentials = json.loads(getCredentials(grpcClient, TeamServerApi_pb2))
credjson = json.loads(cred)

if credjson in currentcredentials:
return

commandTeamServer = AddCredentialsInstruction
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=cred.encode())
resultTermCommand = grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
return result


def handleSekurlsaLogonPasswords(mimikatzOutput: str, grpcClient: GrpcClient,TeamServerApi_pb2):
auth_block_pattern = r"Authentication Id : .*?\n(.*?)(?=\nAuthentication Id :|\Z)"
user_domain_pattern = r"User Name\s*:\s*(.*?)\s*Domain\s*:\s*(.*?)\n"
ntlm_pattern = r"\*\s*NTLM\s*:\s*([a-fA-F0-9]{32})"
password_pattern = r"\*\s*Password\s*:\s*(.+)"

auth_blocks = re.findall(auth_block_pattern, mimikatzOutput, re.DOTALL)
for block in auth_blocks:
user_domain_match = re.search(user_domain_pattern, block)
if user_domain_match:
username = user_domain_match.group(1).strip()
domain = user_domain_match.group(2).strip()
else:
username = "N/A"
domain = "N/A"

matchs = re.findall(ntlm_pattern, block)
matchs = list(dict.fromkeys(matchs))
for ntlm in matchs:
ntlm = ntlm.strip()
if ntlm:
cred = {}
cred["username"] = username
cred["domain"] = domain
cred["ntlm"] = ntlm
addCredentials(grpcClient, TeamServerApi_pb2, json.dumps(cred))

matchs = re.findall(password_pattern, block)
matchs = list(dict.fromkeys(matchs))
for password in matchs:
password = password.strip()
if password and password != "(null)":
cred = {}
cred["username"] = username
cred["domain"] = domain
cred["password"] = password
addCredentials(grpcClient, TeamServerApi_pb2, json.dumps(cred))


def handleLsaDumpSAM(mimikatzOutput: str, grpcClient: GrpcClient,TeamServerApi_pb2):
domain_block_pattern = r"(Domain :.*?)(?=\nDomain :|\Z)"
domain_pattern = r"Domain : (.*)"
rid_block_pattern = r"(RID\s*:.*?)(?=\nRID\s*:|\Z)"
user_hash_pattern = r"User\s*:\s*(\S+)\r?\n\s+Hash NTLM:\s*([a-fA-F0-9]+)"

domain_blocks = re.findall(domain_block_pattern, mimikatzOutput, re.DOTALL)
for block in domain_blocks:
domain_match = re.search(domain_pattern, block)
if domain_match:
domain = domain_match.group(1).strip()
else:
continue

rid_blocks = re.findall(rid_block_pattern, block, re.DOTALL)
for rid_block in rid_blocks:
matches = re.findall(user_hash_pattern, rid_block)
for user, hash_ntlm in matches:
cred = {}
cred["username"] = user
cred["domain"] = domain
cred["ntlm"] = hash_ntlm
addCredentials(grpcClient, TeamServerApi_pb2, json.dumps(cred))


def handleMimikatzCredentials(mimikatzOutput: str, grpcClient: GrpcClient,TeamServerApi_pb2):
# check if "sekurlsa::logonpasswords"
handleSekurlsaLogonPasswords(mimikatzOutput, grpcClient,TeamServerApi_pb2)
# check if "lsadump::sam"
handleLsaDumpSAM(mimikatzOutput, grpcClient, TeamServerApi_pb2)
# check if "sekurlsa::ekeys"
# extract Password / aies256_hmac / rc4_md4
2 changes: 1 addition & 1 deletion client/GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@ def payloadForm(self):
app.setStyleSheet(qdarktheme.load_stylesheet())

ex = App(args.ip, args.port, args.dev)
sys.exit(app.exec_())
sys.exit(app.exec_())
51 changes: 46 additions & 5 deletions client/GraphPanel.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

PrimaryListenerImage = "images/firewall.svg"
WindowsSessionImage = "images/pc.svg"
WindowsHighPrivSessionImage = "images/windowshighpriv.svg"
LinuxSessionImage = "images/linux.svg"
LinuxRootSessionImage = "images/linuxhighpriv.svg"


#
Expand All @@ -36,10 +38,10 @@ class NodeItem(QGraphicsPixmapItem):
# Signal to notify position changes
signaller = Signaller()

def __init__(self, type, hash, os="", privilege="", parent=None):
def __init__(self, type, hash, os="", privilege="", hostname="", parent=None):
if type == ListenerNodeItemType:
self.type = ListenerNodeItemType
pixmap = QPixmap(PrimaryListenerImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
pixmap = self.addImageNode(PrimaryListenerImage, "")
self.beaconHash = ""
self.connectedListenerHash = ""
self.listenerHash = []
Expand All @@ -48,12 +50,19 @@ def __init__(self, type, hash, os="", privilege="", parent=None):
self.type = BeaconNodeItemType
# print("NodeItem beaconHash", hash, "os", os, "privilege", privilege)
if "linux" in os.lower():
pixmap = QPixmap(LinuxSessionImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
if privilege == "root":
pixmap = self.addImageNode(LinuxRootSessionImage, hostname)
else:
pixmap = self.addImageNode(LinuxSessionImage, hostname)
elif "windows" in os.lower():
pixmap = QPixmap(WindowsSessionImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
if privilege == "HIGH":
pixmap = self.addImageNode(WindowsHighPrivSessionImage, hostname)
else:
pixmap = self.addImageNode(WindowsSessionImage, hostname)
else:
pixmap = QPixmap(LinuxSessionImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.beaconHash=hash
self.hostname = hostname
self.connectedListenerHash = ""
self.listenerHash=[]

Expand All @@ -79,6 +88,38 @@ def mousePressEvent(self, event):
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
self.setCursor(Qt.ArrowCursor)

def addImageNode(self, image_path, legend_text, font_size=9, padding=5, text_color=Qt.white):
# Load and scale the image
pixmap = QPixmap(image_path).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation)

# Create a new QPixmap larger than the original for the image and text
legend_height = font_size + padding * 2
legend_width = len(legend_text) * font_size + padding * 2
combined_pixmap = QPixmap(max(legend_width, pixmap.width()), pixmap.height() + legend_height)
combined_pixmap.fill(Qt.transparent) # Transparent background

# Paint the image and the legend onto the combined pixmap
painter = QPainter(combined_pixmap)
image_x = (combined_pixmap.width() - pixmap.width()) // 2
painter.drawPixmap(image_x, 0, pixmap) # Draw the image

pen = QPen()
pen.setColor(text_color) # Set the desired text color
painter.setPen(pen)
# Set font for the legend
font = QFont()
font.setPointSize(font_size)
painter.setFont(font)

# Draw the legend text centered below the image
text_rect = painter.boundingRect(
0, pixmap.height(), combined_pixmap.width(), legend_height, Qt.AlignCenter, legend_text
)
painter.drawText(text_rect, Qt.AlignCenter, legend_text)

painter.end()
return combined_pixmap


class Connector(QGraphicsLineItem):
Expand Down Expand Up @@ -184,7 +225,7 @@ def updateGraph(self):
if session.beaconHash == nodeItem.beaconHash:
inStore=True
if not inStore:
item = NodeItem(BeaconNodeItemType, session.beaconHash, session.os, session.privilege)
item = NodeItem(BeaconNodeItemType, session.beaconHash, session.os, session.privilege, session.hostname)
item.connectedListenerHash = session.listenerHash
item.signaller.signal.connect(self.updateConnectors)
self.scene.addItem(item)
Expand Down
Loading

0 comments on commit b1cc93f

Please sign in to comment.