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