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,
write_to_file_descriptor(fd, obj)47             TokenElevationType = 18,
48             TokenLinkedToken = 19,
49         }
50     }
51 
52     internal class NativeMethods
53     {
54         [DllImport("kernel32.dll", SetLastError = true)]
55         public static extern bool CloseHandle(
56             IntPtr hObject);
57 
58         [DllImport("advapi32.dll", SetLastError = true)]
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")]
68         public static extern SafeNativeHandle GetCurrentProcess();
69 
70         [DllImport("advapi32.dll", SetLastError = true)]
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)]
79         public static extern bool ImpersonateLoggedOnUser(
80             SafeNativeHandle hToken);
81 
82         [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
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)]
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)]
99         public static extern SafeNativeHandle OpenProcess(
100             ProcessAccessFlags dwDesiredAccess,
101             bool bInheritHandle,
102             UInt32 dwProcessId);
103 
104         [DllImport("advapi32.dll", SetLastError = true)]
105         public static extern bool OpenProcessToken(
106             SafeNativeHandle ProcessHandle,
107             TokenAccessLevels DesiredAccess,
108             out SafeNativeHandle TokenHandle);
109 
110         [DllImport("advapi32.dll", SetLastError = true)]
111         public static extern bool RevertToSelf();
112     }
113 
114     internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
115     {
116         public SafeMemoryBuffer() : base(true) { }
117         public SafeMemoryBuffer(int cb) : base(true)
118         {
119             base.SetHandle(Marshal.AllocHGlobal(cb));
120         }
121         public SafeMemoryBuffer(IntPtr handle) : base(true)
122         {
123             base.SetHandle(handle);
124         }
125 
126         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
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 
212         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 
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     {
247         public SafeNativeHandle() : base(true) { }
248         public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; }
249 
250         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
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 
261         public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), 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; } }
268         public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
269     }
270 
271     public class TokenUtil
272     {
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 
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 
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 
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 
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 
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 
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 
362         public static void ImpersonateToken(SafeNativeHandle hToken)
363         {
364             if (!NativeMethods.ImpersonateLoggedOnUser(hToken))
365                 throw new Win32Exception("Failed to impersonate token");
366         }
367 
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 
379         public static SafeNativeHandle OpenProcess()
380         {
381             return NativeMethods.GetCurrentProcess();
382         }
383 
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 
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 
404         public static void RevertToSelf()
405         {
406             if (!NativeMethods.RevertToSelf())
407                 throw new Win32Exception("Failed to revert thread impersonation");
408         }
409 
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 
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 
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 
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