I know how to launch a process with Admin privileges from a process using:
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.Verb = "runas";
where proc is a System.Diagnostics.Process. But how does one do the opposite?
If the process you're in is already elevated, how do you launch the new process without admin privileges? More accurately, we need to launch the new process with the same permission level as Windows Explorer, so no change if UAC is disabled, but if UAC is enabled, but our process is running elevated, we need to perform a certain operation un-elevated because we're creating a virtual drive and if it's created with elevated permissions and Windows explorer is running unelevated it won't show up.
Feel free to change the title to something better, I couldn't come up with a good description.
The solution for you is to use EXPLORER.exe process.
The idea is to run the process in UN-ELEVATED mode, using windows's file explorer process explorer.exe (info).
Lets say the process that we want to launch is on $TEMP\MyUnElevatedProcess.exe.
So, for NSIS code, I will just write: (but can be run in ANY language)
Exec '"$WINDIR\explorer.exe" "$TEMP\MyUnElevatedProcess.exe"'
Example code (using NSIS installer)
Exec '"$WINDIR\explorer.exe" "$TEMP\MyUnElevatedProcess.exe"'
***code taken from http://mdb-blog.blogspot.com/2013/01/nsis-lunch-program-as-user-from-uac.html
We ended up using the sample from this Code Project article: High elevation can be bad for your application: How to start a non-elevated process at the end of the installation
It seems to work so far, I gather it injects into RunDll32.exe, my C++/Win32 is fairly weak so I didn't look too much into the actual implementation, just it's use. Confirmed that it works in Vista and Win7 both x86 and x64 (at least for us, x86 and x64 require different dll's which is checked for at install time and the proper one is used).
Raymond Chen addressed this in his blog:
How can I launch an unelevated process from my elevated process and vice versa?
Searching in GitHub for a C# version of this code, I found the following implementation in Microsoft's Node.js tools for Visual Studio repository: SystemUtilities.cs (the ExecuteProcessUnElevated function).
Just in case the file disappears, here's the file's contents:
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.NodejsTools.SharedProject
{
/// <summary>
/// Utility for accessing window IShell* interfaces in order to use them to launch a process unelevated
/// </summary>
internal class SystemUtility
{
/// <summary>
/// We are elevated and should launch the process unelevated. We can't create the
/// process directly without it becoming elevated. So to workaround this, we have
/// explorer do the process creation (explorer is typically running unelevated).
/// </summary>
internal static void ExecuteProcessUnElevated(string process, string args, string currentDirectory = "")
{
var shellWindows = (IShellWindows)new CShellWindows();
// Get the desktop window
object loc = CSIDL_Desktop;
object unused = new object();
int hwnd;
var serviceProvider = (IServiceProvider)shellWindows.FindWindowSW(ref loc, ref unused, SWC_DESKTOP, out hwnd, SWFO_NEEDDISPATCH);
// Get the shell browser
var serviceGuid = SID_STopLevelBrowser;
var interfaceGuid = typeof(IShellBrowser).GUID;
var shellBrowser = (IShellBrowser)serviceProvider.QueryService(ref serviceGuid, ref interfaceGuid);
// Get the shell dispatch
var dispatch = typeof(IDispatch).GUID;
var folderView = (IShellFolderViewDual)shellBrowser.QueryActiveShellView().GetItemObject(SVGIO_BACKGROUND, ref dispatch);
var shellDispatch = (IShellDispatch2)folderView.Application;
// Use the dispatch (which is unelevated) to launch the process for us
shellDispatch.ShellExecute(process, args, currentDirectory, string.Empty, SW_SHOWNORMAL);
}
/// <summary>
/// Interop definitions
/// </summary>
private const int CSIDL_Desktop = 0;
private const int SWC_DESKTOP = 8;
private const int SWFO_NEEDDISPATCH = 1;
private const int SW_SHOWNORMAL = 1;
private const int SVGIO_BACKGROUND = 0;
private readonly static Guid SID_STopLevelBrowser = new Guid("4C96BE40-915C-11CF-99D3-00AA004AE837");
[ComImport]
[Guid("9BA05972-F6A8-11CF-A442-00A0C90A8F39")]
[ClassInterfaceAttribute(ClassInterfaceType.None)]
private class CShellWindows
{
}
[ComImport]
[Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellWindows
{
[return: MarshalAs(UnmanagedType.IDispatch)]
object FindWindowSW([MarshalAs(UnmanagedType.Struct)] ref object pvarloc, [MarshalAs(UnmanagedType.Struct)] ref object pvarlocRoot, int swClass, out int pHWND, int swfwOptions);
}
[ComImport]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.Interface)]
object QueryService(ref Guid guidService, ref Guid riid);
}
[ComImport]
[Guid("000214E2-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellBrowser
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // InsertMenusSB
void VTableGap04(); // SetMenuSB
void VTableGap05(); // RemoveMenusSB
void VTableGap06(); // SetStatusTextSB
void VTableGap07(); // EnableModelessSB
void VTableGap08(); // TranslateAcceleratorSB
void VTableGap09(); // BrowseObject
void VTableGap10(); // GetViewStateStream
void VTableGap11(); // GetControlWindow
void VTableGap12(); // SendControlMsg
IShellView QueryActiveShellView();
}
[ComImport]
[Guid("000214E3-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellView
{
void VTableGap01(); // GetWindow
void VTableGap02(); // ContextSensitiveHelp
void VTableGap03(); // TranslateAcceleratorA
void VTableGap04(); // EnableModeless
void VTableGap05(); // UIActivate
void VTableGap06(); // Refresh
void VTableGap07(); // CreateViewWindow
void VTableGap08(); // DestroyViewWindow
void VTableGap09(); // GetCurrentInfo
void VTableGap10(); // AddPropertySheetPages
void VTableGap11(); // SaveViewState
void VTableGap12(); // SelectItem
[return: MarshalAs(UnmanagedType.Interface)]
object GetItemObject(UInt32 aspectOfView, ref Guid riid);
}
[ComImport]
[Guid("00020400-0000-0000-C000-000000000046")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IDispatch
{
}
[ComImport]
[Guid("E7A1AF80-4D96-11CF-960C-0080C7F4EE85")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
private interface IShellFolderViewDual
{
object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
}
[ComImport]
[Guid("A4C6892C-3BA9-11D2-9DEA-00C04FB16162")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IShellDispatch2
{
void ShellExecute([MarshalAs(UnmanagedType.BStr)] string File, [MarshalAs(UnmanagedType.Struct)] object vArgs, [MarshalAs(UnmanagedType.Struct)] object vDir, [MarshalAs(UnmanagedType.Struct)] object vOperation, [MarshalAs(UnmanagedType.Struct)] object vShow);
}
}
}
If you want to start an unelevated process from an elevated one you could copy the access token of the shell process and use it to start a new process.
public static class UnelevatedProcessStarter
{
public static void Start(string cmdArgs)
{
// 1. Get the shell
var shell = NativeMethods.GetShellWindow();
if (shell == IntPtr.Zero)
{
throw new Exception("Could not find shell window");
}
// 2. Copy the access token of the process
NativeMethods.GetWindowThreadProcessId(shell, out uint shellProcessId);
var hShellProcess = NativeMethods.OpenProcess(0x00000400 /* QueryInformation */, false, (int)shellProcessId);
if (!NativeMethods.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out IntPtr hShellToken))
{
throw new Win32Exception();
}
// 3. Duplicate the acess token
uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
var securityAttributes = new SecurityAttributes();
NativeMethods.DuplicateTokenEx(
hShellToken,
tokenAccess,
ref securityAttributes,
2 /* SecurityImpersonation */,
1 /* TokenPrimary */,
out IntPtr hToken);
// 4. Create a new process with the copied token
var si = new Startupinfo();
si.cb = Marshal.SizeOf(si);
if (!NativeMethods.CreateProcessWithTokenW(
hToken,
0x00000002 /* LogonNetcredentialsOnly */,
null,
cmdArgs,
0x00000010 /* CreateNewConsole */,
IntPtr.Zero,
null,
ref si,
out ProcessInformation _))
{
throw new Win32Exception();
}
}
public class NativeMethods
{
[DllImport("user32.dll")]
public static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenProcessToken(IntPtr processHandle, UInt32 desiredAccess, out IntPtr tokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess,
ref SecurityAttributes lpTokenAttributes,
int impersonationLevel,
int tokenType,
out IntPtr phNewToken);
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithTokenW(
IntPtr hToken, int dwLogonFlags,
string lpApplicationName, string lpCommandLine,
int dwCreationFlags, IntPtr lpEnvironment,
string lpCurrentDirectory, [In] ref Startupinfo lpStartupInfo, out ProcessInformation lpProcessInformation);
}
[StructLayout(LayoutKind.Sequential)]
public struct ProcessInformation
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SecurityAttributes
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Startupinfo
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
}
Based on #NtFreX I created another solution, that
takes the token from the current identity (seems to work and needs less code), - sorry, that doesn't always work
handles running from an already unelevated process (because in this case CreateProcessWithTokenW fails with error code 1314 - "A required privilege is not held by the client") and
returns the process id so that the calling code can await it etc.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class UnelevatedProcessStarter
{
public static int Start(string cmdArgs)
{
// 1. Get the shell
var shell = NativeMethods.GetShellWindow();
if (shell == IntPtr.Zero)
{
throw new Exception("Could not find shell window");
}
// 2. Copy the access token of the process
uint shellProcessId;
NativeMethods.GetWindowThreadProcessId(shell, out shellProcessId);
var hShellProcess = NativeMethods.OpenProcess(0x00000400 /* QueryInformation */, false, (int)shellProcessId);
IntPtr hShellToken;
if (!NativeMethods.OpenProcessToken(hShellProcess, 2 /* TOKEN_DUPLICATE */, out hShellToken))
{
throw new Win32Exception();
}
// 3. Duplicate the access token
uint tokenAccess = 8 /*TOKEN_QUERY*/ | 1 /*TOKEN_ASSIGN_PRIMARY*/ | 2 /*TOKEN_DUPLICATE*/ | 0x80 /*TOKEN_ADJUST_DEFAULT*/ | 0x100 /*TOKEN_ADJUST_SESSIONID*/;
var securityAttributes = new SecurityAttributes();
IntPtr hToken;
if (!NativeMethods.DuplicateTokenEx(
hShellToken,
tokenAccess,
ref securityAttributes,
2 /* SecurityImpersonation */,
1 /* TokenPrimary */,
out hToken))
{
throw new Win32Exception();
}
// 4. Create a new process with the copied token
var si = new Startupinfo();
si.cb = Marshal.SizeOf(si);
ProcessInformation processInfo;
if (!NativeMethods.CreateProcessWithTokenW(
hToken,
0x00000002 /* LogonNetcredentialsOnly */,
null,
cmdArgs,
0x00000010 /* CreateNewConsole */,
IntPtr.Zero,
null,
ref si,
out processInfo))
{
// Can't do that when not elevated (see https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw)
// -> start the process as usual
if (Marshal.GetLastWin32Error() == 1314)
{
SecurityAttributes processSecurityAttributes = new SecurityAttributes();
SecurityAttributes threadSecurityAttributes = new SecurityAttributes();
if (!NativeMethods.CreateProcessAsUser(
IntPtr.Zero,
null,
cmdArgs,
ref processSecurityAttributes,
ref threadSecurityAttributes,
true,
0x00000010 /* CreateNewConsole */,
IntPtr.Zero,
null,
ref si,
out processInfo))
{
throw new Win32Exception();
}
}
else
{
throw new Win32Exception();
}
}
return processInfo.dwProcessId;
}
public class NativeMethods
{
[DllImport("user32.dll")]
public static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(int processAccess, bool bInheritHandle, int processId);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenProcessToken(IntPtr processHandle, UInt32 desiredAccess, out IntPtr tokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess,
ref SecurityAttributes lpTokenAttributes,
int impersonationLevel,
int tokenType,
out IntPtr phNewToken);
[DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CreateProcessWithTokenW(
IntPtr hToken, int dwLogonFlags,
string lpApplicationName, string lpCommandLine,
int dwCreationFlags, IntPtr lpEnvironment,
string lpCurrentDirectory,
[In] ref Startupinfo lpStartupInfo,
out ProcessInformation lpProcessInformation);
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SecurityAttributes lpProcessAttributes,
ref SecurityAttributes lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref Startupinfo lpStartupInfo,
out ProcessInformation lpProcessInformation);
}
[StructLayout(LayoutKind.Sequential)]
public struct ProcessInformation
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SecurityAttributes
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct Startupinfo
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
}
You can use ProcessStartInfo.UserName and ProcessStartInfo.Password to specify the account you want your process to run under.
class Program
{
static void Main(string[] args)
{
var psi = new ProcessStartInfo(#"c:\windows\system32\whoami.exe");
var password = new SecureString();
password.AppendChar('s');
password.AppendChar('e');
password.AppendChar('c');
password.AppendChar('r');
password.AppendChar('e');
password.AppendChar('t');
psi.Password = password;
psi.UserName = "username";
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
var p = new Process();
p.StartInfo = psi;
p.Start();
p.WaitForExit();
Console.WriteLine(p.StandardOutput.ReadToEnd());
}
}
Related
I am trying to launch an appcontainer using C# and pinvoke and getting hung up setting the security capabilities into the attribute list via UpdateProcThreadAttribute().
The GetLastError() returns
87 - invalid parameter
Similar c++ code from this article (github link at the bottom https://scorpiosoftware.net/2019/01/15/fun-with-appcontainers/) works well.
Code: (pinvoke structs/enums/externs below)
public bool testAppContainer()
{
const uint SE_GROUP_ENABLED = 0x00000004;
const int ProcThreadAttributeSecurityCapabilities = 0x00020009;
STARTUPINFOEX startupInfoEx = new STARTUPINFOEX();
PROCESSINFO pInfo = new PROCESSINFO();
SECURITY_CAPABILITIES sc = new SECURITY_CAPABILITIES();
IntPtr pSidInternetClientServer = IntPtr.Zero;
startupInfoEx.StartupInfo.cb = Marshal.SizeOf(startupInfoEx);
string appContainerName = "Test.AppContainer";
try
{
//get the size, then the sid
IntPtr clientServerSid = IntPtr.Zero; uint cbSid = 0;
SecurityNative.CreateWellKnownSid(SecurityNative.WELL_KNOWN_SID_TYPE.WinCapabilityInternetClientServerSid, clientServerSid, pSidInternetClientServer, ref cbSid);
pSidInternetClientServer = Marshal.AllocCoTaskMem(Convert.ToInt32(cbSid));
if (!CreateWellKnownSid(WELL_KNOWN_SID_TYPE.WinCapabilityInternetClientServerSid, clientServerSid, pSidInternetClientServer, ref cbSid))
{
throw new ApplicationException($"Unable to create well known Client Server capability sid!");
}
//create an array of one capability
SID_AND_ATTRIBUTES[] sidAndAttributes = new SID_AND_ATTRIBUTES[1];
sidAndAttributes[0].Sid = pSidInternetClientServer;
sidAndAttributes[0].Attributes = SE_GROUP_ENABLED;
sc.Capabilities = sidAndAttributes;
sc.CapabilityCount = 1;
IntPtr appConSid = IntPtr.Zero;
int hResult = AppContainerNative.DeriveAppContainerSidFromAppContainerName(appContainerName, out appConSid);
if (hResult < 0)
{
throw new ApplicationException($"Application container {appContainerName} was not found on the machine (err code {hResult})");
}
//security capabilities for existing app container
sc.AppContainerSid = appConSid;
const int reserved = 0; var size = IntPtr.Zero; int attributeCount = 1;
bool alreadyInit = ProcessNative.InitializeProcThreadAttributeList(IntPtr.Zero, attributeCount, reserved, ref size);
if (alreadyInit || size == IntPtr.Zero)
{
throw new Exception(string.Format("Couldn't get the size of the attribute list for {0} attributes", attributeCount));
}
startupInfoEx.lpAttributeList = Marshal.AllocHGlobal(size);
if (startupInfoEx.lpAttributeList == IntPtr.Zero)
{
throw new Exception("Couldn't reserve space for a new attribute list");
}
bool initAttributeList = ProcessNative.InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, attributeCount, reserved, ref size);
if (!initAttributeList )
{
throw new Exception("Couldn't create new attribute list");
}
//also tried this (pass in unmanagedAddr instead of ref sc)
// IntPtr unmanagedAddr = Marshal.AllocHGlobal(Marshal.SizeOf(sc));
// Marshal.StructureToPtr(sc, unmanagedAddr, true);
bool success = UpdateProcThreadAttribute(startupInfoEx.lpAttributeList, reserved, (IntPtr)ProcThreadAttributeSecurityCapabilities,
ref sc, (IntPtr)Marshal.SizeOf(sc), IntPtr.Zero, IntPtr.Zero);
// Marshal.FreeHGlobal(unmanagedAddr);
// unmanagedAddr = IntPtr.Zero;
if (!success)
{
throw new Exception($"Error adding security capabilities to process launch. Error Code: {Marshal.GetLastWin32Error()}");
}
var pSec = new SECURITY_ATTRIBUTES(); pSec.nLength = Marshal.SizeOf(pSec);
var tSec = new SECURITY_ATTRIBUTES(); tSec.nLength = Marshal.SizeOf(tSec);
success = CreateProcess(null, #"c:\windows\notepad.exe", ref pSec, ref tSec, false,
(uint)ProcessNative.CreateProcessFlags.EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref startupInfoEx, out pInfo);
if (success)
{
System.Diagnostics.Debug.WriteLine($"Created new app container process {pInfo.dwProcessId}!");
return true;
}
}
finally
{
// Free the attribute list
if (startupInfoEx.lpAttributeList != IntPtr.Zero)
{
ProcessNative.DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList);
Marshal.FreeHGlobal(startupInfoEx.lpAttributeList);
}
// Close process and thread handles
if (pInfo.hProcess != IntPtr.Zero)
{
ProcessNative.CloseHandle(pInfo.hProcess);
}
if (pInfo.hThread != IntPtr.Zero)
{
ProcessNative.CloseHandle(pInfo.hThread);
}
Marshal.FreeHGlobal(startupInfoEx.lpAttributeList);
if (pSidInternetClientServer != IntPtr.Zero)
Marshal.FreeCoTaskMem(pSidInternetClientServer);
}
return true;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_CAPABILITIES
{
public IntPtr AppContainerSid;
[MarshalAs(UnmanagedType.ByValArray)]
public SID_AND_ATTRIBUTES[] Capabilities;
public uint CapabilityCount;
public uint Reserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct SID_AND_ATTRIBUTES
{
public IntPtr Sid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct PROCESSINFO
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessId;
public Int32 dwThreadId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public enum WELL_KNOWN_SID_TYPE
{
WinNullSid,
WinWorldSid,
WinLocalSid,
WinCreatorOwnerSid,
WinCreatorGroupSid,
WinCreatorOwnerServerSid,
WinCreatorGroupServerSid,
WinNtAuthoritySid,
WinDialupSid,
WinNetworkSid,
WinBatchSid,
WinInteractiveSid,
WinServiceSid,
WinAnonymousSid,
WinProxySid,
WinEnterpriseControllersSid,
WinSelfSid,
WinAuthenticatedUserSid,
WinRestrictedCodeSid,
WinTerminalServerSid,
WinRemoteLogonIdSid,
WinLogonIdsSid,
WinLocalSystemSid,
WinLocalServiceSid,
WinNetworkServiceSid,
WinBuiltinDomainSid,
WinBuiltinAdministratorsSid,
WinBuiltinUsersSid,
WinBuiltinGuestsSid,
WinBuiltinPowerUsersSid,
WinBuiltinAccountOperatorsSid,
WinBuiltinSystemOperatorsSid,
WinBuiltinPrintOperatorsSid,
WinBuiltinBackupOperatorsSid,
WinBuiltinReplicatorSid,
WinBuiltinPreWindows2000CompatibleAccessSid,
WinBuiltinRemoteDesktopUsersSid,
WinBuiltinNetworkConfigurationOperatorsSid,
WinAccountAdministratorSid,
WinAccountGuestSid,
WinAccountKrbtgtSid,
WinAccountDomainAdminsSid,
WinAccountDomainUsersSid,
WinAccountDomainGuestsSid,
WinAccountComputersSid,
WinAccountControllersSid,
WinAccountCertAdminsSid,
WinAccountSchemaAdminsSid,
WinAccountEnterpriseAdminsSid,
WinAccountPolicyAdminsSid,
WinAccountRasAndIasServersSid,
WinNTLMAuthenticationSid,
WinDigestAuthenticationSid,
WinSChannelAuthenticationSid,
WinThisOrganizationSid,
WinOtherOrganizationSid,
WinBuiltinIncomingForestTrustBuildersSid,
WinBuiltinPerfMonitoringUsersSid,
WinBuiltinPerfLoggingUsersSid,
WinBuiltinAuthorizationAccessSid,
WinBuiltinTerminalServerLicenseServersSid,
WinBuiltinDCOMUsersSid,
WinBuiltinIUsersSid,
WinIUserSid,
WinBuiltinCryptoOperatorsSid,
WinUntrustedLabelSid,
WinLowLabelSid,
WinMediumLabelSid,
WinHighLabelSid,
WinSystemLabelSid,
WinWriteRestrictedCodeSid,
WinCreatorOwnerRightsSid,
WinCacheablePrincipalsGroupSid,
WinNonCacheablePrincipalsGroupSid,
WinEnterpriseReadonlyControllersSid,
WinAccountReadonlyControllersSid,
WinBuiltinEventLogReadersGroup,
WinNewEnterpriseReadonlyControllersSid,
WinBuiltinCertSvcDComAccessGroup,
WinMediumPlusLabelSid,
WinLocalLogonSid,
WinConsoleLogonSid,
WinThisOrganizationCertificateSid,
WinApplicationPackageAuthoritySid,
WinBuiltinAnyPackageSid,
WinCapabilityInternetClientSid,
WinCapabilityInternetClientServerSid,
WinCapabilityPrivateNetworkClientServerSid,
WinCapabilityPicturesLibrarySid,
WinCapabilityVideosLibrarySid,
WinCapabilityMusicLibrarySid,
WinCapabilityDocumentsLibrarySid,
WinCapabilitySharedUserCertificatesSid,
WinCapabilityEnterpriseAuthenticationSid,
WinCapabilityRemovableStorageSid,
WinBuiltinRDSRemoteAccessServersSid,
WinBuiltinRDSEndpointServersSid,
WinBuiltinRDSManagementServersSid,
WinUserModeDriversSid,
WinBuiltinHyperVAdminsSid,
WinAccountCloneableControllersSid,
WinBuiltinAccessControlAssistanceOperatorsSid,
WinBuiltinRemoteManagementUsersSid,
WinAuthenticationAuthorityAssertedSid,
WinAuthenticationServiceAssertedSid,
WinLocalAccountSid,
WinLocalAccountAndAdministratorSid,
WinAccountProtectedUsersSid,
WinCapabilityAppointmentsSid,
WinCapabilityContactsSid,
WinAccountDefaultSystemManagedSid,
WinBuiltinDefaultSystemManagedGroupSid,
WinBuiltinStorageReplicaAdminsSid,
WinAccountKeyAdminsSid,
WinAccountEnterpriseKeyAdminsSid,
WinAuthenticationKeyTrustSid,
WinAuthenticationKeyPropertyMFASid,
WinAuthenticationKeyPropertyAttestationSid,
WinAuthenticationFreshKeyAuthSid,
WinBuiltinDeviceOwnersSid
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool CreateWellKnownSid(WELL_KNOWN_SID_TYPE WellKnownSidType, IntPtr DomainSid, IntPtr pSid, ref uint cbSid);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment,
string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, out PROCESSINFO lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, [MarshalAs(UnmanagedType.Struct), In] ref SECURITY_CAPABILITIES caps, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void DeleteProcThreadAttributeList(IntPtr lpAttributeList);
Wow, first off, this is some great work and I'm curious how you are leveraging it (CICD? Security?). As a challenge I played around with the code and noticed that your declaration for the SECURITY_CAPABILITIES structure wasn't matching with the Microsoft documentation. They list the Capabilities member as a pointer to the SID_AND_ATTRIBUTES structure, not a structure itself. So, I changed that to IntPtr making it...
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_CAPABILITIES
{
public IntPtr AppContainerSid;
public IntPtr Capabilities;
public uint CapabilityCount;
public uint Reserved;
}
I'm not sure how to marshal an array of structs to an IntPtr so for now my example below is the single capability you listed.
//create one capability
SID_AND_ATTRIBUTES sidAndAttributes = new SID_AND_ATTRIBUTES();
sidAndAttributes.Sid = pSidInternetClientServer;
sidAndAttributes.Attributes = SE_GROUP_ENABLED;
IntPtr sidAndAttributesPtr = Marshal.AllocHGlobal(Marshal.SizeOf(sidAndAttributes));
Marshal.StructureToPtr(sidAndAttributes, sidAndAttributesPtr, false);
sc.Capabilities = sidAndAttributesPtr;
sc.CapabilityCount = 1;
This allowed notepad.exe to launch with AppContainer integrity and containing the capability flag you specified.
Thank you for the reply! I did get this to work. Your suggestion did help. This does actually launch a notepad.exe in an app container with two capabilities. Here is the code for the marshal for all that want to try this. My objective is to containerize some programs that we are using to protect the servers as much as possible.
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_CAPABILITIES
{
public IntPtr AppContainerSid;
public IntPtr Capabilities;
public int CapabilityCount;
public int Reserved;
}
private void AddDefaultCapabilitiesSid(ref SECURITY_CAPABILITIES sc)
{
//get the size, then the sid
IntPtr mem = IntPtr.Zero;
IntPtr clientServerSid = IntPtr.Zero;
IntPtr privateNetworkSid = IntPtr.Zero;
IntPtr pSid = IntPtr.Zero;
uint cbSid = 0;
//get the size, then the sid
SecurityNative.CreateWellKnownSid(SecurityNative.WELL_KNOWN_SID_TYPE.WinCapabilityInternetClientServerSid, pSid, clientServerSid, ref cbSid);
clientServerSid = Marshal.AllocCoTaskMem(Convert.ToInt32(cbSid));
if (!SecurityNative.CreateWellKnownSid(SecurityNative.WELL_KNOWN_SID_TYPE.WinCapabilityInternetClientServerSid, pSid, clientServerSid, ref cbSid))
{
throw new ApplicationException($"Unable to create well known Client Server capability sid!");
}
//get the size, then the sid
SecurityNative.CreateWellKnownSid(SecurityNative.WELL_KNOWN_SID_TYPE.WinCapabilityPrivateNetworkClientServerSid, pSid, privateNetworkSid, ref cbSid);
privateNetworkSid = Marshal.AllocCoTaskMem(Convert.ToInt32(cbSid));
if (!SecurityNative.CreateWellKnownSid(SecurityNative.WELL_KNOWN_SID_TYPE.WinCapabilityPrivateNetworkClientServerSid, pSid, privateNetworkSid, ref cbSid))
{
throw new ApplicationException($"Unable to create well known Client Server capability sid!");
}
SecurityNative.SID_AND_ATTRIBUTES[] sidAndAttributes = new SecurityNative.SID_AND_ATTRIBUTES[2];
sidAndAttributes[0].Sid = clientServerSid;
sidAndAttributes[0].Attributes = SE_GROUP_ENABLED;
sidAndAttributes[1].Sid = privateNetworkSid;
sidAndAttributes[1].Attributes = SE_GROUP_ENABLED;
int arraySize = sidAndAttributes.Length;
int elemSize = Marshal.SizeOf(typeof(SecurityNative.SID_AND_ATTRIBUTES));
IntPtr result = Marshal.AllocHGlobal(elemSize * arraySize);
mem = new IntPtr(result.ToInt64());
for (int i = 0; i < arraySize; i++)
{
IntPtr ptr = new IntPtr(result.ToInt64() + elemSize * i);
Marshal.StructureToPtr(sidAndAttributes[i], ptr, false);
MemToFree.Add(ptr); //free this mem later
}
sc.Capabilities = mem;
sc.CapabilityCount = arraySize;
}
I want to start a non-elevated process from an elevated one.
I've accomplished this using SaferComputeTokenFromLevel. It appears to be working,since:
C:\>whoami /groups | findstr BUILTIN\Administrators
BUILTIN\Administrators Alias S-1-5-32-544 Group used for deny only
Deny only is there. So I am not admin.
1) Why the (supodsedly) non-admin process Console Host Window shows as "Administrator:" in the title?
I even used SetTokenInformation to set it to medium integrity. It works but still showing as 'Administrator' even if it's not admin.
Then, if that non-admin process wants to start an elevated process again via ShellExecute with Verb=RunAs (with powershell -C Start-Process -Verb RunAs -FilePath CMD.EXE) the UAC popup doesn't appears, and the process is started but not elevated, not-admin.
2) Why does this happens? (linked token maybe?)
3) Finally, how can I launch a non-elevated process, that can use the RunAs verb to trigger an UAC and elevate succesfully?
Full Repro code (Run as admin!)
using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.InteropServices;
namespace SaferRepro
{
class Program
{
static void Main(string[] args)
{
string appToRun = "CMD.EXE";
string arguments = String.Empty;
bool newWindow = true, hidden = false;
var startupFolder = Environment.CurrentDirectory;
int mediumIntegrity = 8192;
using (var newToken = GetTokenFromSaferApi())
{
AdjustedTokenIntegrity(newToken, mediumIntegrity); // optional, launch as medium integrity process.
var process = StartWithToken(newToken, appToRun, arguments, startupFolder, newWindow, hidden);
GetProcessWaitHandle(process.DangerousGetHandle()).WaitOne();
}
}
private static SafeTokenHandle GetTokenFromSaferApi()
{
IntPtr hSaferLevel;
SafeTokenHandle hToken;
SaferLevels level = SaferLevels.NormalUser;
if (!NativeMethods.SaferCreateLevel(SaferScopes.User, level, 1, out hSaferLevel, IntPtr.Zero))
throw new Win32Exception();
if (!NativeMethods.SaferComputeTokenFromLevel(hSaferLevel, IntPtr.Zero, out hToken, SaferComputeTokenFlags.None, IntPtr.Zero))
throw new Win32Exception();
if (!NativeMethods.SaferCloseLevel(hSaferLevel))
throw new Win32Exception();
return hToken;
}
private static SafeProcessHandle StartWithToken(SafeTokenHandle newToken, string appToRun, string args, string startupFolder, bool newWindow, bool hidden)
{
var si = new STARTUPINFO();
if (newWindow)
{
si.dwFlags = 0x00000001; // STARTF_USESHOWWINDOW
si.wShowWindow = (short)(hidden ? 0 : 1);
}
si.cb = Marshal.SizeOf(si);
var pi = new PROCESS_INFORMATION();
uint dwCreationFlags = newWindow ? (uint)0x00000010 /*CREATE_NEW_CONSOLE*/: 0;
if (!NativeMethods.CreateProcessAsUser(newToken, null, $"{appToRun} {args}",
IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags, IntPtr.Zero, startupFolder, ref si,
out pi))
{
throw new Win32Exception();
}
NativeMethods.CloseHandle(pi.hThread);
return new SafeProcessHandle(pi.hProcess, true);
}
private static bool AdjustedTokenIntegrity(SafeTokenHandle newToken, int integrityLevel)
{
string integritySid = "S-1-16-" + (integrityLevel.ToString(CultureInfo.InvariantCulture));
IntPtr pIntegritySid;
if (!NativeMethods.ConvertStringSidToSid(integritySid, out pIntegritySid))
return false;
TOKEN_MANDATORY_LABEL TIL = new TOKEN_MANDATORY_LABEL();
TIL.Label.Attributes = 0x00000020 /* SE_GROUP_INTEGRITY */;
TIL.Label.Sid = pIntegritySid;
var pTIL = Marshal.AllocHGlobal(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>());
Marshal.StructureToPtr(TIL, pTIL, false);
if (!NativeMethods.SetTokenInformation(newToken.DangerousGetHandle(),
TOKEN_INFORMATION_CLASS.TokenIntegrityLevel,
pTIL,
(uint)(Marshal.SizeOf<TOKEN_MANDATORY_LABEL>() + NativeMethods.GetLengthSid(pIntegritySid))))
return false;
return true;
}
public static System.Threading.AutoResetEvent GetProcessWaitHandle(IntPtr processHandle) =>
new System.Threading.AutoResetEvent(false)
{
SafeWaitHandle = new SafeWaitHandle(processHandle, ownsHandle: false)
};
}
internal class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
internal SafeTokenHandle(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
private SafeTokenHandle() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.CloseHandle(base.handle);
}
}
static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CreateProcessAsUser(
SafeTokenHandle hToken,
string applicationName,
string commandLine,
IntPtr pProcessAttributes,
IntPtr pThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr pEnvironment,
string currentDirectory,
ref STARTUPINFO startupInfo,
out PROCESS_INFORMATION processInformation);
#region Safer
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCreateLevel(
SaferScopes dwScopeId,
SaferLevels dwLevelId,
int OpenFlags,
out IntPtr pLevelHandle,
IntPtr lpReserved);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferCloseLevel(
IntPtr pLevelHandle);
[DllImport("advapi32", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool SaferComputeTokenFromLevel(
IntPtr levelHandle,
IntPtr inAccessToken,
out SafeTokenHandle outAccessToken,
SaferComputeTokenFlags dwFlags,
IntPtr lpReserved
);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid
);
[DllImport("advapi32.dll")]
public static extern int GetLengthSid(IntPtr pSid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern Boolean SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,
IntPtr TokenInformation, UInt32 TokenInformationLength);
#endregion
}
[Flags]
public enum SaferLevels : uint
{
Disallowed = 0,
Untrusted = 0x1000,
Constrained = 0x10000,
NormalUser = 0x20000,
FullyTrusted = 0x40000
}
[Flags]
public enum SaferComputeTokenFlags : uint
{
None = 0x0,
NullIfEqual = 0x1,
CompareOnly = 0x2,
MakeIntert = 0x4,
WantFlags = 0x8
}
[Flags]
public enum SaferScopes : uint
{
Machine = 1,
User = 2
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOKEN_MANDATORY_LABEL
{
public SID_AND_ATTRIBUTES Label;
}
[StructLayout(LayoutKind.Sequential)]
public struct SID_AND_ATTRIBUTES
{
public IntPtr Sid;
public uint Attributes;
}
// Integrity Levels
public enum TOKEN_INFORMATION_CLASS
{
TokenUser = 1, TokenGroups, TokenPrivileges, TokenOwner, TokenPrimaryGroup, TokenDefaultDacl, TokenSource, TokenType, TokenImpersonationLevel, TokenStatistics, TokenRestrictedSids, TokenSessionId, TokenGroupsAndPrivileges, TokenSessionReference, TokenSandBoxInert, TokenAuditPolicy, TokenOrigin, TokenElevationType, TokenLinkedToken, TokenElevation, TokenHasRestrictions, TokenAccessInformation, TokenVirtualizationAllowed, TokenVirtualizationEnabled, TokenIntegrityLevel, TokenUIAccess, TokenMandatoryPolicy, TokenLogonSid, MaxTokenInfoClass
}
}
When your a token is elevated, it seems it can't be altered to unflag it's elevated flag status it. You can remove membership from Local Admins (actually set it as Deny Only), or set Integrity to Medium, but it will still be flagged as elevated (even without admin privileges). With this flag set, the RunAs Verb will not elevate.
Your options are:
Get explorer.exe token and assume it's not elevated. (its elevated if UAC is disabled or if the user .
If UAC is enabled, the elevated token has a link to the unelevated token that you can fetch with GetTokenInformation with TOKEN_INFORMATION_CLASS.TokenLinkedToken.
If UAC is disabled there is no linked token. My question doesn't really apply, since the UAC popup is disabled, will never show. FYI you can use SaferApi to create a non-admin token with the elevated flag set, but it's RunAs attempts will fail.
If you want to see how I coded this, check gsudo (a sudo for windows) that I am working on:
https://github.com/gerardog/gsudo/blob/dev/src/gsudo/Tokens/TokenManager.cs
https://github.com/gerardog/gsudo/blob/dev/src/gsudo/Helpers/ProcessFactory.cs#L182
or check this other great answer
In a Windows Service, I'm trying to use a method from an external DLL that displays a UI (it doesn't request any user input). I read that you can't interact with the UI from a Windows Service (since Vista), because it runs in a different session than the user (which is the only one who can interact with the UI). But it is posible to start an interactive process from a service, as shown in this article, I tested it and it works.
So I thought that maybe I can call the method of that DLL in a similar way, by using the session of the active user, and then get the returned data back on the service... any guide or example of how to do this?
I also tried checking the "Allow system to interact with desktop" option on the "Log On" tab of the installed service and starting the service with the user account of the currently logged in user, but it didn't work, it doesn't show any error, but it should display a window and it's not showing anything.
You cannot just call UI from windows service as it's running in the 0 session. For calling UI you need to change session to your current user's session. Here is link how you can do it
https://www.codeproject.com/kb/vista-security/subvertingvistauac.aspx
You have read already and know about how service session(0) doesn't allow you to invoke anything on UI that as it run on different session.
I once used an API wrapper around PInvoke and It has a method called 'CreateProcessAsUser'. What it does is, it gets the token of current logging user session and launch a process using that session. In order to launch the process there should be an active desktop session running.
Here's the complete code for that:https://gist.github.com/vendettamit/a518bceef3678963c5fd
It's kinda hack but it also a great work around. You can launch your third party method via your own process wrapper.
Usually windows service cannot interact with the UI because UI will be running under user session and the service will be running under system. so you need to perform the impersonation and launch the Application.
using System.Runtime.InteropServices;
using System;
using System.Diagnostics;
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public uint 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;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public class ProcessAsUser
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(
IntPtr hObject);
private const short SW_SHOW = 5;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const int GENERIC_ALL_ACCESS = 0x10000000;
private const int STARTF_USESHOWWINDOW = 0x00000001;
private const int STARTF_FORCEONFEEDBACK = 0x00000040;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = #"WinSta0\Default"; //Modify as needed
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
Debug.WriteLine(message);
}
return result;
}
private static IntPtr GetPrimaryToken(int processId)
{
IntPtr token = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
bool retVal = false;
Process p = null;
try
{
p = Process.GetProcessById(processId);
}
catch (ArgumentException)
{
string details = String.Format("ProcessID {0} Not Available", processId);
Debug.WriteLine(details);
throw;
}
//Gets impersonation token
retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
if (retVal == true)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = (uint)Marshal.SizeOf(sa);
//Convert the impersonation token into Primary token
retVal = DuplicateTokenEx(
token,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref primaryToken);
//Close the Token that was previously opened.
CloseHandle(token);
if (retVal == false)
{
string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
}
else
{
string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
//We'll Close this token after it is used.
return primaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr token)
{
IntPtr envBlock = IntPtr.Zero;
bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
if (retVal == false)
{
//Environment Block, things like common paths to My Documents etc.
//Will not be created if "false"
//It should not adversley affect CreateProcessAsUser.
string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
Debug.WriteLine(message);
}
return envBlock;
}
public static bool Launch(string appCmdLine /*,int processId*/)
{
bool ret = false;
//Either specify the processID explicitly
//Or try to get it from a process owned by the user.
//In this case assuming there is only one explorer.exe
Process[] ps = Process.GetProcessesByName("explorer");
int processId = -1;//=processId
if (ps.Length > 0)
{
processId = ps[0].Id;
}
if (processId > 1)
{
IntPtr token = GetPrimaryToken(processId);
if (token != IntPtr.Zero)
{
IntPtr envBlock = GetEnvironmentBlock(token);
ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
if (envBlock != IntPtr.Zero)
DestroyEnvironmentBlock(envBlock);
CloseHandle(token);
}
}
return ret;
}
}
Assuming that the application would be executed by administrator user on Windows; is there a way to use Windows API to retrieve window titles and detect if application is in foreground from all logged in users?
Trying following code we can't retrieve titles from applications other logged in users are running.
// to get the processes
Process[] processlist = Process.GetProcesses();
// and this to get the title:
process.MainWindowTitle
You need to use getforegroundwindow function from winapi. But it is just one problem. It must be executed in context of user session. It means you must enumerate all user sessions in computer, after take user token, and by using this user token, execute secondary process in user session by CreateProcessAsUser. This secondary process can execute getforegroundwindow, and you must implement any ipc method to return information back to your main application. I used this code in windows service, to execute small programm in context of user session:
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUserW", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
UInt32 dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern int WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);
[DllImport("wtsapi32.dll")]
static extern void WTSCloseServer(IntPtr hServer);
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern Int32 WTSEnumerateSessions(
IntPtr hServer,
[MarshalAs(UnmanagedType.U4)] Int32 Reserved,
[MarshalAs(UnmanagedType.U4)] Int32 Version,
ref IntPtr ppSessionInfo,
[MarshalAs(UnmanagedType.U4)] ref Int32 pCount);
[DllImport("wtsapi32.dll")]
static extern void WTSFreeMemory(IntPtr pMemory);
[StructLayout(LayoutKind.Sequential)]
private struct WTS_SESSION_INFO
{
public Int32 SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public String pWinStationName;
public WTS_CONNECTSTATE_CLASS State;
}
public enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
public static IntPtr OpenServer(String Name)
{
IntPtr server = WTSOpenServer(Name);
return server;
}
public static void CloseServer(IntPtr ServerHandle)
{
WTSCloseServer(ServerHandle);
}
public static List<uint> ListSessions(String ServerName)
{
IntPtr server = IntPtr.Zero;
List<uint> ret = new List<uint>();
server = OpenServer(ServerName);
try
{
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int64 current = (int)ppSessionInfo;
if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
current += dataSize;
ret.Add((uint)si.SessionID);
}
WTSFreeMemory(ppSessionInfo);
}
}
finally
{
CloseServer(server);
}
return ret;
}
protected override void OnStart(string[] args)
{
List<uint> retVal = ListSessions("COMPUTER_NAME");
for (int i = 0; i < retVal.Count; i++)
{
IntPtr userToken = new IntPtr();
WTSQueryUserToken(retVal[i], out userToken);
STARTUPINFO info = new STARTUPINFO();
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
CreateProcessAsUser(userToken, null, #"c:\windows\notepad.exe",
IntPtr.Zero, IntPtr.Zero, false, (UInt32)0, IntPtr.Zero, null,
ref info, out procInfo);
}
}
This code is for windows service, and entry point is OnStart() function. In windows, SYSTEM account have all necessary permission to execute this code. I did not check it in Admin context, but probably it will work also.
This code will just execute notepad.exe in all user sessions. You must make you programm, with getforegroundwindow and some sort of interprocess communication
I currently have a windows service that is running under the System Account. My problem is that i need to start certain processes from within the service as the current logged on user. I have all the code etc to get the current logged on user / Active session.
My problem is that i need spawn a process as the logged on user but will not know the user credentials etc.
The service is .net compiled service and i expect that i need to use some Pinvoke methods to get a handle of one of the current users process in order to duplicate it and lunch as process with the handle.
Unfortunately i cannot find any good documentation / solution on how to implement it?
If someone is able to give me some guidance / example i would highly appreciate it.
* Updated *
I think i have explained this incorrectly and need to reajust according to what i actually require. I do not necessarily want to launch a new process, i just want to impersonate the logged on user. I have been so wrapped up at looking at CreateProcess etc i have lead myself down a path of create a new process as the current logged in user (which is not particularly what i want to do).
In turn i just want to run some code under the current user context (Impersonate the current Logged on user)?
One option is to have background application that automatically starts when user logs on and listens to commands from your service through WCF, or thrift, or by just monitoring some file and reading command from there.
Another option is to do what you originally asked for - launch using windows API. But the code is quite scary. Here is a sample, that you can use. It will execute any command line under currently active user session, with CreateProcessInConsoleSession method:
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
}
As is so common with these types of questions about Windows services, you're operating in the mindset of a single-user operating system. The whole reason you decided to write your app as a service was because you were running into conflicts between your mental model of a single-user OS and the reality of a multi-user OS. Unfortunately, a service didn't solve all of your problems and now you're trying to figure out how to accomplish step two in the ultimately-doomed hacked design.
The fact is, you cannot be guaranteed that there is a "logged on user". If no one has logged on to the workstation, there will be no one logged on, yet your service will still be running.
Even if you somehow got past this by ensuring that someone will always be logged on (impossible), then you would run into the situation where multiple users are logged on. Then which one should your service start the process as? Should it just pick one of them randomly?
And is it necessary in your case to distinguish between users logged on locally to the console and those who are logged on remotely? Remember that remote users won't have a local console.
If you could somehow get past all of these hurdles (unfortunately, probably by burying your head in the sand and continuing to pretend that Windows is a single-user OS), you could make use of the WTSGetActiveConsoleSessionId function to obtain the current session ID, the WTSQueryUserToken function to obtain the user token corresponding to that session ID, and then finally the CreateProcessAsUser function to launch your process in the context of that user. If there is one. And they have the appropriate privileges. And the physical console is not attached to a dummy session. And you're not running a server SKU that allows multiple active console sessions. And…
If you could decide on a particular user whose account you wish to use to start the auxiliary process, you could log on that user, manipulate their user token, execute the process, and finally close the process and log out the user. The CreateProcessWithLogonUser function wraps up a lot of this drudgery for you, making the code a lot more svelte. But appearances can be deceiving, and this still has some massive security implications that you probably do not completely understand if you're asking this question in the first place. And you really cannot afford to not understand security risks like this.
Besides, users that are logged in with LogonUser (which is done for you automatically when you use the CreateProcessWithLogonUser function) lack a window station and desktop on which they can launch interactive processes. So if the process you wish to launch in the context of that user will show any kind of UI, you're out of luck. Windows will kill your app as soon as it tries to access a desktop for which it lacks the requisite permissions. There is no way, from a Windows service, to obtain the handle of a desktop that will be useful to you (which goes a long way towards explaining the general rule you probably already know, that services cannot display any type of UI).