Files
maxDcb-C2TeamServer/client/TerminalPanel.py
T
2025-02-13 10:22:46 -05:00

768 lines
25 KiB
Python

import sys
import os
import time
import json
import random
import string
from datetime import datetime
from threading import Thread, Lock, Semaphore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from grpcClient import *
from git import Repo
#
# Dropper modules
#
dropperModulesDir = "./DropperModules"
if not os.path.exists(dropperModulesDir):
os.makedirs(dropperModulesDir)
with open("DropperModules.conf", "r") as file:
repositories = file.readlines()
DropperModules = []
for repo in repositories:
repo = repo.strip()
repoName = repo.split('/')[-1].replace('.git', '')
repoPath = os.path.join(dropperModulesDir, repoName)
if not os.path.exists(repoPath):
print(f"Cloning {repoName} in {repoPath}.")
try:
Repo.clone_from(repo, repoPath)
except Exception as e:
print(f"Failed to clone {repoName}: {e}")
else:
print(f"Repository {repoName} already exists in {dropperModulesDir}.")
for moduleName in os.listdir(dropperModulesDir):
modulePath = os.path.join(dropperModulesDir, moduleName)
if os.path.isdir(modulePath):
if os.path.exists(modulePath):
sys.path.insert(1, modulePath)
try:
# Dynamically import the module
importedModule = __import__(moduleName)
DropperModules.append(importedModule)
print(f"Successfully imported {moduleName}")
except ImportError as e:
print(f"Failed to import {moduleName}: {e}")
#
# Terminal modules
#
sys.path.insert(1, './TerminalModules/Batcave')
import batcave
sys.path.insert(1, './TerminalModules/Credentials')
import credentials
#
# Constant
#
LogFileName = "Terminal.log"
HistoryFileName = ".termHistory"
HttpType = "http"
HttpsType = "https"
GrpcGetBeaconBinaryInstruction = "getBeaconBinary"
GrpcPutIntoUploadDirInstruction = "putIntoUploadDir"
GrpcInfoListenerInstruction = "infoListener"
GrpcBatcaveUploadToolInstruction = "batcaveUpload"
GrpcSocksInstruction = "socks"
BeaconFileWindows = "Beacon.exe"
BeaconFileLinux = "Beacon"
ErrorInstruction = "Error"
HelpInstruction = "help"
SocksInstruction = "Socks"
SocksHelp = """Socks:
Socks start
Socks bind beaconHash
Socks unbind
Socks stop"""
BatcaveInstruction = "Batcave"
BatcaveHelp = """Batcave:
Install the given module locally or on the team server:
exemple:
- Batcave Install rubeus
- Batcave BundleInstall recon
- Batcave Search rec"""
DropperInstruction = "Dropper"
DropperModuleGetHelpFunction = "getHelpExploration"
DropperModuleGeneratePayloadFunction = "generatePayloadsExploration"
HostInstruction = "Host"
HostHelp="""Host:
Host upload a file on the teamserver to be downloaded by a web request from a web listener (http/https):
exemple:
- Host file hostListenerHash"""
CredentialStoreInstruction = "CredentialStore"
CredentialStoreHelp = """CredentialStore:
Handle the credential store:
exemple:
- CredentialStore get
- CredentialStore set domain username credential
- CredentialStore search something"""
GetSubInstruction = "get"
SetSubInstruction = "set"
SearchSubInstruction = "search"
def getHelpMsg():
helpText = HelpInstruction+"\n"
helpText += HostInstruction+"\n"
helpText += DropperInstruction+"\n"
helpText += BatcaveInstruction+"\n"
helpText += CredentialStoreInstruction+"\n"
helpText += SocksInstruction
return helpText
completerData = [
(HelpInstruction,[]),
(HostInstruction,[]),
(DropperInstruction,[]),
(BatcaveInstruction, [
("Install", []),
("BundleInstall", []),
("Search", [])
]),
(CredentialStoreInstruction, [
(GetSubInstruction, []),
(SetSubInstruction, []),
(SearchSubInstruction, [])
])
]
InfoProcessing = "Processing..."
ErrorCmdUnknow = "Error: Command Unknown"
ErrorFileNotFound = "Error: File doesn't exist."
ErrorListener = "Error: Download listener must be of type http or https."
#
# Terminal tab implementation
#
class Terminal(QWidget):
tabPressed = pyqtSignal()
logFileName=""
sem = Semaphore()
def __init__(self, parent, ip, port, devMode):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.grpcClient = GrpcClient(ip, port, devMode)
self.logFileName=LogFileName
self.editorOutput = QPlainTextEdit()
self.editorOutput.setFont(QFont("Courier"));
self.editorOutput.setReadOnly(True)
self.layout.addWidget(self.editorOutput, 8)
self.commandEditor = CommandEditor()
self.layout.addWidget(self.commandEditor, 2)
self.commandEditor.returnPressed.connect(self.runCommand)
def nextCompletion(self):
index = self._compl.currentIndex()
self._compl.popup().setCurrentIndex(index)
start = self._compl.currentRow()
if not self._compl.setCurrentRow(start + 1):
self._compl.setCurrentRow(0)
def event(self, event):
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
self.tabPressed.emit()
return True
return super().event(event)
def printInTerminal(self, cmd, result):
now = datetime.now()
formater = '<p style="white-space:pre">'+'<span style="color:blue;">['+now.strftime("%Y:%m:%d %H:%M:%S").rstrip()+']</span>'+'<span style="color:red;"> [+] </span>'+'<span style="color:red;">{}</span>'+'</p>'
self.sem.acquire()
if cmd:
self.editorOutput.appendHtml(formater.format(cmd))
self.editorOutput.insertPlainText("\n")
if result:
self.editorOutput.insertPlainText(result)
self.editorOutput.insertPlainText("\n")
self.sem.release()
def runCommand(self):
commandLine = self.commandEditor.displayText()
self.commandEditor.clearLine()
self.setCursorEditorAtEnd()
if commandLine == "":
self.printInTerminal("", "")
else:
cmdHistoryFile = open(HistoryFileName, 'a')
cmdHistoryFile.write(commandLine)
cmdHistoryFile.write('\n')
cmdHistoryFile.close()
logFile = open("./logs/"+self.logFileName, 'a')
logFile.write('[+] send: \"' + commandLine + '\"')
logFile.write('\n')
logFile.close()
self.commandEditor.setCmdHistory()
instructions = commandLine.split()
if len(instructions) < 1:
return;
if instructions[0]==HelpInstruction:
self.runHelp()
elif instructions[0]==BatcaveInstruction:
self.runBatcave(commandLine, instructions)
elif instructions[0]==HostInstruction:
self.runHost(commandLine, instructions)
elif instructions[0]==CredentialStoreInstruction:
self.runCredentialStore(commandLine, instructions)
elif instructions[0]==DropperInstruction:
self.runDropper(commandLine, instructions)
elif instructions[0]==SocksInstruction:
self.runSocks(commandLine, instructions)
else:
self.printInTerminal(commandLine, ErrorCmdUnknow)
self.setCursorEditorAtEnd()
def runHelp(self):
self.printInTerminal(HelpInstruction, getHelpMsg())
def runSocks(self, commandLine, instructions):
if len(instructions) < 2:
self.printInTerminal(commandLine, SocksHelp)
return;
cmd = instructions[1]
if cmd == "start" or cmd == "stop" or cmd == "unbind":
commandTeamServer = GrpcSocksInstruction + " " + cmd
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer)
print("Sent " + commandTeamServer)
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
self.printInTerminal(commandLine, result)
return
elif cmd == "bind":
if len(instructions) < 3:
self.printInTerminal(commandLine, SocksHelp)
return;
beaconHash = instructions[2]
commandTeamServer = GrpcSocksInstruction + " " + cmd + " " + beaconHash
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer)
print("Sent " + commandTeamServer)
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
self.printInTerminal(commandLine, result)
else:
self.printInTerminal(commandLine, ErrorCmdUnknow)
return;
return
#
# Batcave
#
def runBatcave(self, commandLine, instructions):
if len(instructions) < 3:
self.printInTerminal(commandLine, BatcaveHelp)
return;
cmd = instructions[1]
batgadget = instructions[2]
if cmd == "Install":
filePath = batcave.downloadBatGadget(batgadget)
try:
filename = os.path.basename(filePath)
with open(filePath, mode='rb') as fileDesc:
payload = fileDesc.read()
except IOError:
self.printInTerminal(commandLine, ErrorFileNotFound)
return
commandTeamServer = GrpcBatcaveUploadToolInstruction + " " + filename
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=payload)
print("Sent " + commandTeamServer)
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
self.printInTerminal(commandLine, f"Added {filename} to TeamServer Tools.")
return
elif cmd == "BundleInstall":
filePathList = batcave.downloadBatBundle(batgadget)
line = ""
for filePath in filePathList:
try:
filename = os.path.basename(filePath)
with open(filePath, mode='rb') as fileDesc:
payload = fileDesc.read()
except IOError:
self.printInTerminal(commandLine, ErrorFileNotFound)
return
commandTeamServer = GrpcBatcaveUploadToolInstruction + " " + filename
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=payload)
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
line += f"Added {filename} to TeamServer Tools.\n"
line += f"BatBundle {batgadget} successfully installed!"
self.printInTerminal(commandLine, line)
elif cmd == "Search":
result = batcave.searchTheBatcave(batgadget)
self.printInTerminal(commandLine, result)
return
else:
self.printInTerminal(commandLine, ErrorCmdUnknow)
return
#
# CredentialStore
#
def runCredentialStore(self, commandLine, instructions):
if len(instructions) < 2:
self.printInTerminal(commandLine, CredentialStoreHelp)
return;
cmd = instructions[1]
if cmd == GetSubInstruction:
currentcredentials = json.loads(credentials.getCredentials(self.grpcClient, TeamServerApi_pb2))
toPrint = ""
for cred in currentcredentials:
toPrint+=json.dumps(cred)
toPrint+="\n"
self.printInTerminal(commandLine, toPrint)
return
elif cmd == SetSubInstruction:
if len(instructions) < 5:
self.printInTerminal(commandLine, CredentialStoreHelp)
return
domain = instructions[2]
username = instructions[3]
credential = instructions[4]
cred = {}
cred["domain"] = domain
cred["username"] = username
cred["manual"] = credential
credentials.addCredentials(self.grpcClient, TeamServerApi_pb2, json.dumps(cred))
return
elif cmd == SearchSubInstruction:
if len(instructions) < 3:
self.printInTerminal(commandLine, CredentialStoreHelp)
return
searchPatern = instructions[2]
currentcredentials = json.loads(credentials.getCredentials(self.grpcClient, TeamServerApi_pb2))
toPrint = ""
for cred in currentcredentials:
for key, value in cred.items():
if searchPatern in value:
toPrint+=json.dumps(cred)
toPrint+="\n"
self.printInTerminal(commandLine, toPrint)
return
else:
self.printInTerminal(commandLine, ErrorCmdUnknow)
return
#
# Host
#
def runHost(self, commandLine, instructions):
if len(instructions) < 3:
self.printInTerminal(commandLine, HostHelp)
return;
filePath = instructions[1]
hostListenerHash = instructions[2]
commandTeamServer = GrpcInfoListenerInstruction+" "+hostListenerHash
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer)
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
results = result.split("\n")
if len(results)<4:
return
schemeDownload = results[0]
ipDownload = results[1]
portDownload = results[2]
downloadPath = results[3]
if not downloadPath:
self.printInTerminal(commandLine, ErrorListener)
return
if downloadPath[0]=="/":
downloadPath = downloadPath[1:]
# Upload the file and get the path
try:
filename = os.path.basename(filePath)
with open(filePath, mode='rb') as fileDesc:
payload = fileDesc.read()
except IOError:
self.printInTerminal(commandLine, ErrorFileNotFound)
return
commandTeamServer = GrpcPutIntoUploadDirInstruction+" "+hostListenerHash+" "+filename
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=payload)
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
result = schemeDownload + "://" + ipDownload + ":" + portDownload + "/" + downloadPath + filename
self.printInTerminal(commandLine, result)
#
# runDropper
#
def runDropper(self, commandLine, instructions):
if len(instructions) < 2:
availableModules = "Available dropper:\n"
for module in DropperModules:
availableModules += module.__name__ + "\n"
self.printInTerminal(commandLine, availableModules)
return;
moduleName = instructions[1]
moduleFound = False
for module in DropperModules:
if moduleName == module.__name__:
moduleFound = True
if len(instructions) < 4:
helpText = ""
getHelp = getattr(module, DropperModuleGetHelpFunction)
helpText += getHelp()
self.printInTerminal(commandLine, helpText)
return;
listenerDownload = instructions[2]
listenerBeacon = instructions[3]
additionalArgss = " ".join(instructions[4:])
print("moduleName", moduleName)
print("listenerDownload", listenerDownload)
print("listenerBeacon", listenerBeacon)
print("additionalArgs", additionalArgss)
self.printInTerminal(commandLine, InfoProcessing)
thread = Thread(target = self.GenerateAndHostGeneric, args = (commandLine, moduleName, listenerDownload, listenerBeacon, additionalArgss))
thread.start()
if moduleFound == False:
self.printInTerminal(commandLine, ErrorCmdUnknow)
return;
#
# Generic dropper module
#
def GenerateAndHostGeneric(self, commandLine, moduleName, listenerDownload, listenerBeacon, additionalArgs):
commandTeamServer = GrpcInfoListenerInstruction+" "+listenerDownload
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer)
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
results = result.split("\n")
if len(results)<4:
return
schemeDownload = results[0]
ipDownload = results[1]
portDownload = results[2]
downloadPath = results[3]
if not downloadPath:
self.printInTerminal(commandLine, ErrorListener)
return
if downloadPath[0]=="/":
downloadPath = downloadPath[1:]
if listenerBeacon != listenerDownload:
commandTeamServer = GrpcInfoListenerInstruction+" "+listenerBeacon
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer)
self.sem.acquire()
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
self.sem.release()
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
results = result.split("\n")
if len(results)<4:
return
scheme = results[0]
ip = results[1]
port = results[2]
else:
scheme=schemeDownload
ip=ipDownload
port=portDownload
targetOs = "windows"
for module in DropperModules:
if module.__name__ == moduleName:
try:
getTargetOs = getattr(module, "getTargetOsExploration")
print(getTargetOs)
targetOs = getTargetOs()
print(targetOs)
except AttributeError:
targetOs = "windows"
commandTeamServer = GrpcGetBeaconBinaryInstruction+" "+listenerBeacon+" "+targetOs
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer)
self.sem.acquire()
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
self.sem.release()
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
if targetOs == "windows":
beaconFilePath = "./"+BeaconFileWindows
else:
beaconFilePath = "./"+BeaconFileLinux
beaconFile = open(beaconFilePath, "wb")
beaconFile.write(resultTermCommand.data)
beaconArg = ip+" "+port
if scheme==HttpType or scheme==HttpsType:
beaconArg = beaconArg+" "+scheme
urlDownload = schemeDownload + "://" + ipDownload + ":" + portDownload + "/" + downloadPath
# Generate the payload
droppersPath = []
shellcodesPath = []
cmdToRUn = ""
for module in DropperModules:
if module.__name__ == moduleName:
genPayload = getattr(module, DropperModuleGeneratePayloadFunction)
droppersPath, shellcodesPath, cmdToRUn = genPayload(beaconFilePath, beaconArg, "", urlDownload, additionalArgs.split(" "))
# Upload the file and get the path
for dropperPath in droppersPath:
try:
with open(dropperPath, mode='rb') as fileDesc:
payload = fileDesc.read()
except IOError:
self.printInTerminal(commandLine, ErrorFileNotFound)
return
filename = os.path.basename(dropperPath)
commandTeamServer = GrpcPutIntoUploadDirInstruction+" "+listenerDownload+" "+filename
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=payload)
self.sem.acquire()
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
self.sem.release()
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
for shellcodePath in shellcodesPath:
try:
with open(shellcodePath, mode='rb') as fileDesc:
payload = fileDesc.read()
except IOError:
self.printInTerminal(commandLine, ErrorFileNotFound)
return
filename = os.path.basename(shellcodePath)
commandTeamServer = GrpcPutIntoUploadDirInstruction+" "+listenerDownload+" "+filename
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=payload)
self.sem.acquire()
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
self.sem.release()
result = resultTermCommand.result
if ErrorInstruction in result:
self.printInTerminal(commandLine, result)
return
result = cmdToRUn
self.printInTerminal(commandLine, result)
# setCursorEditorAtEnd
def setCursorEditorAtEnd(self):
cursor = self.editorOutput.textCursor()
cursor.movePosition(QTextCursor.End,)
self.editorOutput.setTextCursor(cursor)
class CommandEditor(QLineEdit):
tabPressed = pyqtSignal()
cmdHistory = []
idx = 0
def __init__(self, parent=None):
super().__init__(parent)
if(os.path.isfile(HistoryFileName)):
cmdHistoryFile = open(HistoryFileName)
self.cmdHistory = cmdHistoryFile.readlines()
self.idx=len(self.cmdHistory)-1
cmdHistoryFile.close()
QShortcut(Qt.Key_Up, self, self.historyUp)
QShortcut(Qt.Key_Down, self, self.historyDown)
self.codeCompleter = CodeCompleter(completerData, self)
# needed to clear the completer after activation
self.codeCompleter.activated.connect(self.onActivated)
self.setCompleter(self.codeCompleter)
self.tabPressed.connect(self.nextCompletion)
def nextCompletion(self):
index = self.codeCompleter.currentIndex()
self.codeCompleter.popup().setCurrentIndex(index)
start = self.codeCompleter.currentRow()
if not self.codeCompleter.setCurrentRow(start + 1):
self.codeCompleter.setCurrentRow(0)
def event(self, event):
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
self.tabPressed.emit()
return True
return super().event(event)
def historyUp(self):
if(self.idx<len(self.cmdHistory) and self.idx>=0):
cmd = self.cmdHistory[self.idx%len(self.cmdHistory)]
self.idx=max(self.idx-1,0)
self.setText(cmd.strip())
def historyDown(self):
if(self.idx<len(self.cmdHistory) and self.idx>=0):
self.idx=min(self.idx+1,len(self.cmdHistory)-1)
cmd = self.cmdHistory[self.idx%len(self.cmdHistory)]
self.setText(cmd.strip())
def setCmdHistory(self):
cmdHistoryFile = open('.termHistory')
self.cmdHistory = cmdHistoryFile.readlines()
self.idx=len(self.cmdHistory)-1
cmdHistoryFile.close()
def clearLine(self):
self.clear()
def onActivated(self):
QTimer.singleShot(0, self.clear)
class CodeCompleter(QCompleter):
ConcatenationRole = Qt.UserRole + 1
def __init__(self, data, parent=None):
super().__init__(parent)
self.createModel(data)
def splitPath(self, path):
return path.split(' ')
def pathFromIndex(self, ix):
return ix.data(CodeCompleter.ConcatenationRole)
def createModel(self, data):
def addItems(parent, elements, t=""):
for text, children in elements:
item = QStandardItem(text)
data = t + " " + text if t else text
item.setData(data, CodeCompleter.ConcatenationRole)
parent.appendRow(item)
if children:
addItems(item, children, data)
model = QStandardItemModel(self)
addItems(model, data)
self.setModel(model)