1 using Microsoft.Win32.SafeHandles;
2 using System;
3 using System.Collections;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Runtime.ConstrainedExecution;
7 using System.Runtime.InteropServices;
8 using System.Security.Principal;
9 using System.Text;
10 
11 namespace Ansible.Privilege
12 {
13     internal class NativeHelpers
14     {
15         [StructLayout(LayoutKind.Sequential)]
16         public struct LUID
17         {
18             public UInt32 LowPart;
19             public Int32 HighPart;
20         }
21 
22         [StructLayout(LayoutKind.Sequential)]
23         public struct LUID_AND_ATTRIBUTES
24         {
25             public LUID Luid;
26             public PrivilegeAttributes Attributes;
27         }
28 
29         [StructLayout(LayoutKind.Sequential)]
30         public struct TOKEN_PRIVILEGES
31         {
32             public UInt32 PrivilegeCount;
33             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
34             public LUID_AND_ATTRIBUTES[] Privileges;
operator stringAnsible.Become.NativeHelpers.LSA_STRING35         }
36     }
37 
38     internal class NativeMethods
39     {
40         [DllImport("advapi32.dll", SetLastError = true)]
41         public static extern bool AdjustTokenPrivileges(
42             SafeNativeHandle TokenHandle,
43             [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
44             SafeMemoryBuffer NewState,
45             UInt32 BufferLength,
46             SafeMemoryBuffer PreviousState,
47             out UInt32 ReturnLength);
48 
49         [DllImport("kernel32.dll")]
50         public static extern bool CloseHandle(
51             IntPtr hObject);
52 
53         [DllImport("kernel32")]
54         public static extern SafeWaitHandle GetCurrentProcess();
55 
56         [DllImport("advapi32.dll", SetLastError = true)]
57         public static extern bool GetTokenInformation(
58             SafeNativeHandle TokenHandle,
59             UInt32 TokenInformationClass,
60             SafeMemoryBuffer TokenInformation,
61             UInt32 TokenInformationLength,
62             out UInt32 ReturnLength);
63 
64         [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
65         public static extern bool LookupPrivilegeName(
66             string lpSystemName,
67             ref NativeHelpers.LUID lpLuid,
68             StringBuilder lpName,
69             ref UInt32 cchName);
70 
71         [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
72         public static extern bool LookupPrivilegeValue(
73             string lpSystemName,
74             string lpName,
75             out NativeHelpers.LUID lpLuid);
76 
77         [DllImport("advapi32.dll", SetLastError = true)]
78         public static extern bool OpenProcessToken(
79             SafeHandle ProcessHandle,
80             TokenAccessLevels DesiredAccess,
81             out SafeNativeHandle TokenHandle);
82     }
83 
84     internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
85     {
86         public SafeMemoryBuffer() : base(true) { }
87         public SafeMemoryBuffer(int cb) : base(true)
88         {
89             base.SetHandle(Marshal.AllocHGlobal(cb));
90         }
91         public SafeMemoryBuffer(IntPtr handle) : base(true)
92         {
93             base.SetHandle(handle);
94         }
95         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
96         protected override bool ReleaseHandle()
97         {
98             Marshal.FreeHGlobal(handle);
99             return true;
100         }
101     }
AllocateLocallyUniqueId( out Luid Luid)102 
103     internal class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid
104     {
105         public SafeNativeHandle() : base(true) { }
106         public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; }
107         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
108         protected override bool ReleaseHandle()
109         {
110             return NativeMethods.CloseHandle(handle);
111         }
112     }
113 
114     public class Win32Exception : System.ComponentModel.Win32Exception
115     {
116         private string _msg;
117         public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
GetCurrentThreadId()118         public Win32Exception(int errorCode, string message) : base(errorCode)
119         {
120             _msg = String.Format("{0} ({1}, Win32ErrorCode {2})", message, base.Message, errorCode);
121         }
122         public override string Message { get { return _msg; } }
123         public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
GetThreadDesktop( UInt32 dwThreadId)124     }
125 
126     [Flags]
127     public enum PrivilegeAttributes : uint
128     {
129         Disabled = 0x00000000,
130         EnabledByDefault = 0x00000001,
131         Enabled = 0x00000002,
132         Removed = 0x00000004,
133         UsedForAccess = 0x80000000,
134     }
135 
LsaGetLogonSessionData( ref Luid LogonId, out SafeLsaMemoryBuffer ppLogonSessionData)136     public class PrivilegeEnabler : IDisposable
137     {
138         private SafeHandle process;
139         private Dictionary<string, bool?> previousState;
140 
141         /// <summary>
142         /// Temporarily enables the privileges specified and reverts once the class is disposed.
143         /// </summary>
144         /// <param name="strict">Whether to fail if any privilege failed to be enabled, if false then this will continue silently</param>
145         /// <param name="privileges">A list of privileges to enable</param>
146         public PrivilegeEnabler(bool strict, params string[] privileges)
147         {
148             if (privileges.Length > 0)
149             {
150                 process = PrivilegeUtil.GetCurrentProcess();
151                 Dictionary<string, bool?> newState = new Dictionary<string, bool?>();
152                 for (int i = 0; i < privileges.Length; i++)
153                     newState.Add(privileges[i], true);
154                 try
155                 {
156                     previousState = PrivilegeUtil.SetTokenPrivileges(process, newState, strict);
157                 }
158                 catch (Win32Exception e)
159                 {
160                     throw new Win32Exception(e.NativeErrorCode, String.Format("Failed to enable privilege(s) {0}", String.Join(", ", privileges)));
161                 }
162             }
163         }
164 
165         public void Dispose()
166         {
167             // disables any privileges that were enabled by this class
168             if (previousState != null)
169                 PrivilegeUtil.SetTokenPrivileges(process, previousState);
170             GC.SuppressFinalize(this);
171         }
172         ~PrivilegeEnabler() { this.Dispose(); }
173     }
174 
175     public class PrivilegeUtil
SafeLsaHandle()176     {
177         private static readonly UInt32 TOKEN_PRIVILEGES = 3;
178 
179         /// <summary>
180         /// Checks if the specific privilege constant is a valid privilege name
181         /// </summary>
182         /// <param name="name">The privilege constant (Se*Privilege) is valid</param>
183         /// <returns>true if valid, else false</returns>
184         public static bool CheckPrivilegeName(string name)
185         {
186             NativeHelpers.LUID luid;
187             if (!NativeMethods.LookupPrivilegeValue(null, name, out luid))
188             {
189                 int errCode = Marshal.GetLastWin32Error();
190                 if (errCode != 1313)  // ERROR_NO_SUCH_PRIVILEGE
191                     throw new Win32Exception(errCode, String.Format("LookupPrivilegeValue({0}) failed", name));
192                 return false;
193             }
194             else
195             {
196                 return true;
197             }
198         }
199 
200         /// <summary>
201         /// Disables the privilege specified
202         /// </summary>
203         /// <param name="token">The process token to that contains the privilege to disable</param>
204         /// <param name="privilege">The privilege constant to disable</param>
205         /// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
206         public static Dictionary<string, bool?> DisablePrivilege(SafeHandle token, string privilege)
207         {
208             return SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, false } });
209         }
210 
211         /// <summary>
212         /// Disables all the privileges
213         /// </summary>
214         /// <param name="token">The process token to that contains the privilege to disable</param>
215         /// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
216         public static Dictionary<string, bool?> DisableAllPrivileges(SafeHandle token)
217         {
218             return AdjustTokenPrivileges(token, null, false);
219         }
220 
221         /// <summary>
222         /// Enables the privilege specified
223         /// </summary>
224         /// <param name="token">The process token to that contains the privilege to enable</param>
225         /// <param name="privilege">The privilege constant to enable</param>
226         /// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
227         public static Dictionary<string, bool?> EnablePrivilege(SafeHandle token, string privilege)
228         {
229             return SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, true } });
230         }
231 
232         /// <summary>
233         /// Get's the status of all the privileges on the token specified
234         /// </summary>
235         /// <param name="token">The process token to get the privilege status on</param>
236         /// <returns>Dictionary where the key is the privilege constant and the value is the PrivilegeAttributes flags</returns>
237         public static Dictionary<String, PrivilegeAttributes> GetAllPrivilegeInfo(SafeHandle token)
238         {
239             SafeNativeHandle hToken = null;
240             if (!NativeMethods.OpenProcessToken(token, TokenAccessLevels.Query, out hToken))
241                 throw new Win32Exception("OpenProcessToken() failed");
242 
243             using (hToken)
244             {
245                 UInt32 tokenLength = 0;
246                 NativeMethods.GetTokenInformation(hToken, TOKEN_PRIVILEGES, new SafeMemoryBuffer(0), 0, out tokenLength);
247 
248                 NativeHelpers.LUID_AND_ATTRIBUTES[] privileges;
249                 using (SafeMemoryBuffer privilegesPtr = new SafeMemoryBuffer((int)tokenLength))
250                 {
251                     if (!NativeMethods.GetTokenInformation(hToken, TOKEN_PRIVILEGES, privilegesPtr, tokenLength, out tokenLength))
252                         throw new Win32Exception("GetTokenInformation() for TOKEN_PRIVILEGES failed");
253 
254                     NativeHelpers.TOKEN_PRIVILEGES privilegeInfo = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure(
255                         privilegesPtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_PRIVILEGES));
256                     privileges = new NativeHelpers.LUID_AND_ATTRIBUTES[privilegeInfo.PrivilegeCount];
257                     PtrToStructureArray(privileges, IntPtr.Add(privilegesPtr.DangerousGetHandle(), Marshal.SizeOf(privilegeInfo.PrivilegeCount)));
258                 }
259 
260                 return privileges.ToDictionary(p => GetPrivilegeName(p.Luid), p => p.Attributes);
261             }
262         }
263 
264         /// <summary>
265         /// Get a handle to the current process for use with the methods above
266         /// </summary>
267         /// <returns>SafeWaitHandle handle of the current process token</returns>
268         public static SafeWaitHandle GetCurrentProcess()
269         {
270             return NativeMethods.GetCurrentProcess();
271         }
272 
273         /// <summary>
274         /// Removes a privilege from the token. This operation is irreversible
275         /// </summary>
276         /// <param name="token">The process token to that contains the privilege to remove</param>
277         /// <param name="privilege">The privilege constant to remove</param>
278         public static void RemovePrivilege(SafeHandle token, string privilege)
279         {
280             SetTokenPrivileges(token, new Dictionary<string, bool?>() { { privilege, null } });
281         }
282 
283         /// <summary>
284         /// Do a bulk set of multiple privileges
285         /// </summary>
286         /// <param name="token">The process token to use when setting the privilege state</param>
287         /// <param name="state">A dictionary that contains the privileges to set, the key is the constant name and the value can be;
288         ///     true - enable the privilege
289         ///     false - disable the privilege
290         ///     null - remove the privilege (this cannot be reversed)
291         /// </param>
292         /// <param name="strict">When true, will fail if one privilege failed to be set, otherwise it will silently continue</param>
293         /// <returns>The previous state that can be passed to SetTokenPrivileges to revert the action</returns>
294         public static Dictionary<string, bool?> SetTokenPrivileges(SafeHandle token, IDictionary state, bool strict = true)
295         {
296             NativeHelpers.LUID_AND_ATTRIBUTES[] privilegeAttr = new NativeHelpers.LUID_AND_ATTRIBUTES[state.Count];
297             int i = 0;
298 
299             foreach (DictionaryEntry entry in state)
300             {
301                 string key = (string)entry.Key;
302                 NativeHelpers.LUID luid;
303                 if (!NativeMethods.LookupPrivilegeValue(null, key, out luid))
304                     throw new Win32Exception(String.Format("LookupPrivilegeValue({0}) failed", key));
305 
306                 PrivilegeAttributes attributes;
307                 switch ((bool?)entry.Value)
308                 {
309                     case true:
310                         attributes = PrivilegeAttributes.Enabled;
311                         break;
312                     case false:
313                         attributes = PrivilegeAttributes.Disabled;
314                         break;
315                     default:
316                         attributes = PrivilegeAttributes.Removed;
317                         break;
318                 }
319 
320                 privilegeAttr[i].Luid = luid;
321                 privilegeAttr[i].Attributes = attributes;
322                 i++;
323             }
324 
325             return AdjustTokenPrivileges(token, privilegeAttr, strict);
326         }
327 
328         private static Dictionary<string, bool?> AdjustTokenPrivileges(SafeHandle token, NativeHelpers.LUID_AND_ATTRIBUTES[] newState, bool strict)
329         {
330             bool disableAllPrivileges;
331             SafeMemoryBuffer newStatePtr;
332             NativeHelpers.LUID_AND_ATTRIBUTES[] oldStatePrivileges;
333             UInt32 returnLength;
334 
335             if (newState == null)
336             {
337                 disableAllPrivileges = true;
338                 newStatePtr = new SafeMemoryBuffer(0);
339             }
340             else
341             {
342                 disableAllPrivileges = false;
343 
344                 // Need to manually marshal the bytes requires for newState as the constant size
345                 // of LUID_AND_ATTRIBUTES is set to 1 and can't be overridden at runtime, TOKEN_PRIVILEGES
346                 // always contains at least 1 entry so we need to calculate the extra size if there are
347                 // nore than 1 LUID_AND_ATTRIBUTES entry
348                 int tokenPrivilegesSize = Marshal.SizeOf(typeof(NativeHelpers.TOKEN_PRIVILEGES));
349                 int luidAttrSize = 0;
350                 if (newState.Length > 1)
351                     luidAttrSize = Marshal.SizeOf(typeof(NativeHelpers.LUID_AND_ATTRIBUTES)) * (newState.Length - 1);
352                 int totalSize = tokenPrivilegesSize + luidAttrSize;
353                 byte[] newStateBytes = new byte[totalSize];
354 
355                 // get the first entry that includes the struct details
356                 NativeHelpers.TOKEN_PRIVILEGES tokenPrivileges = new NativeHelpers.TOKEN_PRIVILEGES()
357                 {
358                     PrivilegeCount = (UInt32)newState.Length,
359                     Privileges = new NativeHelpers.LUID_AND_ATTRIBUTES[1],
360                 };
361                 if (newState.Length > 0)
362                     tokenPrivileges.Privileges[0] = newState[0];
363                 int offset = StructureToBytes(tokenPrivileges, newStateBytes, 0);
364 
365                 // copy the remaining LUID_AND_ATTRIBUTES (if any)
366                 for (int i = 1; i < newState.Length; i++)
367                     offset += StructureToBytes(newState[i], newStateBytes, offset);
368 
369                 // finally create the pointer to the byte array we just created
370                 newStatePtr = new SafeMemoryBuffer(newStateBytes.Length);
371                 Marshal.Copy(newStateBytes, 0, newStatePtr.DangerousGetHandle(), newStateBytes.Length);
372             }
373 
374             using (newStatePtr)
375             {
376                 SafeNativeHandle hToken;
377                 if (!NativeMethods.OpenProcessToken(token, TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, out hToken))
378                     throw new Win32Exception("OpenProcessToken() failed with Query and AdjustPrivileges");
379 
380                 using (hToken)
381                 {
382                     if (!NativeMethods.AdjustTokenPrivileges(hToken, disableAllPrivileges, newStatePtr, 0, new SafeMemoryBuffer(0), out returnLength))
383                     {
384                         int errCode = Marshal.GetLastWin32Error();
385                         if (errCode != 122) // ERROR_INSUFFICIENT_BUFFER
386                             throw new Win32Exception(errCode, "AdjustTokenPrivileges() failed to get old state size");
387                     }
388 
389                     using (SafeMemoryBuffer oldStatePtr = new SafeMemoryBuffer((int)returnLength))
390                     {
391                         bool res = NativeMethods.AdjustTokenPrivileges(hToken, disableAllPrivileges, newStatePtr, returnLength, oldStatePtr, out returnLength);
392                         int errCode = Marshal.GetLastWin32Error();
393 
394                         // even when res == true, ERROR_NOT_ALL_ASSIGNED may be set as the last error code
395                         // fail if we are running with strict, otherwise ignore those privileges
396                         if (!res || ((strict && errCode != 0) || (!strict && !(errCode == 0 || errCode == 0x00000514))))
397                             throw new Win32Exception(errCode, "AdjustTokenPrivileges() failed");
398 
399                         // Marshal the oldStatePtr to the struct
400                         NativeHelpers.TOKEN_PRIVILEGES oldState = (NativeHelpers.TOKEN_PRIVILEGES)Marshal.PtrToStructure(
401                             oldStatePtr.DangerousGetHandle(), typeof(NativeHelpers.TOKEN_PRIVILEGES));
402                         oldStatePrivileges = new NativeHelpers.LUID_AND_ATTRIBUTES[oldState.PrivilegeCount];
403                         PtrToStructureArray(oldStatePrivileges, IntPtr.Add(oldStatePtr.DangerousGetHandle(), Marshal.SizeOf(oldState.PrivilegeCount)));
404                     }
405                 }
406             }
407 
408             return oldStatePrivileges.ToDictionary(p => GetPrivilegeName(p.Luid), p => (bool?)p.Attributes.HasFlag(PrivilegeAttributes.Enabled));
409         }
410 
411         private static string GetPrivilegeName(NativeHelpers.LUID luid)
412         {
413             UInt32 nameLen = 0;
414             NativeMethods.LookupPrivilegeName(null, ref luid, null, ref nameLen);
415 
416             StringBuilder name = new StringBuilder((int)(nameLen + 1));
417             if (!NativeMethods.LookupPrivilegeName(null, ref luid, name, ref nameLen))
418                 throw new Win32Exception("LookupPrivilegeName() failed");
419 
420             return name.ToString();
421         }
422 
423         private static void PtrToStructureArray<T>(T[] array, IntPtr ptr)
424         {
425             IntPtr ptrOffset = ptr;
426             for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
427                 array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
428         }
429 
430         private static int StructureToBytes<T>(T structure, byte[] array, int offset)
431         {
432             int size = Marshal.SizeOf(structure);
433             using (SafeMemoryBuffer structPtr = new SafeMemoryBuffer(size))
434             {
435                 Marshal.StructureToPtr(structure, structPtr.DangerousGetHandle(), false);
436                 Marshal.Copy(structPtr.DangerousGetHandle(), array, offset, size);
437             }
438 
439             return size;
440         }
441     }
442 }
443 
444