using System; using System.Diagnostics; using System.IO; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; namespace amecs { [StructLayout(LayoutKind.Sequential)] public class NSudo { private struct SECURITY_ATTRIBUTES { public int nLength; public unsafe byte* lpSecurityDescriptor; public int bInheritHandle; } private enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation } private enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation } [StructLayout(LayoutKind.Sequential)] private struct LUID { public uint LowPart; public uint HighPart; } [StructLayout(LayoutKind.Sequential, Pack = 4)] private struct LUID_AND_ATTRIBUTES { public LUID Luid; public UInt32 Attributes; } private struct TOKEN_PRIVILEGES { public int PrivilegeCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst=1)] public LUID_AND_ATTRIBUTES[] Privileges; } private static UInt32 MAXIMUM_ALLOWED = (UInt32)TokenAccessLevels.MaximumAllowed; [DllImport("advapi32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle); [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool DuplicateTokenEx( IntPtr hExistingToken, uint dwDesiredAccess, IntPtr lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, out IntPtr phNewToken ); [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool DuplicateTokenEx( IntPtr hExistingToken, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, out IntPtr phNewToken ); [DllImport("advapi32.dll")] static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpName, ref LUID lpLuid); internal const int SE_PRIVILEGE_ENABLED = 0x00000002; // Use this signature if you do not want the previous state [DllImport("advapi32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2); [System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)] private static extern bool SetThreadToken(IntPtr pHandle, IntPtr hToken); [DllImport("wtsapi32.dll", SetLastError=true)] static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr Token); [DllImport("advapi32.dll", SetLastError = true)] static extern Boolean SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, ref UInt32 TokenInformation, UInt32 TokenInformationLength); [DllImport("userenv.dll", SetLastError=true)] static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit ); public static bool GetUserPrivilege(IntPtr Token) { IntPtr NewToken; DuplicateTokenEx(Token, MAXIMUM_ALLOWED, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenImpersonation, out NewToken); SetThreadToken(IntPtr.Zero, NewToken); return true; } [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] 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); [Flags] enum CreationFlags { CREATE_SUSPENDED = 0x00000004, CREATE_UNICODE_ENVIRONMENT = 0x00000400, CREATE_NO_WINDOW = 0x08000000, CREATE_NEW_CONSOLE = 0x00000010 } [DllImport("advapi32.dll", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool LogonUser( [MarshalAs(UnmanagedType.LPStr)] string pszUserName, [MarshalAs(UnmanagedType.LPStr)] string pszDomain, [MarshalAs(UnmanagedType.LPStr)] string pszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); public static int? RunProcessAsUser(IntPtr Token, string Executable, string Arguments, uint timeout = 0xFFFFFFFF) { GetAssignPrivilege(); GetQuotaPrivilege(); var startupInfo = new STARTUPINFO(); startupInfo.cb = Marshal.SizeOf(startupInfo); startupInfo.dwFlags = 0x00000001; startupInfo.wShowWindow = 1; var procAttrs = new SECURITY_ATTRIBUTES(); var threadAttrs = new SECURITY_ATTRIBUTES(); procAttrs.nLength = Marshal.SizeOf(procAttrs); threadAttrs.nLength = Marshal.SizeOf(threadAttrs); // Log on user temporarily in order to start console process in its security context. var hUserTokenDuplicate = IntPtr.Zero; var pEnvironmentBlock = IntPtr.Zero; DuplicateTokenEx(Token, MAXIMUM_ALLOWED, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out hUserTokenDuplicate); CreateEnvironmentBlock(out pEnvironmentBlock, Token, false); PROCESS_INFORMATION _processInfo; if (!CreateProcessAsUser(hUserTokenDuplicate, null, String.IsNullOrEmpty(Arguments) ? $"\"{Executable}\"" : $"\"{Executable}\" {Arguments}", ref procAttrs, ref threadAttrs, false, (uint)CreationFlags.CREATE_NO_WINDOW | (uint)CreationFlags.CREATE_UNICODE_ENVIRONMENT, pEnvironmentBlock, null, ref startupInfo, out _processInfo)) return null; uint exitCode; WaitForSingleObject(_processInfo.hProcess, timeout); GetExitCodeProcess(_processInfo.hProcess, out exitCode); return (int)exitCode; /* uint dwCreationFlags = (uint)CreationFlags.CREATE_UNICODE_ENVIRONMENT; startupInfo.cb = Marshal.SizeOf(startupInfo); SECURITY_ATTRIBUTES throwaway = new SECURITY_ATTRIBUTES(); SECURITY_ATTRIBUTES throwaway2 = new SECURITY_ATTRIBUTES(); Console.WriteLine(Marshal.GetLastWin32Error() + "-3"); Console.WriteLine(CreateProcessAsUser(hUserToken, String.Empty, "\"C:\\Windows\\notepad.exe\"", ref throwaway, ref throwaway2, false, 0, IntPtr.Zero, String.Empty, ref StartupInfo, out ProcessInfo)); Console.WriteLine(Marshal.GetLastWin32Error() + "-4"); return Process.GetProcessById(ProcessInfo.dwProcessId); */ } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode); [DllImport("kernel32.dll", SetLastError=true)] static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] struct STARTUPINFO { public Int32 cb; public IntPtr lpReserved; public IntPtr lpDesktop; public IntPtr 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; } public static IntPtr GetUserToken() { IntPtr Token; WTSQueryUserToken((uint)SessionID, out Token); return Token; } private static int SessionID = -1; public static bool GetSystemPrivilege() { IntPtr CurrentProcessToken; OpenProcessToken(Process.GetCurrentProcess().Handle, MAXIMUM_ALLOWED, out CurrentProcessToken); IntPtr DuplicatedCurrentProcessToken; DuplicateTokenEx(CurrentProcessToken, MAXIMUM_ALLOWED, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenImpersonation, out DuplicatedCurrentProcessToken); LUID_AND_ATTRIBUTES RawPrivilege = new LUID_AND_ATTRIBUTES(); LookupPrivilegeValue(IntPtr.Zero, "SeDebugPrivilege", ref RawPrivilege.Luid); RawPrivilege.Attributes = SE_PRIVILEGE_ENABLED; TOKEN_PRIVILEGES TokenPrivilege = new TOKEN_PRIVILEGES(); TokenPrivilege.Privileges = new LUID_AND_ATTRIBUTES[] { RawPrivilege }; TokenPrivilege.PrivilegeCount = 1; AdjustTokenPrivileges(DuplicatedCurrentProcessToken, false, ref TokenPrivilege, 0, IntPtr.Zero, IntPtr.Zero); SetThreadToken(IntPtr.Zero, DuplicatedCurrentProcessToken); SessionID = GetActiveSession(); IntPtr OriginalProcessToken = new IntPtr(-1); CreateSystemToken((int)MAXIMUM_ALLOWED, SessionID, ref OriginalProcessToken); IntPtr SystemToken; DuplicateTokenEx(OriginalProcessToken, MAXIMUM_ALLOWED, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenImpersonation, out SystemToken); SetThreadToken(IntPtr.Zero, SystemToken); return true; } [DllImport("advapi32.dll", SetLastError=true)] static extern bool GetTokenInformation( IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength, out int ReturnLength); enum TOKEN_INFORMATION_CLASS { TokenUser = 1, TokenGroups, TokenPrivileges, TokenOwner, TokenPrimaryGroup, TokenDefaultDacl, TokenSource, TokenType, TokenImpersonationLevel, TokenStatistics, TokenRestrictedSids, TokenSessionId, TokenGroupsAndPrivileges, TokenSessionReference, TokenSandBoxInert, TokenAuditPolicy, TokenOrigin } private static int GetActiveSession() { IntPtr pSessionInfo = IntPtr.Zero; Int32 Count = 0; var retval = WTSEnumerateSessions((IntPtr)null, 0, 1, ref pSessionInfo, ref Count); Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); Int64 current = (Int64)pSessionInfo; int result = -1; 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; if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) { result = si.SessionID; break; } } WTSFreeMemory(pSessionInfo); } return result; } private static void CreateSystemToken(int DesiredAccess, int dwSessionID, ref IntPtr TokenHandle) { int dwLsassPID = -1; int dwWinLogonPID = -1; WTS_PROCESS_INFO[] pProcesses; IntPtr pProcessInfo = IntPtr.Zero; int dwProcessCount = 0; if (WTSEnumerateProcesses((IntPtr)null, 0, 1, ref pProcessInfo, ref dwProcessCount)) { IntPtr pMemory = pProcessInfo; pProcesses = new WTS_PROCESS_INFO[dwProcessCount]; for (int i = 0; i < dwProcessCount; i++) { pProcesses[i] = (WTS_PROCESS_INFO)Marshal.PtrToStructure(pProcessInfo, typeof(WTS_PROCESS_INFO)); pProcessInfo = (IntPtr)((long)pProcessInfo + Marshal.SizeOf(pProcesses[i])); var processName = Marshal.PtrToStringAnsi(pProcesses[i].ProcessName); ConvertSidToStringSid(pProcesses[i].UserSid, out string sid); string strSid; if (processName == null || pProcesses[i].UserSid == default || sid != "S-1-5-18") continue; if ((-1 == dwLsassPID) && (0 == pProcesses[i].SessionID) && (processName == "lsass.exe")) { dwLsassPID = pProcesses[i].ProcessID; continue; } if ((-1 == dwWinLogonPID) && (dwSessionID == pProcesses[i].SessionID) && (processName == "winlogon.exe")) { dwWinLogonPID = pProcesses[i].ProcessID; continue; } } WTSFreeMemory(pMemory); } bool Result = false; IntPtr SystemProcessHandle = IntPtr.Zero; try { SystemProcessHandle = Process.GetProcessById(dwLsassPID).Handle; } catch { SystemProcessHandle = Process.GetProcessById(dwWinLogonPID).Handle; } IntPtr SystemTokenHandle = IntPtr.Zero; if (OpenProcessToken(SystemProcessHandle, TOKEN_DUPLICATE, out SystemTokenHandle)) { Result = DuplicateTokenEx(SystemTokenHandle, (uint)DesiredAccess, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out TokenHandle); CloseHandle(SystemTokenHandle); } CloseHandle(SystemProcessHandle); // return Result; return; } [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess( uint processAccess, bool bInheritHandle, uint processId ); public const UInt32 TOKEN_DUPLICATE = 0x0002; [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)] static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid); [DllImport("kernel32.dll", SetLastError=true)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [SuppressUnmanagedCodeSecurity] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); [DllImport("wtsapi32.dll", SetLastError=true)] static extern int WTSEnumerateSessions( System.IntPtr hServer, int Reserved, int Version, ref System.IntPtr ppSessionInfo, ref int pCount); [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 } [DllImport("wtsapi32.dll")] static extern void WTSFreeMemory(IntPtr pMemory); [DllImport("wtsapi32.dll", SetLastError=true)] static extern bool WTSEnumerateProcesses( IntPtr serverHandle, // Handle to a terminal server. Int32 reserved, // must be 0 Int32 version, // must be 1 ref IntPtr ppProcessInfo, // pointer to array of WTS_PROCESS_INFO ref Int32 pCount // pointer to number of processes ); struct WTS_PROCESS_INFO { public int SessionID; public int ProcessID; //This is a pointer to string... public IntPtr ProcessName; public IntPtr UserSid; } [DllImport("ntdll.dll", SetLastError = true)] static extern IntPtr RtlAdjustPrivilege(int Privilege, bool bEnablePrivilege, bool IsThreadPrivilege, out bool PreviousValue); [DllImport("advapi32.dll")] static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpName, ref UInt64 lpLuid); public static void GetOwnershipPrivilege() { ulong luid = 0; bool throwaway; LookupPrivilegeValue(IntPtr.Zero, "SeTakeOwnershipPrivilege", ref luid); RtlAdjustPrivilege((int)luid, true, true, out throwaway); } public static void GetAssignPrivilege() { ulong luid = 0; bool throwaway; LookupPrivilegeValue(IntPtr.Zero, "SeAssignPrimaryTokenPrivilege", ref luid); RtlAdjustPrivilege((int)luid, true, true, out throwaway); } public static void GetQuotaPrivilege() { ulong luid = 0; bool throwaway; LookupPrivilegeValue(IntPtr.Zero, "SeIncreaseQuotaPrivilege", ref luid); RtlAdjustPrivilege((int)luid, true, true, out throwaway); } public static void GetShutdownPrivilege() { ulong luid = 0; bool throwaway; LookupPrivilegeValue(IntPtr.Zero, "SeShutdownPrivilege", ref luid); RtlAdjustPrivilege((int)luid, true, true, out throwaway); } public static void RunAsUser(Action action) { var token = NSudo.GetUserToken(); Task.Run((Action)Delegate.Combine((Action)(() => { NSudo.GetUserPrivilege(token); }), action)).Wait(); Marshal.FreeHGlobal(token); } private static async Task RunAsUserAsync(Action action) { var token = NSudo.GetUserToken(); await Task.Run((Action)Delegate.Combine((Action)(() => { NSudo.GetUserPrivilege(token); }), action)); Marshal.FreeHGlobal(token); } } }