基于Active Directory凭据远程运行程序的.NET/C++实现咨询
Absolutely! Both .NET and C++ can absolutely handle this scenario—using valid Active Directory credentials to copy an EXE to a remote domain-joined PC and run it is totally feasible. Let’s break down how to approach this for each platform, with practical code examples and key considerations.
.NET provides several built-in and P/Invoke-based tools to handle impersonation, file transfer, and remote process execution. Here’s a step-by-step approach:
1. Impersonate the AD User
First, you need to impersonate the AD account to gain the necessary permissions on the remote machine. Using the LogonUser Win32 API via P/Invoke is a reliable method:
using System; using System.Runtime.InteropServices; using System.Security.Principal; public class ImpersonationHelper { [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool LogonUser( string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public extern static bool CloseHandle(IntPtr handle); public static WindowsImpersonationContext ImpersonateUser(string username, string domain, string password) { IntPtr userToken = IntPtr.Zero; // LOGON32_LOGON_NEW_CREDENTIALS = 9, suitable for remote resource access bool success = LogonUser(username, domain, password, 9, 0, out userToken); if (!success) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); WindowsIdentity identity = new WindowsIdentity(userToken); CloseHandle(userToken); return identity.Impersonate(); } }
2. Copy the EXE to the Remote Machine
With impersonation active, you can copy the EXE to a shared folder on the remote machine (like the default Admin$ share, or a custom temp folder):
string sourceExePath = @"C:\local\myapp.exe"; string remoteMachineName = "REMOTE-PC-NAME"; string remoteExePath = @$"\\{remoteMachineName}\C$\temp\myapp.exe"; // Create remote temp folder if it doesn't exist System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(remoteExePath)); // Copy the executable file System.IO.File.Copy(sourceExePath, remoteExePath, overwrite: true);
3. Execute the Remote Process
Use WMI (Windows Management Instrumentation) to start the process on the remote machine—it’s well-supported in .NET and integrates seamlessly with domain credentials:
using System.Management; public static void StartRemoteProcess(string remoteMachine, string domain, string username, string password, string exePath) { ConnectionOptions options = new ConnectionOptions { Username = $@"{domain}\{username}", Password = password, Impersonation = ImpersonationLevel.Impersonate, Authentication = AuthenticationLevel.Default }; ManagementScope scope = new ManagementScope($@"\\{remoteMachine}\root\cimv2", options); scope.Connect(); ManagementClass processClass = new ManagementClass(scope, new ManagementPath("Win32_Process"), new ObjectGetOptions()); ManagementBaseObject inParams = processClass.GetMethodParameters("Create"); inParams["CommandLine"] = exePath; ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null); uint returnValue = (uint)outParams["returnValue"]; if (returnValue != 0) throw new Exception($"Failed to start remote process. Error code: {returnValue}"); }
Key Notes for .NET:
- Ensure the AD user has administrative privileges on the remote machine, or at least permissions to access the shared folder and start processes.
- The remote machine must have WMI enabled (default on domain-joined PCs) and firewall rules allowing WMI traffic (ports 135 and dynamic RPC ports).
- Never hardcode credentials—use secure storage like Windows Credential Manager to retrieve them at runtime.
For C++, you’ll use Win32 APIs directly. The workflow mirrors the .NET approach: impersonate the user, copy the file, then start the remote process.
1. Impersonate the AD User
Use LogonUser and ImpersonateLoggedOnUser APIs to assume the AD user’s context:
#include <windows.h> #include <iostream> HANDLE ImpersonateADUser(LPCWSTR username, LPCWSTR domain, LPCWSTR password) { HANDLE hToken = NULL; BOOL success = LogonUserW( username, domain, password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, &hToken); if (!success) { DWORD err = GetLastError(); std::cerr << "LogonUser failed. Error: " << err << std::endl; return NULL; } if (!ImpersonateLoggedOnUser(hToken)) { DWORD err = GetLastError(); std::cerr << "ImpersonateLoggedOnUser failed. Error: " << err << std::endl; CloseHandle(hToken); return NULL; } return hToken; }
2. Copy the EXE to the Remote Machine
Use CopyFileW to transfer the executable to the remote share:
#include <shlwapi.h> #pragma comment(lib, "shlwapi.lib") BOOL CopyExeToRemote(LPCWSTR localPath, LPCWSTR remotePath) { // Create remote directory if it doesn't exist WCHAR remoteDir[MAX_PATH]; wcscpy_s(remoteDir, remotePath); PathRemoveFileSpecW(remoteDir); if (!CreateDirectoryW(remoteDir, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { std::cerr << "Failed to create remote directory. Error: " << GetLastError() << std::endl; return FALSE; } return CopyFileW(localPath, remotePath, FALSE); }
3. Execute the Remote Process
Use WMI via COM to start the process on the remote machine:
#include <comdef.h> #include <Wbemidl.h> #pragma comment(lib, "wbemuuid.lib") BOOL StartRemoteProcessCplusplus(LPCWSTR remoteMachine, LPCWSTR domain, LPCWSTR username, LPCWSTR password, LPCWSTR exePath) { HRESULT hres; // Initialize COM hres = CoInitializeEx(0, COINIT_MULTITHREADED); if (FAILED(hres)) { std::cerr << "CoInitializeEx failed. HRESULT: 0x" << std::hex << hres << std::endl; return FALSE; } // Set COM security levels hres = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); if (FAILED(hres)) { std::cerr << "CoInitializeSecurity failed. HRESULT: 0x" << std::hex << hres << std::endl; CoUninitialize(); return FALSE; } // Obtain WMI locator IWbemLocator* pLoc = NULL; hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc); if (FAILED(hres)) { std::cerr << "CoCreateInstance failed. HRESULT: 0x" << std::hex << hres << std::endl; CoUninitialize(); return FALSE; } // Connect to remote WMI service IWbemServices* pSvc = NULL; BSTR serverPath = SysAllocStringLen(NULL, wcslen(remoteMachine) + 15); swprintf_s(serverPath, L"\\\\%s\\ROOT\\CIMV2", remoteMachine); COAUTHIDENTITY authIdent = { 0 }; authIdent.User = (USHORT*)username; authIdent.UserLength = (USHORT)wcslen(username); authIdent.Domain = (USHORT*)domain; authIdent.DomainLength = (USHORT)wcslen(domain); authIdent.Password = (USHORT*)password; authIdent.PasswordLength = (USHORT)wcslen(password); authIdent.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; COAUTHINFO authInfo = { 0 }; authInfo.dwAuthnSvc = RPC_C_AUTHN_WINNT; authInfo.dwAuthzSvc = RPC_C_AUTHZ_NONE; authInfo.pwszServerPrincName = NULL; authInfo.dwAuthnLevel = RPC_C_AUTHN_LEVEL_DEFAULT; authInfo.dwImpersonationLevel = RPC_C_IMP_LEVEL_IMPERSONATE; authInfo.pAuthIdentityData = &authIdent; authInfo.dwCapabilities = EOAC_NONE; CONNECTINFO connectInfo = { 0 }; connectInfo.pAuthInfo = &authInfo; hres = pLoc->ConnectServer( serverPath, NULL, NULL, 0, NULL, 0, 0, &pSvc); SysFreeString(serverPath); if (FAILED(hres)) { std::cerr << "ConnectServer failed. HRESULT: 0x" << std::hex << hres << std::endl; pLoc->Release(); CoUninitialize(); return FALSE; } // Set proxy security blanket hres = CoSetProxyBlanket( pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, &authIdent, EOAC_NONE); if (FAILED(hres)) { std::cerr << "CoSetProxyBlanket failed. HRESULT: 0x" << std::hex << hres << std::endl; pSvc->Release(); pLoc->Release(); CoUninitialize(); return FALSE; } // Execute Win32_Process.Create method IWbemClassObject* pProcessClass = NULL; hres = pSvc->GetObject(L"Win32_Process", 0, NULL, &pProcessClass, NULL); if (FAILED(hres)) { std::cerr << "GetObject failed. HRESULT: 0x" << std::hex << hres << std::endl; pSvc->Release(); pLoc->Release(); CoUninitialize(); return FALSE; } IWbemClassObject* pInParams = NULL; hres = pProcessClass->GetMethod(L"Create", 0, &pInParams, NULL); if (FAILED(hres)) { std::cerr << "GetMethod failed. HRESULT: 0x" << std::hex << hres << std::endl; pProcessClass->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); return FALSE; } VARIANT cmdLine; VariantInit(&cmdLine); cmdLine.vt = VT_BSTR; cmdLine.bstrVal = SysAllocString(exePath); hres = pInParams->Put(L"CommandLine", 0, &cmdLine, 0); VariantClear(&cmdLine); if (FAILED(hres)) { std::cerr << "Put failed. HRESULT: 0x" << std::hex << hres << std::endl; pInParams->Release(); pProcessClass->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); return FALSE; } IWbemClassObject* pOutParams = NULL; hres = pSvc->ExecMethod(L"Win32_Process", L"Create", 0, NULL, pInParams, &pOutParams, NULL); if (FAILED(hres)) { std::cerr << "ExecMethod failed. HRESULT: 0x" << std::hex << hres << std::endl; pInParams->Release(); pProcessClass->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); return FALSE; } // Check process start result VARIANT returnVal; VariantInit(&returnVal); hres = pOutParams->Get(L"returnValue", 0, &returnVal, NULL, NULL); if (returnVal.lVal != 0) { std::cerr << "Remote process failed to start. Error code: " << returnVal.lVal << std::endl; VariantClear(&returnVal); pOutParams->Release(); pInParams->Release(); pProcessClass->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); return FALSE; } // Cleanup COM resources VariantClear(&returnVal); pOutParams->Release(); pInParams->Release(); pProcessClass->Release(); pSvc->Release(); pLoc->Release(); CoUninitialize(); return TRUE; }
Key Notes for C++:
- Link against required libraries:
advapi32.lib,kernel32.lib,wbemuuid.lib,shlwapi.lib. - Same remote machine requirements apply: WMI enabled, firewall rules allowing WMI traffic, and AD user privileges.
- Always clean up COM objects and handles to avoid memory leaks.
General Security & Best Practices
- Never hardcode credentials: Use secure storage mechanisms like Windows Credential Manager to retrieve credentials at runtime.
- Minimize privileges: Use the least-privileged AD account that has only the necessary permissions (file access, process execution) on the remote machines.
- Robust error handling: Add detailed error handling for all API calls—remote operations can fail due to network issues, permission problems, or machine availability.
- Audit actions: Log all remote operations for compliance and troubleshooting purposes.
内容的提问来源于stack exchange,提问作者Process Monitor

