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