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;
}
}