/// <summary> /// Creates a new process object the interactive desktop session. /// </summary> /// <param name="info"> /// The <see cref="WardenStartInfo"/> that contains the information that is used to start the process, /// including the PackageFamilyName, ApplicationId, and any command-line arguments. /// </param> /// <returns>A <see cref="WardenProcess"/> instance that is associated with the created process.</returns> internal static WardenProcess?AsUser(WardenStartInfo info) { var environmentBlockHandle = IntPtr.Zero; var startInfo = new StartupInfo(); var processInformation = new ProcessInformation(); startInfo.cb = Marshal.SizeOf <StartupInfo>(); try { if (!TryGetInteractiveUserToken(out var sessionId, out var interactiveUserToken)) { throw new Win32Exception("GetSessionUserToken failed."); } // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning // the window station has a desktop that is invisible and the process is incapable of receiving // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user // interaction with the new process. startInfo.wShowWindow = (short)SW.SW_SHOW; startInfo.lpDesktop = InteractiveWindowStation; using var registryHandle = LoadUserProfile((int)sessionId, interactiveUserToken, out _); if (registryHandle.IsInvalid) { throw new Win32Exception("LoadUserProfile failed."); } // copy the users env block if (!CreateEnvironmentBlock(ref environmentBlockHandle, interactiveUserToken, false)) { throw new Win32Exception("CreateEnvironmentBlock failed."); } const int creationFlags = CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS; if (!CreateProcessAsUser(interactiveUserToken, info.FileName, info.Arguments, null, null, false, creationFlags, environmentBlockHandle, info.WorkingDirectory, ref startInfo, out processInformation)) { throw new Win32Exception("CreateProcessAsUser failed"); } UnloadUserProfile(interactiveUserToken, registryHandle); return(WardenProcess.GetProcessById(processInformation.ProcessId, info.Track, info.FilteredImages)); } finally { DestroyEnvironmentBlock(environmentBlockHandle); CloseHandle(processInformation.ThreadHandle); CloseHandle(processInformation.ProcessHandle); CloseHandle(startInfo.hStdError); CloseHandle(startInfo.hStdInput); CloseHandle(startInfo.hStdOutput); CloseHandle(startInfo.lpReserved2); } }
/// <summary> /// Creates a new process object with the privileges of LocalSystem in the interactive desktop session. /// </summary> /// <param name="info"> /// The <see cref="WardenStartInfo"/> that contains the information that is used to start the process, /// including the PackageFamilyName, ApplicationId, and any command-line arguments. /// </param> /// <returns>A <see cref="WardenProcess"/> instance that is associated with the created process.</returns> internal static WardenProcess?AsLocalSystem(WardenStartInfo info) { var environmentBlockHandle = IntPtr.Zero; var startInfo = new StartupInfo(); var processInformation = new ProcessInformation(); startInfo.cb = Marshal.SizeOf <StartupInfo>(); try { if (!TryGetInteractiveUserToken(out var sessionId, out var interactiveUserToken)) { throw new Win32Exception("GetSessionUserToken failed."); } // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning // the window station has a desktop that is invisible and the process is incapable of receiving // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user // interaction with the new process. startInfo.wShowWindow = (short)SW.SW_SHOW; startInfo.lpDesktop = InteractiveWindowStation; using var registryHandle = LoadUserProfile((int)sessionId, interactiveUserToken, out _); if (registryHandle.IsInvalid) { throw new Win32Exception("LoadUserProfile failed."); } // copy the users env block if (!CreateEnvironmentBlock(ref environmentBlockHandle, interactiveUserToken, false)) { throw new Win32Exception("CreateEnvironmentBlock failed."); } var logonProcessId = GetWinLogonProcessId(sessionId); if (logonProcessId == 0) { throw new Win32Exception($"Unable to find the WinLogon process ID for session '{sessionId}'."); } using var processHandle = ProcessNative.OpenProcessHandle(ProcessNative.ProcessAccessFlags.MaximumAllowed, logonProcessId); if (processHandle.IsInvalid) { throw new Win32Exception("Unable to obtain a valid handle for winlogon.exe"); } if (!ProcessNative.OpenProcessToken(processHandle, TOKEN_ALL_ACCESS, out var processToken)) { throw new Win32Exception("Unable to open the process token for winlogon.exe"); } // ReSharper disable once UseObjectOrCollectionInitializer var securityAttributes = new SecurityAttributes(); securityAttributes.Length = Marshal.SizeOf <SecurityAttributes>(); // copy the access token of the explorer process; the newly created token will be a primary token if (!DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, securityAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TokenType.Primary, out var winLogonToken)) { throw new Win32Exception("Unable to duplicate the winlogon.exe process token"); } const int creationFlags = CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS; if (!CreateProcessAsUser(winLogonToken, info.FileName, info.Arguments, securityAttributes, securityAttributes, false, creationFlags, environmentBlockHandle, info.WorkingDirectory, ref startInfo, out processInformation)) { throw new Win32Exception($"CreateProcessAsUserW failed: {Marshal.GetLastWin32Error()}"); } UnloadUserProfile(interactiveUserToken, registryHandle); return(WardenProcess.GetProcessById(processInformation.ProcessId, info.Track, info.FilteredImages)); } finally { DestroyEnvironmentBlock(environmentBlockHandle); CloseHandle(processInformation.ThreadHandle); CloseHandle(processInformation.ProcessHandle); CloseHandle(startInfo.hStdError); CloseHandle(startInfo.hStdInput); CloseHandle(startInfo.hStdOutput); CloseHandle(startInfo.lpReserved2); } }