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;
35         }
36     }
37 
38     internal class NativeMethods
39     {
40         [DllImport("advapi32.dll", SetLastError = true)]
AdjustTokenPrivileges( SafeNativeHandle TokenHandle, [MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges, SafeMemoryBuffer NewState, UInt32 BufferLength, SafeMemoryBuffer PreviousState, out UInt32 ReturnLength)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")]
CloseHandle( IntPtr hObject)50         public static extern bool CloseHandle(
51             IntPtr hObject);
52 
53         [DllImport("kernel32")]
GetCurrentProcess()54         public static extern SafeWaitHandle GetCurrentProcess();
55 
56         [DllImport("advapi32.dll", SetLastError = true)]
GetTokenInformation( SafeNativeHandle TokenHandle, UInt32 TokenInformationClass, SafeMemoryBuffer TokenInformation, UInt32 TokenInformationLength, out UInt32 ReturnLength)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)]
LookupPrivilegeName( string lpSystemName, ref NativeHelpers.LUID lpLuid, StringBuilder lpName, ref UInt32 cchName)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)]
LookupPrivilegeValue( string lpSystemName, string lpName, out NativeHelpers.LUID lpLuid)72         public static extern bool LookupPrivilegeValue(
73             string lpSystemName,
74             string lpName,
75             out NativeHelpers.LUID lpLuid);
76 
77         [DllImport("advapi32.dll", SetLastError = true)]
OpenProcessToken( SafeHandle ProcessHandle, TokenAccessLevels DesiredAccess, out SafeNativeHandle TokenHandle)78         public static extern bool OpenProcessToken(
79             SafeHandle ProcessHandle,
80             TokenAccessLevels DesiredAccess,
81             out SafeNativeHandle TokenHandle);
82     }
83 
84     internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
85     {
SafeMemoryBuffer()86         public SafeMemoryBuffer() : base(true) { }
SafeMemoryBuffer(int cb)87         public SafeMemoryBuffer(int cb) : base(true)
88         {
89             base.SetHandle(Marshal.AllocHGlobal(cb));
90         }
SafeMemoryBuffer(IntPtr handle)91         public SafeMemoryBuffer(IntPtr handle) : base(true)
92         {
93             base.SetHandle(handle);
94         }
95         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
ReleaseHandle()96         protected override bool ReleaseHandle()
97         {
98             Marshal.FreeHGlobal(handle);
99             return true;
100         }
101     }
102 
103     internal class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid
104     {
SafeNativeHandle()105         public SafeNativeHandle() : base(true) { }
SafeNativeHandle(IntPtr handle)106         public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; }
107         [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
ReleaseHandle()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;
Win32Exception(string message)117         public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
Win32Exception(int errorCode, string message)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; } }
operator Win32Exception(string message)123         public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
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 
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>
PrivilegeEnabler(bool strict, params string[] privileges)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 
Dispose()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         }
~PrivilegeEnabler()172         ~PrivilegeEnabler() { this.Dispose(); }
173     }
174 
175     public class PrivilegeUtil
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>
CheckPrivilegeName(string name)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>
DisablePrivilege(SafeHandle token, string privilege)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>
DisableAllPrivileges(SafeHandle token)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>
EnablePrivilege(SafeHandle token, string privilege)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>
GetAllPrivilegeInfo(SafeHandle token)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>
GetCurrentProcess()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>
RemovePrivilege(SafeHandle token, string privilege)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>
SetTokenPrivileges(SafeHandle token, IDictionary state, bool strict = true)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 
AdjustTokenPrivileges(SafeHandle token, NativeHelpers.LUID_AND_ATTRIBUTES[] newState, bool strict)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 
GetPrivilegeName(NativeHelpers.LUID luid)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 
PtrToStructureArray(T[] array, IntPtr ptr)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 
StructureToBytes(T structure, byte[] array, int offset)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