mirror of
https://github.com/Adaptix-Framework/AdaptixC2
synced 2026-06-08 10:20:39 +00:00
493 lines
18 KiB
C++
493 lines
18 KiB
C++
#include <Agent/Commander.h>
|
|
#include <QJSEngine>
|
|
|
|
QString serializeParam(const QString &token)
|
|
{
|
|
QString result = token;
|
|
result.replace("\\", "\\\\");
|
|
result.replace("\"", "\\\"");
|
|
if (result.contains(' ')) {
|
|
result = "\"" + result + "\"";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QStringList unserializeParams(const QString &commandline)
|
|
{
|
|
QStringList tokens;
|
|
QString token;
|
|
bool inQuotes = false;
|
|
int len = commandline.length();
|
|
|
|
for (int i = 0; i < len; ) {
|
|
QChar c = commandline[i];
|
|
|
|
if (c.isSpace() && !inQuotes) {
|
|
if (!token.isEmpty()) {
|
|
tokens << token;
|
|
token.clear();
|
|
}
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
// If we encounter a double quote
|
|
if (c == '"') {
|
|
inQuotes = !inQuotes;
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
// If we encounter a backslash, handle escape sequences
|
|
if (c == '\\') {
|
|
int numBS = 0;
|
|
// Count the number of consecutive backslashes
|
|
while (i < len && commandline[i] == '\\') {
|
|
++numBS;
|
|
++i;
|
|
}
|
|
// Check if the next character is a double quote
|
|
if (i < len && commandline[i] == '"') {
|
|
// Append half the number of backslashes (integer division)
|
|
token.append(QString(numBS / 2, '\\'));
|
|
if (numBS % 2 == 0) {
|
|
// Even number of backslashes: the quote is not escaped, so it toggles the quote state
|
|
inQuotes = !inQuotes;
|
|
} else {
|
|
// Odd number of backslashes: the quote is escaped, add it to the token
|
|
token.append('"');
|
|
}
|
|
++i;
|
|
} else {
|
|
// No double quote after backslashes: all backslashes are literal
|
|
token.append(QString(numBS, '\\'));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
token.append(c);
|
|
++i;
|
|
}
|
|
|
|
if (!token.isEmpty())
|
|
tokens << token;
|
|
|
|
return tokens;
|
|
}
|
|
|
|
|
|
|
|
Commander::Commander(){}
|
|
|
|
Commander::~Commander() = default;
|
|
|
|
void Commander::AddRegCommands(const CommandsGroup &group) { regCommandsGroup = group; }
|
|
|
|
void Commander::AddAxCommands(const QString &groupName, const QList<Command> &axCommands, QJSEngine *engine)
|
|
{
|
|
CommandsGroup newGroup = {groupName, axCommands};
|
|
axCommandsGroup.append(newGroup);
|
|
}
|
|
|
|
void Commander::RemoveAxCommands(const QString &filepath)
|
|
{
|
|
// extModules.remove(filepath);
|
|
}
|
|
|
|
CommanderResult Commander::ProcessInput(QString agentId, QString cmdline) /// todo ProcessCmdLine
|
|
{
|
|
QStringList parts = unserializeParams(cmdline);
|
|
if (parts.isEmpty())
|
|
return CommanderResult{false, true, ""};
|
|
|
|
QString commandName = parts[0];
|
|
parts.removeAt(0);
|
|
|
|
if( commandName == "help")
|
|
return this->ProcessHelp(parts);
|
|
|
|
for ( auto script_group : axCommandsGroup ) {
|
|
for (Command command : script_group.commands) {
|
|
if (command.name == commandName) {
|
|
QJsonObject jsonObj;
|
|
jsonObj["command"] = command.name;
|
|
|
|
if ( command.subcommands.isEmpty() ) {
|
|
return ProcessCommand(command, parts, jsonObj);
|
|
}
|
|
else {
|
|
if ( parts.isEmpty() )
|
|
return CommanderResult{true, true, "Subcommand must be set"};
|
|
|
|
QString subCommandName = parts[0];
|
|
parts.removeAt(0);
|
|
|
|
for (Command subcommand : command.subcommands) {
|
|
if (subCommandName == subcommand.name) {
|
|
jsonObj["subcommand"] = subcommand.name;
|
|
|
|
return ProcessCommand(subcommand, parts, jsonObj);
|
|
}
|
|
}
|
|
|
|
return CommanderResult{true, true, "Subcommand not found"};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Command command : regCommandsGroup.commands) {
|
|
if (command.name == commandName) {
|
|
QJsonObject jsonObj;
|
|
jsonObj["command"] = command.name;
|
|
|
|
if ( command.subcommands.isEmpty() ) {
|
|
if (command.is_pre_hook) {
|
|
ProcessPreHook(regCommandsGroup.engine, agentId, command, cmdline, parts);
|
|
return CommanderResult{false, false, "", {}, true };
|
|
} else {
|
|
return ProcessCommand(command, parts, jsonObj);
|
|
}
|
|
} else {
|
|
if ( parts.isEmpty() )
|
|
return CommanderResult{true, true, "Subcommand must be set" };
|
|
|
|
QString subCommandName = parts[0];
|
|
parts.removeAt(0);
|
|
|
|
for (Command subcommand : command.subcommands) {
|
|
if (subCommandName == subcommand.name) {
|
|
jsonObj["subcommand"] = subcommand.name;
|
|
|
|
if (subcommand.is_pre_hook) {
|
|
ProcessPreHook(regCommandsGroup.engine, agentId, subcommand, cmdline, parts);
|
|
return CommanderResult{false, false, "", {}, true };
|
|
} else {
|
|
return ProcessCommand(subcommand, parts, jsonObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
return CommanderResult{true, true, "Subcommand not found" };
|
|
}
|
|
}
|
|
}
|
|
|
|
return CommanderResult{true, true, "Command not found"};
|
|
}
|
|
|
|
void Commander::ProcessPreHook(QJSEngine *engine, const QString &agentId, const Command &command, const QString &cmdline, QStringList args)
|
|
{
|
|
if (!engine)
|
|
return;
|
|
|
|
QList<QJSValue> jsArgs;
|
|
jsArgs << engine->toScriptValue(agentId);
|
|
jsArgs << engine->toScriptValue(cmdline);
|
|
for (const QString& arg : args) {
|
|
jsArgs << engine->toScriptValue(arg);
|
|
}
|
|
|
|
QJSValue result = command.pre_hook.call(jsArgs);
|
|
if (result.isError()) {
|
|
qWarning() << "JS error:" << result.property("message").toString();
|
|
qWarning() << "Stack trace:" << result.property("stack").toString();
|
|
}
|
|
}
|
|
|
|
CommanderResult Commander::ProcessCommand(Command command, QStringList args, QJsonObject jsonObj)
|
|
{
|
|
QMap<QString, QString> parsedArgsMap;
|
|
QString wideKey;
|
|
|
|
for (int i = 0; i < args.size(); ++i) {
|
|
QString arg = args[i];
|
|
|
|
bool isWideArgs = true;
|
|
|
|
for (Argument commandArg : command.args) {
|
|
if (commandArg.flag) {
|
|
if (commandArg.type == "BOOL" && commandArg.mark == arg) {
|
|
parsedArgsMap[commandArg.mark] = "true";
|
|
wideKey = commandArg.mark;
|
|
isWideArgs = false;
|
|
break;
|
|
} else if (commandArg.mark == arg && args.size() > i + 1) {
|
|
++i;
|
|
parsedArgsMap[commandArg.name] = args[i];
|
|
wideKey = commandArg.name;
|
|
isWideArgs = false;
|
|
break;
|
|
}
|
|
} else if (!parsedArgsMap.contains(commandArg.name)) {
|
|
parsedArgsMap[commandArg.name] = arg;
|
|
wideKey = commandArg.name;
|
|
isWideArgs = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( isWideArgs ) {
|
|
QString wideStr;
|
|
for(int j = i; j < args.size(); ++j) {
|
|
wideStr += " " + args[j];
|
|
}
|
|
parsedArgsMap[wideKey] += wideStr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (Argument commandArg : command.args) {
|
|
if (parsedArgsMap.contains(commandArg.name) || parsedArgsMap.contains(commandArg.mark)) {
|
|
if (commandArg.type == "STRING") {
|
|
jsonObj[commandArg.name] = parsedArgsMap[commandArg.name];
|
|
}
|
|
else if (commandArg.type == "INT") {
|
|
jsonObj[commandArg.name] = parsedArgsMap[commandArg.name].toInt();
|
|
}
|
|
else if (commandArg.type == "BOOL") {
|
|
jsonObj[commandArg.mark] = parsedArgsMap[commandArg.mark] == "true";
|
|
}
|
|
else if (commandArg.type == "FILE") {
|
|
QString path = parsedArgsMap[commandArg.name];
|
|
if (path.startsWith("~/"))
|
|
path = QDir::home().filePath(path.mid(2));
|
|
|
|
QFile file(path);
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
QByteArray fileData = file.readAll();
|
|
jsonObj[commandArg.name] = QString::fromLatin1(fileData.toBase64());
|
|
file.close();
|
|
} else {
|
|
return CommanderResult{true, true, "Failed to open file: " + path};
|
|
}
|
|
}
|
|
} else if (commandArg.required) {
|
|
if ( (commandArg.defaultValue.isNull() || !commandArg.defaultValue.isValid()) && !commandArg.defaultUsed) {
|
|
return CommanderResult{true, true, "Missing required argument: " + commandArg.name};
|
|
}
|
|
else {
|
|
if (commandArg.type == "STRING" && commandArg.defaultValue.typeId() == QMetaType::QString) {
|
|
jsonObj[commandArg.name] = commandArg.defaultValue.toString();
|
|
} else if (commandArg.type == "INT" && commandArg.defaultValue.typeId() == QMetaType::Int) {
|
|
jsonObj[commandArg.name] = commandArg.defaultValue.toInt();
|
|
} else if (commandArg.type == "BOOL" && commandArg.defaultValue.typeId() == QMetaType::Bool) {
|
|
jsonObj[commandArg.mark] = commandArg.defaultValue.toBool();
|
|
}
|
|
else {
|
|
return CommanderResult{true, true, "Missing required argument: " + commandArg.name};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QString msg = command.message;
|
|
if( !msg.isEmpty() ) {
|
|
for ( QString k : parsedArgsMap.keys() ) {
|
|
QString param = "<" + k + ">";
|
|
if( msg.contains(param) )
|
|
msg = msg.replace(param, parsedArgsMap[k]);
|
|
}
|
|
jsonObj["message"] = msg;
|
|
}
|
|
|
|
return CommanderResult{false, false, "", jsonObj };
|
|
}
|
|
|
|
QString Commander::GetError() { return error; }
|
|
|
|
CommanderResult Commander::ProcessHelp(QStringList commandParts)
|
|
{
|
|
QString result;
|
|
QTextStream output(&result);
|
|
if (commandParts.isEmpty()) {
|
|
int TotalWidth = 24;
|
|
output << QString("\n");
|
|
output << QString(" Command Description\n");
|
|
output << QString(" ------- -----------\n");
|
|
|
|
for (auto command : regCommandsGroup.commands) {
|
|
QString commandName = command.name;
|
|
if (!command.subcommands.isEmpty())
|
|
commandName += '*';
|
|
|
|
QString tab = QString(TotalWidth - commandName.size(), ' ');
|
|
output << " " + commandName + tab + " " + command.description + "\n";
|
|
}
|
|
|
|
for ( auto script_group : axCommandsGroup ){
|
|
output << QString("\n");
|
|
output << QString(" Group - " + script_group.groupName + "\n");
|
|
output << QString(" =====================================\n");
|
|
|
|
for ( auto command : script_group.commands ) {
|
|
QString commandName = command.name;
|
|
if ( command.subcommands.isEmpty() ) {
|
|
QString tab = QString(TotalWidth - commandName.size(), ' ');
|
|
output << " " + commandName + tab + " " + command.description + "\n";
|
|
}
|
|
else {
|
|
for ( auto subcmd : command.subcommands ) {
|
|
QString subcmdName = commandName + " " + subcmd.name;
|
|
QString tab = QString(TotalWidth - subcmdName.size(), ' ');
|
|
output << " " + subcmdName + tab + " " + subcmd.description + "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return CommanderResult{false, true, result};
|
|
}
|
|
else {
|
|
Command foundCommand;
|
|
QString commandName = commandParts[0];
|
|
|
|
for (Command cmd : regCommandsGroup.commands) {
|
|
if (cmd.name == commandName) {
|
|
foundCommand = cmd;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for(auto script_group : axCommandsGroup) {
|
|
if ( !foundCommand.name.isEmpty() )
|
|
break;
|
|
|
|
for (Command cmd : script_group.commands) {
|
|
if (cmd.name == commandName) {
|
|
foundCommand = cmd;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( foundCommand.name.isEmpty() )
|
|
return CommanderResult{true, true, "Unknown command: " + commandName};
|
|
|
|
if (commandParts.size() == 1) {
|
|
output << QString("\n");
|
|
output << " Command : " + foundCommand.name + "\n";
|
|
if(!foundCommand.description.isEmpty())
|
|
output << " Description : " + foundCommand.description + "\n";
|
|
if(!foundCommand.example.isEmpty())
|
|
output << " Example : " + foundCommand.example + "\n";
|
|
if( !foundCommand.subcommands.isEmpty() ) {
|
|
output << "\n";
|
|
output << " SubCommand Description\n";
|
|
output << " ---------- -----------\n";
|
|
for ( auto subcmd : foundCommand.subcommands ) {
|
|
int TotalWidth = 20;
|
|
int cmdWidth = subcmd.name.size();
|
|
if (cmdWidth > TotalWidth)
|
|
cmdWidth = TotalWidth;
|
|
|
|
QString tab = QString(TotalWidth - cmdWidth, ' ');
|
|
output << " " + subcmd.name + tab + " " + subcmd.description + "\n";
|
|
}
|
|
}
|
|
else if (!foundCommand.args.isEmpty()) {
|
|
QString usageHelp;
|
|
QTextStream usageStream(&usageHelp);
|
|
usageStream << foundCommand.name;
|
|
|
|
int maxArgLength = 0;
|
|
for (const auto &arg : foundCommand.args) {
|
|
QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]");
|
|
maxArgLength = qMax(maxArgLength, fullarg.size());
|
|
usageStream << " " + fullarg;
|
|
}
|
|
|
|
output << " Usage : " + usageHelp + "\n\n";
|
|
output << " Arguments:\n";
|
|
|
|
for (const auto &arg : foundCommand.args) {
|
|
QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]");
|
|
QString padding = QString(maxArgLength - fullarg.size(), ' ');
|
|
output << " " + fullarg + padding + " : " + arg.type + (arg.defaultUsed ? " (default: '" + arg.defaultValue.toString() + "'). " : ". ") + arg.description + "\n";
|
|
}
|
|
}
|
|
}
|
|
else if (commandParts.size() == 2) {
|
|
Command foundSubCommand;
|
|
QString subCommandName = commandParts[1];
|
|
for (Command subcmd : foundCommand.subcommands) {
|
|
if (subcmd.name == subCommandName) {
|
|
foundSubCommand = subcmd;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( foundSubCommand.name.isEmpty() )
|
|
return CommanderResult{true, true, "Unknown subcommand: " + subCommandName};
|
|
|
|
output << " Command : " + foundCommand.name + " " + foundSubCommand.name +"\n";
|
|
if(!foundSubCommand.description.isEmpty())
|
|
output << " Description : " + foundSubCommand.description + "\n";
|
|
if(!foundSubCommand.example.isEmpty())
|
|
output << " Example : " + foundSubCommand.example + "\n";
|
|
if (!foundSubCommand.args.isEmpty()) {
|
|
QString usageHelp;
|
|
QTextStream usageStream(&usageHelp);
|
|
usageStream << foundCommand.name + " " + foundSubCommand.name;
|
|
|
|
int maxArgLength = 0;
|
|
for (const auto &arg : foundSubCommand.args) {
|
|
QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]");
|
|
maxArgLength = qMax(maxArgLength, fullarg.size());
|
|
usageStream << " " + fullarg;
|
|
}
|
|
|
|
output << " Usage : " + usageHelp + "\n\n";
|
|
output << " Arguments:\n";
|
|
|
|
for (const auto &arg : foundSubCommand.args) {
|
|
QString fullarg = (arg.required ? "<" : "[") + arg.mark + (arg.mark.isEmpty() || arg.name.isEmpty() ? "" : " ") + arg.name + (arg.required ? ">" : "]");
|
|
QString padding = QString(maxArgLength - fullarg.size(), ' ');
|
|
output << " " + fullarg + padding + " : " + arg.type + (arg.defaultUsed ? " (default: '" + arg.defaultValue.toString() + "'). " : ". ") + arg.description + "\n";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return CommanderResult{true, true, "Error Help format: 'help [command [subcommand]]'"};
|
|
}
|
|
return CommanderResult{false, true, output.readAll()};
|
|
}
|
|
}
|
|
|
|
QStringList Commander::GetCommands()
|
|
{
|
|
QStringList commandList;
|
|
QStringList helpCommandList;
|
|
|
|
for (Command cmd : regCommandsGroup.commands) {
|
|
|
|
helpCommandList << "help " + cmd.name;
|
|
if (cmd.subcommands.isEmpty())
|
|
commandList << cmd.name;
|
|
|
|
for (Command subcmd : cmd.subcommands) {
|
|
commandList << cmd.name + " " + subcmd.name;
|
|
helpCommandList << "help " + cmd.name + " " + subcmd.name;
|
|
}
|
|
}
|
|
|
|
for( auto script_group : axCommandsGroup) {
|
|
for (Command cmd : script_group.commands) {
|
|
|
|
helpCommandList << "help " + cmd.name;
|
|
if (cmd.subcommands.isEmpty())
|
|
commandList << cmd.name;
|
|
|
|
for (Command subcmd : cmd.subcommands) {
|
|
commandList << cmd.name + " " + subcmd.name;
|
|
helpCommandList << "help " + cmd.name + " " + subcmd.name;
|
|
}
|
|
}
|
|
}
|
|
|
|
for( QString cmd : helpCommandList)
|
|
commandList << cmd;
|
|
|
|
return commandList;
|
|
}
|