Impersonate.cs
using System;
using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Principal; namespace BFrame { /// <summary> /// /// </summary> /// <remarks> /// https://support.microsoft.com/en-us/kb/889251 /// https://social.msdn.microsoft.com/Forums/vstudio/en-US/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb/how-run-client-application-as-a-windows-service-in-c?forum=csharpgeneral /// </remarks> public class Impersonation : IDisposable { public enum LOGON_TYPE { LOGON32_LOGON_INTERACTIVE = 2, LOGON32_LOGON_NETWORK, LOGON32_LOGON_BATCH, LOGON32_LOGON_SERVICE, LOGON32_LOGON_UNLOCK = 7, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_LOGON_NEW_CREDENTIALS } public enum LogonProvider { LOGON32_PROVIDER_DEFAULT = 0, LOGON32_PROVIDER_WINNT35 = 1, LOGON32_PROVIDER_WINNT40 = 2, LOGON32_PROVIDER_WINNT50 = 3 }; [DllImport("advapi32.dll")] public static extern int LogonUserA(String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool RevertToSelf(); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CloseHandle(IntPtr handle); [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } [StructLayout(LayoutKind.Sequential)] internal struct SECURITY_ATTRIBUTES { public uint nLength; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public uint cb; public string lpReserved; public string lpDesktop; public string lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } internal enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation } internal enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation } [DllImport("advapi32.dll", SetLastError = true)] private static extern bool CreateProcessAsUser( IntPtr hToken, string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)] private static extern bool DuplicateTokenEx( IntPtr hExistingToken, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, Int32 ImpersonationLevel, Int32 dwTokenType, ref IntPtr phNewToken); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool OpenProcessToken( IntPtr ProcessHandle, UInt32 DesiredAccess, ref IntPtr TokenHandle); [DllImport("userenv.dll", SetLastError = true)] private static extern bool CreateEnvironmentBlock( ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); [DllImport("userenv.dll", SetLastError = true)] private static extern bool DestroyEnvironmentBlock( IntPtr lpEnvironment); [StructLayout(LayoutKind.Sequential)] public struct ProfileInfo { /// /// Specifies the size of the structure, in bytes. /// public int dwSize; /// /// This member can be one of the following flags: /// PI_NOUI or PI_APPLYPOLICY /// public int dwFlags; /// /// Pointer to the name of the user. /// This member is used as the base name of the directory /// in which to store a new profile. /// public string lpUserName; /// /// Pointer to the roaming user profile path. /// If the user does not have a roaming profile, this member can be NULL. /// public string lpProfilePath; /// /// Pointer to the default user profile path. This member can be NULL. /// public string lpDefaultPath; /// /// Pointer to the name of the validating domain controller, in NetBIOS format. /// If this member is NULL, the Windows NT 4.0-style policy will not be applied. /// public string lpServerName; /// /// Pointer to the path of the Windows NT 4.0-style policy file. /// This member can be NULL. /// public string lpPolicyPath; /// /// Handle to the HKEY_CURRENT_USER registry key. /// public IntPtr hProfile; } [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern bool LoadUserProfile (IntPtr hToken, ref ProfileInfo lpProfileInfo); [DllImport("Userenv.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)] private static extern bool UnloadUserProfile (IntPtr hToken, IntPtr lpProfileInfo); private const short SW_SHOW = 5; private const uint TOKEN_QUERY = 0x0008; private const uint TOKEN_DUPLICATE = 0x0002; private const uint TOKEN_ASSIGN_PRIMARY = 0x0001; private const int GENERIC_ALL_ACCESS = 0x10000000; private const int STARTF_USESHOWWINDOW = 0x00000001; private const int STARTF_FORCEONFEEDBACK = 0x00000040; private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400; private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock) { bool result = false; var pi = new PROCESS_INFORMATION(); var saProcess = new SECURITY_ATTRIBUTES(); var saThread = new SECURITY_ATTRIBUTES(); saProcess.nLength = (uint)Marshal.SizeOf(saProcess); saThread.nLength = (uint)Marshal.SizeOf(saThread); var si = new STARTUPINFO(); si.cb = (uint)Marshal.SizeOf(si); //if this member is NULL, the new process inherits the desktop //and window station of its parent process. If this member is //an empty string, the process does not inherit the desktop and //window station of its parent process; instead, the system //determines if a new desktop and window station need to be created. //If the impersonated user already has a desktop, the system uses the //existing desktop. si.lpDesktop = @"WinSta0\Default"; //Modify as needed si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK; si.wShowWindow = SW_SHOW; //Set other si properties as required. result = CreateProcessAsUser( token, null, cmdLine, ref saProcess, ref saThread, false, CREATE_UNICODE_ENVIRONMENT, envBlock, null, ref si, out pi); if (result == false) { int error = Marshal.GetLastWin32Error(); string message = String.Format("CreateProcessAsUser Error: {0}", error); Debug.WriteLine(message); } return result; } private static IntPtr GetPrimaryToken(int processId) { IntPtr token = IntPtr.Zero; IntPtr primaryToken = IntPtr.Zero; bool retVal = false; Process p = null; try { p = Process.GetProcessById(processId); } catch (ArgumentException) { string details = String.Format("ProcessID {0} Not Available", processId); Debug.WriteLine(details); throw; } //Gets impersonation token retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token); if (retVal == true) { var sa = new SECURITY_ATTRIBUTES(); sa.nLength = (uint)Marshal.SizeOf(sa); //Convert the impersonation token into Primary token retVal = DuplicateTokenEx( token, TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref primaryToken); //Close the Token that was previously opened. CloseHandle(token); if (retVal == false) { string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error()); Debug.WriteLine(message); } } else { string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error()); Debug.WriteLine(message); } //We'll Close this token after it is used. return primaryToken; } private static IntPtr GetEnvironmentBlock(IntPtr token) { IntPtr envBlock = IntPtr.Zero; bool retVal = CreateEnvironmentBlock(ref envBlock, token, false); if (retVal == false) { //Environment Block, things like common paths to My Documents etc. //Will not be created if "false" //It should not adversley affect CreateProcessAsUser. string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error()); Debug.WriteLine(message); } return envBlock; } public static bool Launch(string appCmdLine /*,int processId*/) { bool ret = false; //Either specify the processID explicitly //Or try to get it from a process owned by the user. //In this case assuming there is only one explorer.exe Process[] ps = Process.GetProcessesByName("explorer"); int processId = -1;//=processId if (ps.Length > 0) { processId = ps[0].Id; } if (processId > 1) { IntPtr token = GetPrimaryToken(processId); if (token != IntPtr.Zero) { IntPtr envBlock = GetEnvironmentBlock(token); ret = LaunchProcessAsUser(appCmdLine, token, envBlock); if (envBlock != IntPtr.Zero) DestroyEnvironmentBlock(envBlock); CloseHandle(token); } } return ret; } private WindowsImpersonationContext Context; private IntPtr token = IntPtr.Zero; private IntPtr tokenDuplicate = IntPtr.Zero; public Impersonation(String userName, String domain, String password) { if (RevertToSelf()) { if (LogonUserA(userName, domain, password, (int)LOGON_TYPE.LOGON32_LOGON_NETWORK, (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, ref token) != 0) { var sa = new SECURITY_ATTRIBUTES(); sa.bInheritHandle = true; sa.nLength = (uint)Marshal.SizeOf(sa); sa.lpSecurityDescriptor = (IntPtr)0; //if (DuplicateToken(token, 2, ref tokenDuplicate) != 0) if (DuplicateTokenEx(token, GENERIC_ALL_ACCESS, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenImpersonation, ref tokenDuplicate)) { var tempWindowsIdentity = new WindowsIdentity(tokenDuplicate); Context = tempWindowsIdentity.Impersonate(); LoadProfile(userName); CloseTokens(); } } } } private void LoadProfile(string userName) { var profileInfo = new ProfileInfo() { dwSize = Marshal.SizeOf<ProfileInfo>(), lpUserName = userName, dwFlags = 1 }; Boolean loadSuccess = LoadUserProfile(tokenDuplicate, ref profileInfo); } private void CloseTokens() { if (token != IntPtr.Zero) CloseHandle(token); if (tokenDuplicate != IntPtr.Zero) CloseHandle(tokenDuplicate); } public void Dispose() { if (Context != null) { Context.Undo(); } CloseTokens(); } } } |