Skip to content

Commit 3a4c4c1

Browse files
authored
Merge pull request #27 from maxDcb/develop
Fix bug & small add
2 parents 456dadf + d1e780d commit 3a4c4c1

File tree

5 files changed

+134
-34
lines changed

5 files changed

+134
-34
lines changed

C2Client/C2Client/AssistantPanel.py

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -135,33 +135,6 @@ def runCommand(self):
135135

136136
else:
137137

138-
function_spec_assemblyExec = {
139-
"name": "assemblyExec",
140-
"description": "Execute a red team tool on a specific beacon using assembly execution",
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-
"tool": {
153-
"type": "string",
154-
"description": "The tool to use (e.g., Rubeus)"
155-
},
156-
"arguments": {
157-
"type": "string",
158-
"description": "Command line arguments to pass to the tool (e.g., '/dump')"
159-
}
160-
},
161-
"required": ["beacon_hash", "listener_hash", "tool"]
162-
}
163-
}
164-
165138
function_spec_ls = {
166139
"name": "ls",
167140
"description": "List the contents of a specified directory on a specific beacon.",
@@ -250,7 +223,30 @@ def runCommand(self):
250223
},
251224
"required": ["beacon_hash", "listener_hash"]
252225
}
253-
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+
}
254250

255251
api_key = os.environ.get("OPENAI_API_KEY")
256252

@@ -275,7 +271,7 @@ def runCommand(self):
275271
model="gpt-4o",
276272
# model="gpt-3.5-turbo-1106", # test
277273
messages=self.messages,
278-
functions=[function_spec_ls, function_spec_cd, function_spec_cat, function_spec_pwd],
274+
functions=[function_spec_ls, function_spec_cd, function_spec_cat, function_spec_pwd, function_spec_tree],
279275
function_call="auto",
280276
temperature=0.05
281277
)
@@ -337,6 +333,16 @@ def executeCmd(self, cmd, args):
337333
if result.message:
338334
self.printInTerminal("", commandLine, result.message.decode(encoding="latin1", errors="ignore"))
339335

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+
340346
elif cmd == "cd":
341347
beacon_hash = args["beacon_hash"]
342348
listener_hash = args["listener_hash"]

C2Client/C2Client/SessionPanel.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ def showContextMenu(self, position):
115115

116116

117117
# catch Interact and Stop menu click
118-
# TODO add remove ?
119118
def actionClicked(self, action):
120119
hash = self.item
121120
for ix, sessionStore in enumerate(self.listSessionObject):

C2Client/C2Client/TerminalPanel.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
GrpcInfoListenerInstruction = "infoListener"
109109
GrpcBatcaveUploadToolInstruction = "batcaveUpload"
110110
GrpcSocksInstruction = "socks"
111+
GrpcReloadModulesInstruction = "reloadModules";
111112

112113
BeaconFileWindows = "Beacon.exe"
113114
BeaconFileLinux = "Beacon"
@@ -154,13 +155,20 @@
154155
SetSubInstruction = "set"
155156
SearchSubInstruction = "search"
156157

158+
ReloadModulesInstruction = "ReloadModules";
159+
ReloadModulesHelp = """ReloadModules:
160+
Command the TeamServer to reload the modules libraries located in TeamServerModulesDirectoryPath.
161+
Can be used to add a new functionality without restarting the TeamServer.
162+
"""
163+
157164

158165
def getHelpMsg():
159166
helpText = HostInstruction+"\n"
160167
helpText += DropperInstruction+"\n"
161168
helpText += BatcaveInstruction+"\n"
162169
helpText += CredentialStoreInstruction+"\n"
163-
helpText += SocksInstruction
170+
helpText += SocksInstruction+"\n"
171+
helpText += ReloadModulesInstruction
164172
return helpText
165173

166174
completerData = [
@@ -176,7 +184,8 @@ def getHelpMsg():
176184
(GetSubInstruction, []),
177185
(SetSubInstruction, []),
178186
(SearchSubInstruction, [])
179-
])
187+
]),
188+
(ReloadModulesInstruction,[]),
180189
]
181190

182191
InfoProcessing = "Processing..."
@@ -275,6 +284,8 @@ def runCommand(self):
275284
self.printInTerminal(commandLine, HostHelp)
276285
elif instructions[1].lower() == CredentialStoreInstruction.lower():
277286
self.printInTerminal(commandLine, CredentialStoreHelp)
287+
elif instructions[1].lower() == ReloadModulesInstruction.lower():
288+
self.printInTerminal(commandLine, ReloadModulesHelp)
278289
elif instructions[1].lower() == DropperInstruction.lower():
279290
availableModules = "- Available dropper:\n"
280291
for module in DropperModules:
@@ -295,6 +306,8 @@ def runCommand(self):
295306
self.runDropper(commandLine, instructions)
296307
elif instructions[0].lower()==SocksInstruction.lower():
297308
self.runSocks(commandLine, instructions)
309+
elif instructions[0].lower()==ReloadModulesInstruction.lower():
310+
self.runReloadModules(commandLine, instructions)
298311
else:
299312
self.printInTerminal(commandLine, ErrorCmdUnknow)
300313

@@ -305,6 +318,16 @@ def runHelp(self):
305318
self.printInTerminal(HelpInstruction, getHelpMsg())
306319

307320

321+
def runReloadModules(self, commandLine, instructions):
322+
commandTeamServer = GrpcReloadModulesInstruction
323+
termCommand = TeamServerApi_pb2.TermCommand(cmd=commandTeamServer)
324+
resultTermCommand = self.grpcClient.sendTermCmd(termCommand)
325+
326+
result = resultTermCommand.result
327+
self.printInTerminal(commandLine, result)
328+
return
329+
330+
308331
def runSocks(self, commandLine, instructions):
309332
if len(instructions) < 2:
310333
self.printInTerminal(commandLine, SocksHelp)

core

teamServer/teamServer/TeamServer.cpp

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,7 +1633,68 @@ grpc::Status TeamServer::SendTermCmd(grpc::ServerContext* context, const teamser
16331633
// TODO
16341634
else if(instruction==ReloadModulesInstruction)
16351635
{
1636-
// reload all the modules of the ../Modules directory
1636+
m_logger->info("Reloading TeamServer modules from directory: {0}", m_teamServerModulesDirectoryPath.c_str());
1637+
1638+
// Clear previously loaded modules
1639+
m_moduleCmd.clear();
1640+
1641+
try {
1642+
for (const auto& entry : fs::recursive_directory_iterator(m_teamServerModulesDirectoryPath))
1643+
{
1644+
if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".so")
1645+
{
1646+
m_logger->info("Trying to load {0}", entry.path().c_str());
1647+
1648+
void* handle = dlopen(entry.path().c_str(), RTLD_LAZY);
1649+
if (!handle)
1650+
{
1651+
m_logger->warn("Failed to load {0}: {1}", entry.path().c_str(), dlerror());
1652+
continue;
1653+
}
1654+
1655+
// Derive constructor function name
1656+
std::string funcName = entry.path().filename();
1657+
funcName = funcName.substr(3); // remove lib
1658+
funcName = funcName.substr(0, funcName.length() - 3); // remove .so
1659+
funcName += "Constructor"; // add Constructor
1660+
1661+
m_logger->info("Looking for constructor function: {0}", funcName);
1662+
1663+
constructProc construct = (constructProc)dlsym(handle, funcName.c_str());
1664+
if (!construct) {
1665+
m_logger->warn("Failed to find constructor: {0}", dlerror());
1666+
dlclose(handle);
1667+
continue;
1668+
}
1669+
1670+
ModuleCmd* moduleCmd = construct();
1671+
if (!moduleCmd)
1672+
{
1673+
m_logger->warn("Constructor returned null");
1674+
dlclose(handle);
1675+
continue;
1676+
}
1677+
1678+
std::unique_ptr<ModuleCmd> moduleCmdPtr(moduleCmd);
1679+
moduleCmdPtr->setDirectories(
1680+
m_teamServerModulesDirectoryPath,
1681+
m_linuxModulesDirectoryPath,
1682+
m_windowsModulesDirectoryPath,
1683+
m_linuxBeaconsDirectoryPath,
1684+
m_windowsBeaconsDirectoryPath,
1685+
m_toolsDirectoryPath,
1686+
m_scriptsDirectoryPath
1687+
);
1688+
1689+
m_logger->info("Module {0} loaded", entry.path().filename().c_str());
1690+
m_moduleCmd.push_back(std::move(moduleCmdPtr));
1691+
}
1692+
}
1693+
}
1694+
catch (const std::filesystem::filesystem_error& e)
1695+
{
1696+
m_logger->warn("Error accessing module directory: {0}", e.what());
1697+
}
16371698
}
16381699
else if(instruction == SocksInstruction_)
16391700
{
@@ -1835,6 +1896,17 @@ int TeamServer::prepMsg(const std::string& input, C2Message& c2Message, bool isW
18351896
if (splitedCmd.size() == 2)
18361897
{
18371898
std::string param = splitedCmd[1];
1899+
1900+
// Handle the 4 historicals commands where the cmd name don't match the module file name
1901+
if(param=="ls")
1902+
param = "listDirectory";
1903+
else if(param=="cd")
1904+
param = "changeDirectory";
1905+
else if(param=="ps")
1906+
param = "listProcesses";
1907+
else if(param=="pwd")
1908+
param = "printWorkingDirectory";
1909+
18381910
if(param.size() >= 3 && param.substr(param.size() - 3) == ".so")
18391911
{
18401912

0 commit comments

Comments
 (0)