using System;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Collections.Generic;
using System.Text;
namespace AccessToken
{
class AccessCheckWrap
{
/* Generic mask mappings aren't yet supported for .NET framework, so we'll put it here. */
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
internal struct GENERIC_MAPPING
{
internal uint GenericRead;
internal uint GenericWrite;
internal uint GenericExecute;
internal uint GenericAll;
}
[System.Runtime.InteropServices.DllImport("advapi32",
CallingConvention = System.Runtime.InteropServices.CallingConvention.StdCall)]
internal static extern void MapGenericMask(ref uint AccessMask, ref GENERIC_MAPPING GenericMapping);
/* C# doesn't support struct initializers for class members here, so I use this workaround function */
internal static GENERIC_MAPPING RegGeneric()
{
GENERIC_MAPPING genericMapping = new GENERIC_MAPPING();
genericMapping.GenericRead = (uint)(System.Security.AccessControl.RegistryRights.ReadKey);
genericMapping.GenericWrite = (uint)(System.Security.AccessControl.RegistryRights.WriteKey);
genericMapping.GenericExecute = (uint)(System.Security.AccessControl.RegistryRights.ExecuteKey);
genericMapping.GenericAll = (uint)(System.Security.AccessControl.RegistryRights.FullControl);
return genericMapping;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID_AND_ATTRIBUTES
{
public Int64 Luid;
public int Attributes;
}
private const Int32 ANYSIZE_ARRAY = 1;
[StructLayout(LayoutKind.Sequential)]
protected struct PRIVILEGE_SET
{
public uint PrivilegeCount;
public uint Control;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = ANYSIZE_ARRAY)]
public LUID_AND_ATTRIBUTES[] Privilege;
}
/* Wrapper class for the AccessCheck API. */
[DllImport("advapi32", SetLastError=true)]
protected static extern bool AccessCheck(
[MarshalAs(UnmanagedType.LPArray)]
byte [] pSecurityDescriptor,
IntPtr ClientToken,
uint DesiredAccess,
[In] ref GENERIC_MAPPING GenericMapping,
IntPtr PrivilegeSet,
ref uint PrivilegeSetLength,
out uint GrantedAccess,
out bool AccessStatus);
/// <summary>
/// This function provides a managed wrapper over the AccessCheck API, minus the rubbish
/// (And AccessCheck does have a lot of useless parameters).
/// </summary>
/// <param name="regKey">The security descriptor of the registry key.</param>
/// <param name="DesiredAccess">The rights you want to the object.</param>
/// <returns>true if access is allowed, false for access denied. May except with a Win32Exception.</returns>
public static bool DoRegistryAccessCheck(RegistrySecurity regKey, uint DesiredAccess)
{
using (ManagedTokenHandle procHandle = AccessToken.GetAccessToken(TokenAccessLevels.Duplicate))
using (AccessToken procToken = new AccessToken(procHandle.HandleInternal))
using (ManagedTokenHandle userToken = procToken.CreateImpersonationToken(TokenImpersonationLevel.Impersonation))
{
uint PrivilegeSetLength = 0, GrantedAccess = 0;
bool AccessStatus = false;
IntPtr PrivilegeSet = IntPtr.Zero;
byte [] RawForm = regKey.GetSecurityDescriptorBinaryForm();
GENERIC_MAPPING genericRegMapping = RegGeneric();
MapGenericMask(ref DesiredAccess, ref genericRegMapping);
/* The first call to AccessCheck is designed to fail. */
AccessCheck(RawForm, userToken.HandleInternal, DesiredAccess, ref genericRegMapping,
PrivilegeSet, ref PrivilegeSetLength, out GrantedAccess, out AccessStatus);
PrivilegeSet = Marshal.AllocHGlobal(Convert.ToInt32(PrivilegeSetLength));
if (!AccessCheck(RawForm, userToken.HandleInternal, DesiredAccess, ref genericRegMapping, PrivilegeSet,
ref PrivilegeSetLength, out GrantedAccess, out AccessStatus))
{
throw new System.ComponentModel.Win32Exception();
}
/* AccessStatus should be true, and GrantedAccess should be equal to DesiredAccess */
if (AccessStatus == true && DesiredAccess == GrantedAccess)
return true;
else return false;
}
}
/// <summary>
/// Unlike DoRegistryAccessCheck, this function doesn't Pinvoke to the Advapi32 AccessCheck functions.
/// Instead, it makes the access check itself, using its own algorithm.
/// </summary>
/// <param name="regKey">the security descriptor for the registry key</param>
/// <param name="DesiredAccess">What you want to do with the registry key (RegistryRight cast as uint)</param>
/// <returns>true for access approved, false for access denied</returns>
public static bool DoManualAccess(RegistrySecurity regKey, uint DesiredAccess)
{
/* returns false if the user is likely to get Access denied. */
if(regKey == null)
{/* Success any NULL DACLs */
return true;
}
if(regKey.GetOwner(typeof(SecurityIdentifier)) == WindowsIdentity.GetCurrent().User)
{/* The owner has ChangePermissions | ReadControl */
DesiredAccess &= ~((uint)RegistryRights.ChangePermissions | (uint)RegistryRights.ReadPermissions);
}
/** BUGBUG: If there is a deny ACE that is present in our token, it's not checked properly against
* deny SIDs. As a result, access may be granted rather than denied.
*
* For AccCheckFrm and DACLForm, we are saved because we open up the registry key later, and that
* Will fail as expected.
**/
foreach (RegistryAccessRule Entry in
regKey.GetAccessRules(true, true, typeof(SecurityIdentifier)))
{
WindowsPrincipal userSID = new WindowsPrincipal(WindowsIdentity.GetCurrent());
/* Remap generic rights. */
uint accessMask = (uint)(Entry.RegistryRights);
GENERIC_MAPPING genericReg = RegGeneric();
MapGenericMask(ref accessMask, ref genericReg);
/* IsInRole handles most of the Group stuff for us. */
if (userSID.IsInRole((SecurityIdentifier)(Entry.IdentityReference)))
{
/* This right applies to us. */
if ((accessMask & DesiredAccess) != 0)
{/* This right holds our desired Registry permission. If its allow, true, otherwise false */
if (Entry.AccessControlType == AccessControlType.Deny)
{/* Some rights are disallowed. You're gonna get an error 5 */
return false;
}
else
{/* NAND out the Access flags from DesiredAccess. */
DesiredAccess &= ~(accessMask);
}
}
}
}
/* At the end, DesiredAccess should be 0 */
if (DesiredAccess == 0) return true;
return false;
}
}
}