Esegui un processo da un servizio Windows come utente corrente

Al momento dispongo di un servizio Windows in esecuzione con l’account di sistema. Il mio problema è che ho bisogno di avviare determinati processi all’interno del servizio come l’utente attualmente connesso. Ho tutto il codice ecc. Per ottenere l’utente loggato corrente / sessione triggers.

Il mio problema è che ho bisogno di generare un processo come utente connesso ma non conosco le credenziali dell’utente, ecc.

Il servizio è .net servizio compilato e mi aspetto che ho bisogno di utilizzare alcuni metodi Pinvoke per ottenere un handle di uno degli attuali processi utente al fine di duplicarlo e pranzare come processo con l’handle.

Purtroppo non riesco a trovare alcuna buona documentazione / soluzione su come implementarla?

Se qualcuno è in grado di darmi qualche consiglio / esempio, lo apprezzerei molto.

* Aggiornato * Penso di averlo spiegato in modo errato e di doverlo riorganizzare in base a ciò che effettivamente richiedo. Non voglio necessariamente avviare un nuovo processo, voglio solo impersonare l’utente connesso. Sono stato così impegnato a guardare CreateProcess, ecc. Ho guidato un percorso di creazione di un nuovo processo come utente attualmente connesso (che non è particolarmente quello che voglio fare).

A turno voglio solo eseguire un codice sotto il contesto utente corrente (Impersonare l’utente connesso corrente)?

Un’opzione consiste nell’avere un’applicazione in background che si avvia automaticamente quando l’utente si collega e ascolta i comandi dal tuo servizio tramite WCF, o parsimonia, o semplicemente monitorando alcuni comandi di file e di lettura da lì.

Un’altra opzione è quella di fare ciò che inizialmente richiesto: avviare utilizzando Windows API. Ma il codice è piuttosto spaventoso. Ecco un esempio, che puoi usare. Eseguirà qualsiasi riga di comando nella sessione utente attualmente triggers, con il metodo CreateProcessInConsoleSession:

internal class ApplicationLauncher { public enum TOKEN_INFORMATION_CLASS { TokenUser = 1, TokenGroups, TokenPrivileges, TokenOwner, TokenPrimaryGroup, TokenDefaultDacl, TokenSource, TokenType, TokenImpersonationLevel, TokenStatistics, TokenRestrictedSids, TokenSessionId, TokenGroupsAndPrivileges, TokenSessionReference, TokenSandBoxInert, TokenAuditPolicy, TokenOrigin, MaxTokenInfoClass // MaxTokenInfoClass should always be the last enum } public const int READ_CONTROL = 0x00020000; public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000; public const int STANDARD_RIGHTS_READ = READ_CONTROL; public const int STANDARD_RIGHTS_WRITE = READ_CONTROL; public const int STANDARD_RIGHTS_EXECUTE = READ_CONTROL; public const int STANDARD_RIGHTS_ALL = 0x001F0000; public const int SPECIFIC_RIGHTS_ALL = 0x0000FFFF; public const int TOKEN_ASSIGN_PRIMARY = 0x0001; public const int TOKEN_DUPLICATE = 0x0002; public const int TOKEN_IMPERSONATE = 0x0004; public const int TOKEN_QUERY = 0x0008; public const int TOKEN_QUERY_SOURCE = 0x0010; public const int TOKEN_ADJUST_PRIVILEGES = 0x0020; public const int TOKEN_ADJUST_GROUPS = 0x0040; public const int TOKEN_ADJUST_DEFAULT = 0x0080; public const int TOKEN_ADJUST_SESSIONID = 0x0100; public const int TOKEN_ALL_ACCESS_P = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT); public const int TOKEN_ALL_ACCESS = TOKEN_ALL_ACCESS_P | TOKEN_ADJUST_SESSIONID; public const int TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY; public const int TOKEN_WRITE = STANDARD_RIGHTS_WRITE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT; public const int TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE; public const uint MAXIMUM_ALLOWED = 0x2000000; public const int CREATE_NEW_PROCESS_GROUP = 0x00000200; public const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; public const int IDLE_PRIORITY_CLASS = 0x40; public const int NORMAL_PRIORITY_CLASS = 0x20; public const int HIGH_PRIORITY_CLASS = 0x80; public const int REALTIME_PRIORITY_CLASS = 0x100; public const int CREATE_NEW_CONSOLE = 0x00000010; public const string SE_DEBUG_NAME = "SeDebugPrivilege"; public const string SE_RESTORE_NAME = "SeRestorePrivilege"; public const string SE_BACKUP_NAME = "SeBackupPrivilege"; public const int SE_PRIVILEGE_ENABLED = 0x0002; public const int ERROR_NOT_ALL_ASSIGNED = 1300; private const uint TH32CS_SNAPPROCESS = 0x00000002; public static int INVALID_HANDLE_VALUE = -1; [DllImport("advapi32.dll", SetLastError = true)] public static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid); [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool DuplicateToken(IntPtr ExistingTokenHandle, int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, ref uint TokenInformation, uint TokenInformationLength); [DllImport("userenv.dll", SetLastError = true)] public static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); public static bool CreateProcessInConsoleSession(String CommandLine, bool bElevate) { PROCESS_INFORMATION pi; bool bResult = false; uint dwSessionId, winlogonPid = 0; IntPtr hUserToken = IntPtr.Zero, hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero; Debug.Print("CreateProcessInConsoleSession"); // Log the client on to the local computer. dwSessionId = WTSGetActiveConsoleSessionId(); // Find the winlogon process var procEntry = new PROCESSENTRY32(); uint hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnap == INVALID_HANDLE_VALUE) { return false; } procEntry.dwSize = (uint) Marshal.SizeOf(procEntry); //sizeof(PROCESSENTRY32); if (Process32First(hSnap, ref procEntry) == 0) { return false; } String strCmp = "explorer.exe"; do { if (strCmp.IndexOf(procEntry.szExeFile) == 0) { // We found a winlogon process...make sure it's running in the console session uint winlogonSessId = 0; if (ProcessIdToSessionId(procEntry.th32ProcessID, ref winlogonSessId) && winlogonSessId == dwSessionId) { winlogonPid = procEntry.th32ProcessID; break; } } } while (Process32Next(hSnap, ref procEntry) != 0); //Get the user token used by DuplicateTokenEx WTSQueryUserToken(dwSessionId, ref hUserToken); var si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); si.lpDesktop = "winsta0\\default"; var tp = new TOKEN_PRIVILEGES(); var luid = new LUID(); hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid); if ( !OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID | TOKEN_READ | TOKEN_WRITE, ref hPToken)) { Debug.Print(String.Format("CreateProcessInConsoleSession OpenProcessToken error: {0}", Marshal.GetLastWin32Error())); } if (!LookupPrivilegeValue(IntPtr.Zero, SE_DEBUG_NAME, ref luid)) { Debug.Print(String.Format("CreateProcessInConsoleSession LookupPrivilegeValue error: {0}", Marshal.GetLastWin32Error())); } var sa = new SECURITY_ATTRIBUTES(); sa.Length = Marshal.SizeOf(sa); if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int) SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int) TOKEN_TYPE.TokenPrimary, ref hUserTokenDup)) { Debug.Print( String.Format( "CreateProcessInConsoleSession DuplicateTokenEx error: {0} Token does not have the privilege.", Marshal.GetLastWin32Error())); CloseHandle(hProcess); CloseHandle(hUserToken); CloseHandle(hPToken); return false; } if (bElevate) { //tp.Privileges[0].Luid = luid; //tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; tp.PrivilegeCount = 1; tp.Privileges = new int[3]; tp.Privileges[2] = SE_PRIVILEGE_ENABLED; tp.Privileges[1] = luid.HighPart; tp.Privileges[0] = luid.LowPart; //Adjust Token privilege if ( !SetTokenInformation(hUserTokenDup, TOKEN_INFORMATION_CLASS.TokenSessionId, ref dwSessionId, (uint) IntPtr.Size)) { Debug.Print( String.Format( "CreateProcessInConsoleSession SetTokenInformation error: {0} Token does not have the privilege.", Marshal.GetLastWin32Error())); //CloseHandle(hProcess); //CloseHandle(hUserToken); //CloseHandle(hPToken); //CloseHandle(hUserTokenDup); //return false; } if ( !AdjustTokenPrivileges(hUserTokenDup, false, ref tp, Marshal.SizeOf(tp), /*(PTOKEN_PRIVILEGES)*/ IntPtr.Zero, IntPtr.Zero)) { int nErr = Marshal.GetLastWin32Error(); if (nErr == ERROR_NOT_ALL_ASSIGNED) { Debug.Print( String.Format( "CreateProcessInConsoleSession AdjustTokenPrivileges error: {0} Token does not have the privilege.", nErr)); } else { Debug.Print(String.Format("CreateProcessInConsoleSession AdjustTokenPrivileges error: {0}", nErr)); } } } uint dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE; IntPtr pEnv = IntPtr.Zero; if (CreateEnvironmentBlock(ref pEnv, hUserTokenDup, true)) { dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT; } else { pEnv = IntPtr.Zero; } // Launch the process in the client's logon session. bResult = CreateProcessAsUser(hUserTokenDup, // client's access token null, // file to execute CommandLine, // command line ref sa, // pointer to process SECURITY_ATTRIBUTES ref sa, // pointer to thread SECURITY_ATTRIBUTES false, // handles are not inheritable (int) dwCreationFlags, // creation flags pEnv, // pointer to new environment block null, // name of current directory ref si, // pointer to STARTUPINFO structure out pi // receives information about new process ); // End impersonation of client. //GetLastError should be 0 int iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); //Close handles task CloseHandle(hProcess); CloseHandle(hUserToken); CloseHandle(hUserTokenDup); CloseHandle(hPToken); return (iResultOfCreateProcessAsUser == 0) ? true : false; } [DllImport("kernel32.dll")] private static extern int Process32First(uint hSnapshot, ref PROCESSENTRY32 lppe); [DllImport("kernel32.dll")] private static extern int Process32Next(uint hSnapshot, ref PROCESSENTRY32 lppe); [DllImport("kernel32.dll", SetLastError = true)] private static extern uint CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hSnapshot); [DllImport("kernel32.dll")] private static extern uint WTSGetActiveConsoleSessionId(); [DllImport("Wtsapi32.dll")] private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); [DllImport("kernel32.dll")] private static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId); [DllImport("kernel32.dll")] private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [DllImport("advapi32", SetLastError = true)] [SuppressUnmanagedCodeSecurity] private static extern bool OpenProcessToken(IntPtr ProcessHandle, // handle to process int DesiredAccess, // desired access to process ref IntPtr TokenHandle); #region Nested type: LUID [StructLayout(LayoutKind.Sequential)] internal struct LUID { public int LowPart; public int HighPart; } #endregion //end struct #region Nested type: LUID_AND_ATRIBUTES [StructLayout(LayoutKind.Sequential)] internal struct LUID_AND_ATRIBUTES { public LUID Luid; public int Attributes; } #endregion #region Nested type: PROCESSENTRY32 [StructLayout(LayoutKind.Sequential)] private struct PROCESSENTRY32 { public uint dwSize; public readonly uint cntUsage; public readonly uint th32ProcessID; public readonly IntPtr th32DefaultHeapID; public readonly uint th32ModuleID; public readonly uint cntThreads; public readonly uint th32ParentProcessID; public readonly int pcPriClassBase; public readonly uint dwFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public readonly string szExeFile; } #endregion #region Nested type: PROCESS_INFORMATION [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } #endregion #region Nested type: SECURITY_ATTRIBUTES [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } #endregion #region Nested type: SECURITY_IMPERSONATION_LEVEL private enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3, } #endregion #region Nested type: STARTUPINFO [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public int cb; public String lpReserved; public String lpDesktop; public String lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } #endregion #region Nested type: TOKEN_PRIVILEGES [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_PRIVILEGES { internal int PrivilegeCount; //LUID_AND_ATRIBUTES [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] internal int[] Privileges; } #endregion #region Nested type: TOKEN_TYPE private enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation = 2 } #endregion // handle to open access token } 

Come è così comune con questi tipi di domande sui servizi di Windows, stai operando nella mentalità di un sistema operativo a utente singolo. L’intera ragione per cui hai deciso di scrivere la tua app come servizio era perché ti imbattevi in ​​conflitti tra il tuo modello mentale di un sistema operativo a utente singolo e la realtà di un sistema operativo multiutente. Sfortunatamente, un servizio non ha risolto tutti i tuoi problemi e ora stai cercando di capire come completare il secondo passo nel design compromesso, ultimamente condannato.

Il fatto è che non è ansible garantire che vi sia un “utente connesso”. Se nessuno ha effettuato l’accesso alla workstation, non ci sarà nessuno connesso, ma il servizio sarà ancora in esecuzione.

Anche se in qualche modo hai superato questo problema assicurandoti che qualcuno sia sempre connesso (imansible), ti imbatterai nella situazione in cui sono connessi più utenti. Quindi quale dovrebbe essere il tuo servizio avviare il processo? Dovrebbe sceglierne uno a caso?

E nel tuo caso è necessario distinguere tra gli utenti connessi localmente alla console e quelli che hanno effettuato l’accesso da remoto? Ricorda che gli utenti remoti non avranno una console locale.

Se in qualche modo riuscissi a superare tutti questi ostacoli (sfortunatamente, probabilmente seppellendo la testa nella sabbia e continuando a fingere che Windows sia un sistema operativo a utente singolo), potresti utilizzare la funzione WTSGetActiveConsoleSessionId per ottenere l’ID di sessione corrente, la funzione WTSQueryUserToken per ottenere il token utente corrispondente a quell’ID di sessione e infine la funzione CreateProcessAsUser per avviare il processo nel contesto di tale utente. Se ce n’è uno. E hanno i privilegi appropriati. E la console fisica non è collegata a una sessione fittizia. E non stai utilizzando un SKU server che consente più sessioni di console attive. E…

Se si può decidere su un particolare utente il cui account si desidera utilizzare per avviare il processo ausiliario, è ansible accedere a tale utente, manipolare il proprio token utente, eseguire il processo e infine chiudere il processo e disconnettersi dall’utente. La funzione CreateProcessWithLogonUser racchiude un sacco di questo drudgery per te, rendendo il codice molto più svelto. Ma le apparenze possono ingannare e questo ha ancora delle enormi implicazioni sulla sicurezza che probabilmente non capisci completamente se stai facendo questa domanda in primo luogo. E davvero non puoi permetterti di non capire i rischi per la sicurezza come questo.

Inoltre, gli utenti che hanno eseguito l’accesso con LogonUser (che viene eseguito automaticamente quando si utilizza la funzione CreateProcessWithLogonUser ) non dispongono di una stazione finestra e di un desktop su cui possono avviare processi interattivi. Quindi se il processo che desideri avviare nel contesto di quell’utente mostrerà un qualsiasi tipo di interfaccia utente, sei sfortunato. Windows ucciderà la tua app non appena tenta di accedere a un desktop per il quale non dispone delle autorizzazioni necessarie. Non c’è modo, da un servizio Windows, di ottenere l’handle di un desktop che ti sarà utile (il che spiega molto bene la regola generale che probabilmente già conosci, che i servizi non possono visualizzare alcun tipo di interfaccia utente).