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