1 using Microsoft.Win32.SafeHandles; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Runtime.ConstrainedExecution; 6 using System.Runtime.InteropServices; 7 using System.Security.Principal; 8 using System.Text; 9 10 namespace Ansible.AccessToken 11 { 12 internal class NativeHelpers 13 { 14 [StructLayout(LayoutKind.Sequential)] 15 public struct LUID_AND_ATTRIBUTES 16 { 17 public Luid Luid; 18 public UInt32 Attributes; 19 } 20 21 [StructLayout(LayoutKind.Sequential)] 22 public struct SID_AND_ATTRIBUTES 23 { 24 public IntPtr Sid; 25 public int Attributes; 26 } 27 28 [StructLayout(LayoutKind.Sequential)] 29 public struct TOKEN_PRIVILEGES 30 { 31 public UInt32 PrivilegeCount; 32 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] 33 public LUID_AND_ATTRIBUTES[] Privileges; 34 } 35 36 [StructLayout(LayoutKind.Sequential)] 37 public struct TOKEN_USER 38 { 39 public SID_AND_ATTRIBUTES User; 40 } 41 42 public enum TokenInformationClass : uint 43 { 44 TokenUser = 1, 45 TokenPrivileges = 3, 46 TokenStatistics = 10, 47 TokenElevationType = 18, 48 TokenLinkedToken = 19, 49 } 50 } 51 52 internal class NativeMethods 53 { 54 [DllImport("kernel32.dll", SetLastError = true)] CloseHandle( IntPtr hObject)55 public static extern bool CloseHandle( 56 IntPtr hObject); 57 58 [DllImport("advapi32.dll", SetLastError = true)] DuplicateTokenEx( SafeNativeHandle hExistingToken, TokenAccessLevels dwDesiredAccess, IntPtr lpTokenAttributes, SecurityImpersonationLevel ImpersonationLevel, TokenType TokenType, out SafeNativeHandle phNewToken)59 public static extern bool DuplicateTokenEx( 60 SafeNativeHandle hExistingToken, 61 TokenAccessLevels dwDesiredAccess, 62 IntPtr lpTokenAttributes, 63 SecurityImpersonationLevel ImpersonationLevel, 64 TokenType TokenType, 65 out SafeNativeHandle phNewToken); 66 67 [DllImport("kernel32.dll")] GetCurrentProcess()68 public static extern SafeNativeHandle GetCurrentProcess(); 69 70 [DllImport("advapi32.dll", SetLastError = true)] GetTokenInformation( SafeNativeHandle TokenHandle, NativeHelpers.TokenInformationClass TokenInformationClass, SafeMemoryBuffer TokenInformation, UInt32 TokenInformationLength, out UInt32 ReturnLength)71 public static extern bool GetTokenInformation( 72 SafeNativeHandle TokenHandle, 73 NativeHelpers.TokenInformationClass TokenInformationClass, 74 SafeMemoryBuffer TokenInformation, 75 UInt32 TokenInformationLength, 76 out UInt32 ReturnLength); 77 78 [DllImport("advapi32.dll", SetLastError = true)] ImpersonateLoggedOnUser( SafeNativeHandle hToken)79 public static extern bool ImpersonateLoggedOnUser( 80 SafeNativeHandle hToken); 81 82 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] LogonUserW( string lpszUsername, string lpszDomain, string lpszPassword, LogonType dwLogonType, LogonProvider dwLogonProvider, out SafeNativeHandle phToken)83 public static extern bool LogonUserW( 84 string lpszUsername, 85 string lpszDomain, 86 string lpszPassword, 87 LogonType dwLogonType, 88 LogonProvider dwLogonProvider, 89 out SafeNativeHandle phToken); 90 91 [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] LookupPrivilegeNameW( string lpSystemName, ref Luid lpLuid, StringBuilder lpName, ref UInt32 cchName)92 public static extern bool LookupPrivilegeNameW( 93 string lpSystemName, 94 ref Luid lpLuid, 95 StringBuilder lpName, 96 ref UInt32 cchName); 97 98 [DllImport("kernel32.dll", SetLastError = true)] OpenProcess( ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, UInt32 dwProcessId)99 public static extern SafeNativeHandle OpenProcess( 100 ProcessAccessFlags dwDesiredAccess, 101 bool bInheritHandle, 102 UInt32 dwProcessId); 103 104 [DllImport("advapi32.dll", SetLastError = true)] OpenProcessToken( SafeNativeHandle ProcessHandle, TokenAccessLevels DesiredAccess, out SafeNativeHandle TokenHandle)105 public static extern bool OpenProcessToken( 106 SafeNativeHandle ProcessHandle, 107 TokenAccessLevels DesiredAccess, 108 out SafeNativeHandle TokenHandle); 109 110 [DllImport("advapi32.dll", SetLastError = true)] RevertToSelf()111 public static extern bool RevertToSelf(); 112 } 113 114 internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid 115 { SafeMemoryBuffer()116 public SafeMemoryBuffer() : base(true) { } SafeMemoryBuffer(int cb)117 public SafeMemoryBuffer(int cb) : base(true) 118 { 119 base.SetHandle(Marshal.AllocHGlobal(cb)); 120 } SafeMemoryBuffer(IntPtr handle)121 public SafeMemoryBuffer(IntPtr handle) : base(true) 122 { 123 base.SetHandle(handle); 124 } 125 126 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] ReleaseHandle()127 protected override bool ReleaseHandle() 128 { 129 Marshal.FreeHGlobal(handle); 130 return true; 131 } 132 } 133 134 public enum LogonProvider 135 { 136 Default, 137 WinNT35, 138 WinNT40, 139 WinNT50, 140 } 141 142 public enum LogonType 143 { 144 Interactive = 2, 145 Network = 3, 146 Batch = 4, 147 Service = 5, 148 Unlock = 7, 149 NetworkCleartext = 8, 150 NewCredentials = 9, 151 } 152 153 [Flags] 154 public enum PrivilegeAttributes : uint 155 { 156 Disabled = 0x00000000, 157 EnabledByDefault = 0x00000001, 158 Enabled = 0x00000002, 159 Removed = 0x00000004, 160 UsedForAccess = 0x80000000, 161 } 162 163 [Flags] 164 public enum ProcessAccessFlags : uint 165 { 166 Terminate = 0x00000001, 167 CreateThread = 0x00000002, 168 VmOperation = 0x00000008, 169 VmRead = 0x00000010, 170 VmWrite = 0x00000020, 171 DupHandle = 0x00000040, 172 CreateProcess = 0x00000080, 173 SetQuota = 0x00000100, 174 SetInformation = 0x00000200, 175 QueryInformation = 0x00000400, 176 SuspendResume = 0x00000800, 177 QueryLimitedInformation = 0x00001000, 178 Delete = 0x00010000, 179 ReadControl = 0x00020000, 180 WriteDac = 0x00040000, 181 WriteOwner = 0x00080000, 182 Synchronize = 0x00100000, 183 } 184 185 public enum SecurityImpersonationLevel 186 { 187 Anonymous, 188 Identification, 189 Impersonation, 190 Delegation, 191 } 192 193 public enum TokenElevationType 194 { 195 Default = 1, 196 Full, 197 Limited, 198 } 199 200 public enum TokenType 201 { 202 Primary = 1, 203 Impersonation, 204 } 205 206 [StructLayout(LayoutKind.Sequential)] 207 public struct Luid 208 { 209 public UInt32 LowPart; 210 public Int32 HighPart; 211 operator UInt64Ansible.AccessToken.Luid212 public static explicit operator UInt64(Luid l) 213 { 214 return (UInt64)((UInt64)l.HighPart << 32) | (UInt64)l.LowPart; 215 } 216 } 217 218 [StructLayout(LayoutKind.Sequential)] 219 public struct TokenStatistics 220 { 221 public Luid TokenId; 222 public Luid AuthenticationId; 223 public Int64 ExpirationTime; 224 public TokenType TokenType; 225 public SecurityImpersonationLevel ImpersonationLevel; 226 public UInt32 DynamicCharged; 227 public UInt32 DynamicAvailable; 228 public UInt32 GroupCount; 229 public UInt32 PrivilegeCount; 230 public Luid ModifiedId; 231 } 232 233 public class PrivilegeInfo 234 { 235 public string Name; 236 public PrivilegeAttributes Attributes; 237 PrivilegeInfo(NativeHelpers.LUID_AND_ATTRIBUTES la)238 internal PrivilegeInfo(NativeHelpers.LUID_AND_ATTRIBUTES la) 239 { 240 Name = TokenUtil.GetPrivilegeName(la.Luid); 241 Attributes = (PrivilegeAttributes)la.Attributes; 242 } 243 } 244 245 public class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid 246 { SafeNativeHandle()247 public SafeNativeHandle() : base(true) { } SafeNativeHandle(IntPtr handle)248 public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; } 249 250 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] ReleaseHandle()251 protected override bool ReleaseHandle() 252 { 253 return NativeMethods.CloseHandle(handle); 254 } 255 } 256 257 public class Win32Exception : System.ComponentModel.Win32Exception 258 { 259 private string _msg; 260 Win32Exception(string message)261 public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { } Win32Exception(int errorCode, string message)262 public Win32Exception(int errorCode, string message) : base(errorCode) 263 { 264 _msg = String.Format("{0} ({1}, Win32ErrorCode {2} - 0x{2:X8})", message, base.Message, errorCode); 265 } 266 267 public override string Message { get { return _msg; } } operator Win32Exception(string message)268 public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } 269 } 270 271 public class TokenUtil 272 { DuplicateToken(SafeNativeHandle hToken, TokenAccessLevels access, SecurityImpersonationLevel impersonationLevel, TokenType tokenType)273 public static SafeNativeHandle DuplicateToken(SafeNativeHandle hToken, TokenAccessLevels access, 274 SecurityImpersonationLevel impersonationLevel, TokenType tokenType) 275 { 276 SafeNativeHandle dupToken; 277 if (!NativeMethods.DuplicateTokenEx(hToken, access, IntPtr.Zero, impersonationLevel, tokenType, out dupToken)) 278 throw new Win32Exception("Failed to duplicate token"); 279 return dupToken; 280 } 281 GetTokenUser(SafeNativeHandle hToken)282 public static SecurityIdentifier GetTokenUser(SafeNativeHandle hToken) 283 { 284 using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, 285 NativeHelpers.TokenInformationClass.TokenUser)) 286 { 287 NativeHelpers.TOKEN_USER tokenUser = (NativeHelpers.TOKEN_USER)Marshal.PtrToStructure( 288 tokenInfo.DangerousGetHandle(), 289 typeof(NativeHelpers.TOKEN_USER)); 290 return new SecurityIdentifier(tokenUser.User.Sid); 291 } 292 } 293 GetTokenPrivileges(SafeNativeHandle hToken)294 public static List<PrivilegeInfo> GetTokenPrivileges(SafeNativeHandle hToken) 295 { 296 using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, 297 NativeHelpers.TokenInformationClass.TokenPrivileges)) 298 { 299 NativeHelpers.TOKEN_PRIVILEGES tokenPrivs = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure( 300 tokenInfo.DangerousGetHandle(), 301 typeof(NativeHelpers.TOKEN_PRIVILEGES)); 302 303 NativeHelpers.LUID_AND_ATTRIBUTES[] luidAttrs = 304 new NativeHelpers.LUID_AND_ATTRIBUTES[tokenPrivs.PrivilegeCount]; 305 PtrToStructureArray(luidAttrs, IntPtr.Add(tokenInfo.DangerousGetHandle(), 306 Marshal.SizeOf(tokenPrivs.PrivilegeCount))); 307 308 return luidAttrs.Select(la => new PrivilegeInfo(la)).ToList(); 309 } 310 } 311 GetTokenStatistics(SafeNativeHandle hToken)312 public static TokenStatistics GetTokenStatistics(SafeNativeHandle hToken) 313 { 314 using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, 315 NativeHelpers.TokenInformationClass.TokenStatistics)) 316 { 317 TokenStatistics tokenStats = (TokenStatistics)Marshal.PtrToStructure( 318 tokenInfo.DangerousGetHandle(), 319 typeof(TokenStatistics)); 320 return tokenStats; 321 } 322 } 323 GetTokenElevationType(SafeNativeHandle hToken)324 public static TokenElevationType GetTokenElevationType(SafeNativeHandle hToken) 325 { 326 using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, 327 NativeHelpers.TokenInformationClass.TokenElevationType)) 328 { 329 return (TokenElevationType)Marshal.ReadInt32(tokenInfo.DangerousGetHandle()); 330 } 331 } 332 GetTokenLinkedToken(SafeNativeHandle hToken)333 public static SafeNativeHandle GetTokenLinkedToken(SafeNativeHandle hToken) 334 { 335 using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, 336 NativeHelpers.TokenInformationClass.TokenLinkedToken)) 337 { 338 return new SafeNativeHandle(Marshal.ReadIntPtr(tokenInfo.DangerousGetHandle())); 339 } 340 } 341 EnumerateUserTokens(SecurityIdentifier sid, TokenAccessLevels access = TokenAccessLevels.Query)342 public static IEnumerable<SafeNativeHandle> EnumerateUserTokens(SecurityIdentifier sid, 343 TokenAccessLevels access = TokenAccessLevels.Query) 344 { 345 foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses()) 346 { 347 // We always need the Query access level so we can query the TokenUser 348 using (process) 349 using (SafeNativeHandle hToken = TryOpenAccessToken(process, access | TokenAccessLevels.Query)) 350 { 351 if (hToken == null) 352 continue; 353 354 if (!sid.Equals(GetTokenUser(hToken))) 355 continue; 356 357 yield return hToken; 358 } 359 } 360 } 361 ImpersonateToken(SafeNativeHandle hToken)362 public static void ImpersonateToken(SafeNativeHandle hToken) 363 { 364 if (!NativeMethods.ImpersonateLoggedOnUser(hToken)) 365 throw new Win32Exception("Failed to impersonate token"); 366 } 367 LogonUser(string username, string domain, string password, LogonType logonType, LogonProvider logonProvider)368 public static SafeNativeHandle LogonUser(string username, string domain, string password, LogonType logonType, 369 LogonProvider logonProvider) 370 { 371 SafeNativeHandle hToken; 372 if (!NativeMethods.LogonUserW(username, domain, password, logonType, logonProvider, out hToken)) 373 throw new Win32Exception(String.Format("Failed to logon {0}", 374 String.IsNullOrEmpty(domain) ? username : domain + "\\" + username)); 375 376 return hToken; 377 } 378 OpenProcess()379 public static SafeNativeHandle OpenProcess() 380 { 381 return NativeMethods.GetCurrentProcess(); 382 } 383 OpenProcess(Int32 pid, ProcessAccessFlags access, bool inherit)384 public static SafeNativeHandle OpenProcess(Int32 pid, ProcessAccessFlags access, bool inherit) 385 { 386 SafeNativeHandle hProcess = NativeMethods.OpenProcess(access, inherit, (UInt32)pid); 387 if (hProcess.IsInvalid) 388 throw new Win32Exception(String.Format("Failed to open process {0} with access {1}", 389 pid, access.ToString())); 390 391 return hProcess; 392 } 393 OpenProcessToken(SafeNativeHandle hProcess, TokenAccessLevels access)394 public static SafeNativeHandle OpenProcessToken(SafeNativeHandle hProcess, TokenAccessLevels access) 395 { 396 SafeNativeHandle hToken; 397 if (!NativeMethods.OpenProcessToken(hProcess, access, out hToken)) 398 throw new Win32Exception(String.Format("Failed to open proces token with access {0}", 399 access.ToString())); 400 401 return hToken; 402 } 403 RevertToSelf()404 public static void RevertToSelf() 405 { 406 if (!NativeMethods.RevertToSelf()) 407 throw new Win32Exception("Failed to revert thread impersonation"); 408 } 409 GetPrivilegeName(Luid luid)410 internal static string GetPrivilegeName(Luid luid) 411 { 412 UInt32 nameLen = 0; 413 NativeMethods.LookupPrivilegeNameW(null, ref luid, null, ref nameLen); 414 415 StringBuilder name = new StringBuilder((int)(nameLen + 1)); 416 if (!NativeMethods.LookupPrivilegeNameW(null, ref luid, name, ref nameLen)) 417 throw new Win32Exception("LookupPrivilegeName() failed"); 418 419 return name.ToString(); 420 } 421 GetTokenInformation(SafeNativeHandle hToken, NativeHelpers.TokenInformationClass infoClass)422 private static SafeMemoryBuffer GetTokenInformation(SafeNativeHandle hToken, 423 NativeHelpers.TokenInformationClass infoClass) 424 { 425 UInt32 tokenLength; 426 bool res = NativeMethods.GetTokenInformation(hToken, infoClass, new SafeMemoryBuffer(IntPtr.Zero), 0, 427 out tokenLength); 428 int errCode = Marshal.GetLastWin32Error(); 429 if (!res && errCode != 24 && errCode != 122) // ERROR_INSUFFICIENT_BUFFER, ERROR_BAD_LENGTH 430 throw new Win32Exception(errCode, String.Format("GetTokenInformation({0}) failed to get buffer length", 431 infoClass.ToString())); 432 433 SafeMemoryBuffer tokenInfo = new SafeMemoryBuffer((int)tokenLength); 434 if (!NativeMethods.GetTokenInformation(hToken, infoClass, tokenInfo, tokenLength, out tokenLength)) 435 throw new Win32Exception(String.Format("GetTokenInformation({0}) failed", infoClass.ToString())); 436 437 return tokenInfo; 438 } 439 PtrToStructureArray(T[] array, IntPtr ptr)440 private static void PtrToStructureArray<T>(T[] array, IntPtr ptr) 441 { 442 IntPtr ptrOffset = ptr; 443 for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T)))) 444 array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T)); 445 } 446 TryOpenAccessToken(System.Diagnostics.Process process, TokenAccessLevels access)447 private static SafeNativeHandle TryOpenAccessToken(System.Diagnostics.Process process, TokenAccessLevels access) 448 { 449 try 450 { 451 using (SafeNativeHandle hProcess = OpenProcess(process.Id, ProcessAccessFlags.QueryInformation, false)) 452 return OpenProcessToken(hProcess, access); 453 } 454 catch (Win32Exception) 455 { 456 return null; 457 } 458 } 459 } 460 } 461