Created
January 6, 2024 18:54
-
-
Save smourier/70cc9208b47534ba475279a4554180ce to your computer and use it in GitHub Desktop.
RunAsDesktopUser with new LibraryImport (https://stackoverflow.com/questions/77770231/libraryimport-the-type-is-not-supported-by-source-generated-p-invokes#77770231)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void RunAsDesktopUser(string fileName) | |
{ | |
if (string.IsNullOrWhiteSpace(fileName)) | |
throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName)); | |
// To start process as shell user you will need to carry out these steps: | |
// 1. Enable the SeIncreaseQuotaPrivilege in your current token | |
// 2. Get an HWND representing the desktop shell (GetShellWindow) | |
// 3. Get the Process ID(PID) of the process associated with that window(GetWindowThreadProcessId) | |
// 4. Open that process(OpenProcess) | |
// 5. Get the access token from that process (OpenProcessToken) | |
// 6. Make a primary token with that token(DuplicateTokenEx) | |
// 7. Start the new process with that primary token(CreateProcessWithTokenW) | |
var hProcessToken = IntPtr.Zero; | |
// Enable SeIncreaseQuotaPrivilege in this process. (This won't work if current process is not elevated.) | |
try | |
{ | |
var process = GetCurrentProcess(); | |
if (!OpenProcessToken(process, 0x0020, ref hProcessToken)) | |
return; | |
var tkp = new TOKEN_PRIVILEGES | |
{ | |
PrivilegeCount = 1, | |
Privileges = new LUID_AND_ATTRIBUTES[1] | |
}; | |
if (!LookupPrivilegeValue(null, "SeIncreaseQuotaPrivilege", ref tkp.Privileges[0].Luid)) | |
return; | |
tkp.Privileges[0].Attributes = 0x00000002; | |
if (!AdjustTokenPrivileges(hProcessToken, false, tkp, 0, IntPtr.Zero, IntPtr.Zero)) | |
return; | |
} | |
finally | |
{ | |
CloseHandle(hProcessToken); | |
} | |
// Get an HWND representing the desktop shell. | |
// CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been | |
// replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and | |
// restarted elevated. | |
var hwnd = GetShellWindow(); | |
if (hwnd == IntPtr.Zero) | |
return; | |
var hShellProcess = IntPtr.Zero; | |
var hShellProcessToken = IntPtr.Zero; | |
var hPrimaryToken = IntPtr.Zero; | |
try | |
{ | |
// Get the PID of the desktop shell process. | |
uint dwPID; | |
if (GetWindowThreadProcessId(hwnd, out dwPID) == 0) | |
return; | |
// Open the desktop shell process in order to query it (get the token) | |
hShellProcess = OpenProcess(ProcessAccessFlags.QueryInformation, false, dwPID); | |
if (hShellProcess == IntPtr.Zero) | |
return; | |
// Get the process token of the desktop shell. | |
if (!OpenProcessToken(hShellProcess, 0x0002, ref hShellProcessToken)) | |
return; | |
var dwTokenRights = 395U; | |
// Duplicate the shell's process token to get a primary token. | |
// Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation). | |
if (!DuplicateTokenEx(hShellProcessToken, dwTokenRights, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out hPrimaryToken)) | |
return; | |
// Start the target process with the new token. | |
var si = new STARTUPINFO(); | |
var pi = new PROCESS_INFORMATION(); | |
if (!CreateProcessWithTokenW(hPrimaryToken, 0, fileName, "", 0, IntPtr.Zero, Path.GetDirectoryName(fileName), ref si, out pi)) | |
{ | |
var gle = Marshal.GetLastWin32Error(); | |
var msg = new Win32Exception(gle).Message; | |
MessageBox.Show(msg); | |
return; | |
} | |
} | |
finally | |
{ | |
CloseHandle(hShellProcessToken); | |
CloseHandle(hPrimaryToken); | |
CloseHandle(hShellProcess); | |
} | |
} | |
#region Interop | |
[CustomMarshaller(typeof(TOKEN_PRIVILEGES), MarshalMode.ManagedToUnmanagedIn, typeof(TOKEN_PRIVILEGESMarshaller))] | |
private unsafe static class TOKEN_PRIVILEGESMarshaller | |
{ | |
public struct Unmanaged | |
{ | |
public uint PrivilegeCount; | |
public LUID_AND_ATTRIBUTES* Privileges; | |
} | |
public static Unmanaged ConvertToUnmanaged(TOKEN_PRIVILEGES managed) | |
{ | |
var unmanaged = new Unmanaged | |
{ | |
PrivilegeCount = managed.PrivilegeCount, | |
Privileges = ArrayMarshaller<LUID_AND_ATTRIBUTES, LUID_AND_ATTRIBUTES>.AllocateContainerForUnmanagedElements(managed.Privileges, out var count) | |
}; | |
ArrayMarshaller<LUID_AND_ATTRIBUTES, LUID_AND_ATTRIBUTES>.GetManagedValuesSource(managed.Privileges) | |
.CopyTo(ArrayMarshaller<LUID_AND_ATTRIBUTES, LUID_AND_ATTRIBUTES> | |
.GetUnmanagedValuesDestination(unmanaged.Privileges, count)); | |
return unmanaged; | |
} | |
public static void Free(Unmanaged unmanaged) => ArrayMarshaller<LUID_AND_ATTRIBUTES, LUID_AND_ATTRIBUTES>.Free(unmanaged.Privileges); | |
} | |
[NativeMarshalling(typeof(TOKEN_PRIVILEGESMarshaller))] | |
private struct TOKEN_PRIVILEGES | |
{ | |
public uint PrivilegeCount; | |
//[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] // you can keep that for compat reasons | |
public LUID_AND_ATTRIBUTES[] Privileges; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct LUID_AND_ATTRIBUTES | |
{ | |
public LUID Luid; | |
public uint Attributes; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct LUID | |
{ | |
public uint LowPart; | |
public int HighPart; | |
} | |
[Flags] | |
private enum ProcessAccessFlags : uint | |
{ | |
All = 0x001F0FFF, | |
Terminate = 0x00000001, | |
CreateThread = 0x00000002, | |
VirtualMemoryOperation = 0x00000008, | |
VirtualMemoryRead = 0x00000010, | |
VirtualMemoryWrite = 0x00000020, | |
DuplicateHandle = 0x00000040, | |
CreateProcess = 0x000000080, | |
SetQuota = 0x00000100, | |
SetInformation = 0x00000200, | |
QueryInformation = 0x00000400, | |
QueryLimitedInformation = 0x00001000, | |
Synchronize = 0x00100000 | |
} | |
private enum SECURITY_IMPERSONATION_LEVEL | |
{ | |
SecurityAnonymous, | |
SecurityIdentification, | |
SecurityImpersonation, | |
SecurityDelegation | |
} | |
private enum TOKEN_TYPE | |
{ | |
TokenPrimary = 1, | |
TokenImpersonation | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
private struct PROCESS_INFORMATION | |
{ | |
public IntPtr hProcess; | |
public IntPtr hThread; | |
public int dwProcessId; | |
public int dwThreadId; | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
private struct STARTUPINFO | |
{ | |
public int cb; | |
public IntPtr lpReserved; | |
public IntPtr lpDesktop; | |
public IntPtr lpTitle; | |
public int dwX; | |
public int dwY; | |
public int dwXSize; | |
public int dwYSize; | |
public int dwXCountChars; | |
public int dwYCountChars; | |
public int dwFillAttribute; | |
public int dwFlags; | |
public short wShowWindow; | |
public short cbReserved2; | |
public IntPtr lpReserved2; | |
public IntPtr hStdInput; | |
public IntPtr hStdOutput; | |
public IntPtr hStdError; | |
} | |
[LibraryImport("kernel32")] | |
private static partial IntPtr GetCurrentProcess(); | |
[LibraryImport("advapi32", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static partial bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); | |
[LibraryImport("advapi32", SetLastError = true, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "LookupPrivilegeValueW")] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static partial bool LookupPrivilegeValue(string host, string name, ref LUID pluid); | |
[LibraryImport("advapi32", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static partial bool AdjustTokenPrivileges(IntPtr htok, [MarshalAs(UnmanagedType.Bool)] bool disall, TOKEN_PRIVILEGES newst, int len, IntPtr prev, IntPtr relen); | |
[LibraryImport("kernel32.dll", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static partial bool CloseHandle(IntPtr hObject); | |
[LibraryImport("user32.dll")] | |
private static partial IntPtr GetShellWindow(); | |
[LibraryImport("user32.dll", SetLastError = true)] | |
private static partial uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); | |
[LibraryImport("kernel32.dll", SetLastError = true)] | |
private static partial IntPtr OpenProcess(ProcessAccessFlags processAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint processId); | |
[LibraryImport("advapi32", SetLastError = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static partial bool DuplicateTokenEx(IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL impersonationLevel, TOKEN_TYPE tokenType, out IntPtr phNewToken); | |
[LibraryImport("advapi32", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
private static partial bool CreateProcessWithTokenW(IntPtr hToken, int dwLogonFlags, string lpApplicationName, string lpCommandLine, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment