1 using System;
2 using System.Reflection;
3 using System.Security.Principal;
4 using System.Runtime.InteropServices;
5 using System.Diagnostics;
6
7
8 namespace Common.Utilities.Processes
9 { 10 public class ProcessUtilities
11 { 12 /*** Imports ***/
13 #region Imports
14
15 [DllImport( "advapi32.dll", EntryPoint = "AdjustTokenPrivileges", SetLastError = true )]
16 public static extern bool AdjustTokenPrivileges( IntPtr in_hToken, [MarshalAs( UnmanagedType.Bool )]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 BufferLength, IntPtr PreviousState, IntPtr ReturnLength );
17
18 [DllImport( "advapi32.dll", EntryPoint = "OpenProcessToken", SetLastError = true )]
19 public static extern bool OpenProcessToken( IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle );
20
21 [DllImport( "advapi32.dll", EntryPoint = "LookupPrivilegeValue", SetLastError = true, CharSet = CharSet.Auto )]
22 public static extern bool LookupPrivilegeValue( string lpSystemName, string lpName, out LUID lpLuid );
23
24 [DllImport( "userenv.dll", EntryPoint = "CreateEnvironmentBlock", SetLastError = true )]
25 public static extern bool CreateEnvironmentBlock( out IntPtr out_ptrEnvironmentBlock, IntPtr in_ptrTokenHandle, bool in_bInheritProcessEnvironment );
26
27 [DllImport( "kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true )]
28 public static extern bool CloseHandle( IntPtr handle );
29
30 [DllImport( "wtsapi32.dll", EntryPoint = "WTSQueryUserToken", SetLastError = true )]
31 public static extern bool WTSQueryUserToken( UInt32 in_nSessionID, out IntPtr out_ptrTokenHandle );
32
33 [DllImport( "kernel32.dll", EntryPoint = "WTSGetActiveConsoleSessionId", SetLastError = true )]
34 public static extern uint WTSGetActiveConsoleSessionId();
35
36 [DllImport( "Wtsapi32.dll", EntryPoint = "WTSQuerySessionInformation", SetLastError = true )]
37 public static extern bool WTSQuerySessionInformation( IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out uint pBytesReturned );
38
39 [DllImport( "wtsapi32.dll", EntryPoint= "WTSFreeMemory", SetLastError = false )]
40 public static extern void WTSFreeMemory( IntPtr memory );
41
42 [DllImport( "userenv.dll", EntryPoint = "LoadUserProfile", SetLastError = true )]
43 public static extern bool LoadUserProfile( IntPtr hToken, ref PROFILEINFO lpProfileInfo );
44
45 [DllImport( "advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Auto )]
46 public static extern bool CreateProcessAsUser( IntPtr in_ptrUserTokenHandle, string in_strApplicationName, string in_strCommandLine, ref SECURITY_ATTRIBUTES in_oProcessAttributes, ref SECURITY_ATTRIBUTES in_oThreadAttributes, bool in_bInheritHandles, CreationFlags in_eCreationFlags, IntPtr in_ptrEnvironmentBlock, string in_strCurrentDirectory, ref STARTUPINFO in_oStartupInfo, ref PROCESS_INFORMATION in_oProcessInformation );
47
48 #endregion //Imports
49
50 /*** Delegates ***/
51
52 /*** Structs ***/
53 #region Structs
54
55 [StructLayout( LayoutKind.Sequential )]
56 public struct LUID
57 { 58 public uint m_nLowPart;
59 public uint m_nHighPart;
60 }
61
62 [StructLayout( LayoutKind.Sequential )]
63 public struct TOKEN_PRIVILEGES
64 { 65 public int m_nPrivilegeCount;
66 public LUID m_oLUID;
67 public int m_nAttributes;
68 }
69
70 [StructLayout( LayoutKind.Sequential )]
71 public struct PROFILEINFO
72 { 73 public int dwSize;
74 public int dwFlags;
75 [MarshalAs( UnmanagedType.LPTStr )]
76 public String lpUserName;
77 [MarshalAs( UnmanagedType.LPTStr )]
78 public String lpProfilePath;
79 [MarshalAs( UnmanagedType.LPTStr )]
80 public String lpDefaultPath;
81 [MarshalAs( UnmanagedType.LPTStr )]
82 public String lpServerName;
83 [MarshalAs( UnmanagedType.LPTStr )]
84 public String lpPolicyPath;
85 public IntPtr hProfile;
86 }
87
88 [StructLayout( LayoutKind.Sequential )]
89 public struct STARTUPINFO
90 { 91 public Int32 cb;
92 public string lpReserved;
93 public string lpDesktop;
94 public string lpTitle;
95 public Int32 dwX;
96 public Int32 dwY;
97 public Int32 dwXSize;
98 public Int32 dwXCountChars;
99 public Int32 dwYCountChars;
100 public Int32 dwFillAttribute;
101 public Int32 dwFlags;
102 public Int16 wShowWindow;
103 public Int16 cbReserved2;
104 public IntPtr lpReserved2;
105 public IntPtr hStdInput;
106 public IntPtr hStdOutput;
107 public IntPtr hStdError;
108 }
109
110 [StructLayout( LayoutKind.Sequential )]
111 public struct PROCESS_INFORMATION
112 { 113 public IntPtr hProcess;
114 public IntPtr hThread;
115 public Int32 dwProcessID;
116 public Int32 dwThreadID;
117 }
118
119 [StructLayout( LayoutKind.Sequential )]
120 public struct SECURITY_ATTRIBUTES
121 { 122 public Int32 Length;
123 public IntPtr lpSecurityDescriptor;
124 public bool bInheritHandle;
125 }
126
127 #endregion //Structs
128
129 /*** Classes ***/
130
131 /*** Enums ***/
132 #region Enums
133
134 public enum CreationFlags
135 { 136 CREATE_SUSPENDED = 0x00000004,
137 CREATE_NEW_CONSOLE = 0x00000010,
138 CREATE_NEW_PROCESS_GROUP = 0x00000200,
139 CREATE_UNICODE_ENVIRONMENT = 0x00000400,
140 CREATE_SEPARATE_WOW_VDM = 0x00000800,
141 CREATE_DEFAULT_ERROR_MODE = 0x04000000,
142 }
143
144 public enum WTS_INFO_CLASS
145 { 146 WTSInitialProgram,
147 WTSApplicationName,
148 WTSWorkingDirectory,
149 WTSOEMId,
150 WTSSessionId,
151 WTSUserName,
152 WTSWinStationName,
153 WTSDomainName,
154 WTSConnectState,
155 WTSClientBuildNumber,
156 WTSClientName,
157 WTSClientDirectory,
158 WTSClientProductId,
159 WTSClientHardwareId,
160 WTSClientAddress,
161 WTSClientDisplay,
162 WTSClientProtocolType
163 }
164
165 #endregion //Enums
166
167 /*** Defines ***/
168 #region Defines
169
170 private const int TOKEN_QUERY = 0x08;
171 private const int TOKEN_ADJUST_PRIVILEGES = 0x20;
172 private const int SE_PRIVILEGE_ENABLED = 0x02;
173
174 public const int ERROR_NO_TOKEN = 1008;
175 public const int RPC_S_INVALID_BINDING = 1702;
176
177 #endregion //Defines
178
179 /*** Methods ***/
180 #region Methods
181
182 /*
183 If you need to give yourself permissions to inspect processes for their modules,
184 and create tokens without worrying about what account you're running under,
185 this is the method for you :) (such as the token privilege "SeDebugPrivilege")
186 */
187 static public bool AdjustProcessTokenPrivileges( IntPtr in_ptrProcessHandle, string in_strTokenToEnable )
188 { 189 IntPtr l_hProcess = IntPtr.Zero;
190 IntPtr l_hToken = IntPtr.Zero;
191 LUID l_oRestoreLUID;
192 TOKEN_PRIVILEGES l_oTokenPrivileges;
193
194 Debug.Assert( in_ptrProcessHandle != IntPtr.Zero );
195
196 //Get the process security token
197 if( false == OpenProcessToken( in_ptrProcessHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out l_hToken ) )
198 { 199 return false;
200 }
201
202 //Lookup the LUID for the privilege we need
203 if( false == LookupPrivilegeValue( String.Empty, in_strTokenToEnable, out l_oRestoreLUID ) )
204 { 205 return false;
206 }
207
208 //Adjust the privileges of the current process to include the new privilege
209 l_oTokenPrivileges.m_nPrivilegeCount = 1;
210 l_oTokenPrivileges.m_oLUID = l_oRestoreLUID;
211 l_oTokenPrivileges.m_nAttributes = SE_PRIVILEGE_ENABLED;
212
213 if( false == AdjustTokenPrivileges( l_hToken, false, ref l_oTokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero ) )
214 { 215 return false;
216 }
217
218 return true;
219 }
220
221 /*
222 Start a process the simplest way you can imagine
223 */
224 static public int SimpleProcessStart( string in_strTarget, string in_strArguments )
225 { 226 Process l_oProcess = new Process();
227 Debug.Assert( l_oProcess != null );
228
229 l_oProcess.StartInfo.FileName = in_strTarget;
230 l_oProcess.StartInfo.Arguments = in_strArguments;
231
232 if( true == l_oProcess.Start() )
233 { 234 return l_oProcess.Id;
235 }
236
237 return -1;
238 }
239
240 /*
241 All the magic is in the call to WTSQueryUserToken, it saves you changing DACLs,
242 process tokens, pulling the SID, manipulating the Windows Station and Desktop
243 (and its DACLs) - if you don't know what those things are, you're lucky and should
244 be on your knees thanking God at this moment.
245
246 DEV NOTE: This method currently ASSumes that it should impersonate the user
247 who is logged into session 1 (if more than one user is logged in, each
248 user will have a session of their own which means that if user switching
249 is going on, this method could start a process whose UI shows up in
250 the session of the user who is not actually using the machine at this
251 moment.)
252
253 DEV NOTE 2: If the process being started is a binary which decides, based upon
254 the user whose session it is being created in, to relaunch with a
255 different integrity level (such as Internet Explorer), the process
256 id will change immediately and the Process Manager will think
257 that the process has died (because in actuality the process it
258 launched DID in fact die only that it was due to self-termination)
259 This means beware of using this service to startup such applications
260 although it can connect to them to alarm in case of failure, just
261 make sure you don't configure it to restart it or you'll get non
262 stop process creation ;)
263 */
264 static public int CreateUIProcessForServiceRunningAsLocalSystem( string in_strTarget, string in_strArguments )
265 { 266 PROCESS_INFORMATION l_oProcessInformation = new PROCESS_INFORMATION();
267 SECURITY_ATTRIBUTES l_oSecurityAttributes = new SECURITY_ATTRIBUTES();
268 STARTUPINFO l_oStartupInfo = new STARTUPINFO();
269 PROFILEINFO l_oProfileInfo = new PROFILEINFO();
270 IntPtr l_ptrUserToken = new IntPtr( 0 );
271 uint l_nActiveUserSessionId = 0xFFFFFFFF;
272 string l_strActiveUserName = "";
273 int l_nProcessID = -1;
274 IntPtr l_ptrBuffer = IntPtr.Zero;
275 uint l_nBytes = 0;
276
277 try
278 { 279 //The currently active user is running what session?
280 l_nActiveUserSessionId = WTSGetActiveConsoleSessionId();
281
282 if( l_nActiveUserSessionId == 0xFFFFFFFF )
283 { 284 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSGetActiveConsoleSessionId failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
285 }
286
287 if( false == WTSQuerySessionInformation( IntPtr.Zero, (int)l_nActiveUserSessionId, WTS_INFO_CLASS.WTSUserName, out l_ptrBuffer, out l_nBytes ) )
288 { 289 int l_nLastError = Marshal.GetLastWin32Error();
290
291 //On earlier operating systems from Vista, when no one is logged in, you get RPC_S_INVALID_BINDING which is ok, we just won't impersonate
292 if( l_nLastError != RPC_S_INVALID_BINDING )
293 { 294 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQuerySessionInformation failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
295 }
296
297 //No one logged in so let's just do this the simple way
298 return SimpleProcessStart( in_strTarget, in_strArguments );
299 }
300
301 l_strActiveUserName = Marshal.PtrToStringAnsi( l_ptrBuffer );
302 WTSFreeMemory( l_ptrBuffer );
303
304 //We are supposedly running as a service so we're going to be running in session 0 so get a user token from the active user session
305 if( false == WTSQueryUserToken( (uint)l_nActiveUserSessionId, out l_ptrUserToken ) )
306 { 307 int l_nLastError = Marshal.GetLastWin32Error();
308
309 //Remember, sometimes nobody is logged in (especially when we're set to Automatically startup) you should get error code 1008 (no user token available)
310 if( ERROR_NO_TOKEN != l_nLastError )
311 { 312 //Ensure we're running under the local system account
313 WindowsIdentity l_oIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
314
315 if( "NT AUTHORITY\\SYSTEM" != l_oIdentity.Name )
316 { 317 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQueryUserToken failed and querying the process' account identity results in an identity which does not match 'NT AUTHORITY\\SYSTEM' but instead returns the name:" + l_oIdentity.Name + " GetLastError returns: " + l_nLastError.ToString() );
318 }
319
320 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to WTSQueryUserToken failed, GetLastError returns: " + l_nLastError.ToString() );
321 }
322
323 //No one logged in so let's just do this the simple way
324 return SimpleProcessStart( in_strTarget, in_strArguments );
325 }
326
327 //Create an appropriate environment block for this user token (if we have one)
328 IntPtr l_ptrEnvironment = IntPtr.Zero;
329
330 Debug.Assert( l_ptrUserToken != IntPtr.Zero );
331
332 if( false == CreateEnvironmentBlock( out l_ptrEnvironment, l_ptrUserToken, false ) )
333 { 334 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to CreateEnvironmentBlock failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
335 }
336
337 l_oSecurityAttributes.Length = Marshal.SizeOf( l_oSecurityAttributes );
338 l_oStartupInfo.cb = Marshal.SizeOf( l_oStartupInfo );
339
340 //DO NOT set this to "winsta0\\default" (even though many online resources say to do so)
341 l_oStartupInfo.lpDesktop = String.Empty;
342 l_oProfileInfo.dwSize = Marshal.SizeOf( l_oProfileInfo );
343 l_oProfileInfo.lpUserName = l_strActiveUserName;
344
345 //Remember, sometimes nobody is logged in (especially when we're set to Automatically startup)
346 if( false == LoadUserProfile( l_ptrUserToken, ref l_oProfileInfo ) )
347 { 348 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to LoadUserProfile failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
349 }
350
351 if( false == CreateProcessAsUser( l_ptrUserToken, in_strTarget, String.Empty, ref l_oSecurityAttributes, ref l_oSecurityAttributes, false, CreationFlags.CREATE_UNICODE_ENVIRONMENT, l_ptrEnvironment, null, ref l_oStartupInfo, ref l_oProcessInformation ) )
352 { 353 //System.Diagnostics.EventLog.WriteEntry( "CreateProcessAsUser FAILED", Marshal.GetLastWin32Error().ToString() );
354 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "The call to CreateProcessAsUser failed, GetLastError returns: " + Marshal.GetLastWin32Error().ToString() );
355 }
356
357 l_nProcessID = l_oProcessInformation.dwProcessID;
358 }
359 catch( Exception l_oException )
360 { 361 throw new Exception( "ProcessUtilities" + "->" + MethodInfo.GetCurrentMethod().Name + "->" + "An unhandled exception was caught spawning the process, the exception was: " + l_oException.Message );
362 }
363 finally
364 { 365 if( l_oProcessInformation.hProcess != IntPtr.Zero )
366 { 367 CloseHandle( l_oProcessInformation.hProcess );
368 }
369 if( l_oProcessInformation.hThread != IntPtr.Zero )
370 { 371 CloseHandle( l_oProcessInformation.hThread );
372 }
373 }
374
375 return l_nProcessID;
376 }
377
378 #endregion //Methods
379 }
380 }