Click here to Skip to main content
15,888,610 members
Articles / Programming Languages / C

The Windows Access Control Model: Part 2

Rate me:
Please Sign up or sign in to vote.
4.80/5 (28 votes)
27 Jun 2005CPOL43 min read 245K   7.2K   113  
This second part of the Access Control series will program with the basic Access Control structures.
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#define _WIN32_WINNT 0x400
#define _CRTDBG_MAP_ALLOC

#include<aclapi.h>
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<crtdbg.h>
#include<tchar.h>



//***********************************************
BOOL GetTextualSid(PSID pSid, LPTSTR TextualSid, LPDWORD lpdwBufferLen)
{/* This is a helper function from http://www.codeproject.com/system/sid.asp */
	PSID_IDENTIFIER_AUTHORITY psia;
	DWORD dwSubAuthorities;
	DWORD dwSidRev=SID_REVISION;
	DWORD dwCounter;
	DWORD dwSidSize;

	// Validate the binary SID.

	if(!IsValidSid(pSid)) return FALSE;

	// Get the identifier authority value from the SID.

	psia = GetSidIdentifierAuthority(pSid);

	// Get the number of subauthorities in the SID.

	dwSubAuthorities = *GetSidSubAuthorityCount(pSid);

	// Compute the buffer length.
	// S-SID_REVISION- + IdentifierAuthority- + subauthorities- + NULL

	dwSidSize=(15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR);

	// Check input buffer length.
	// If too small, indicate the proper size and set the last error.

	if (*lpdwBufferLen < dwSidSize || TextualSid == NULL)
	{
		*lpdwBufferLen = dwSidSize;
		SetLastError(ERROR_INSUFFICIENT_BUFFER);
		return FALSE;
	}

	// Add 'S' prefix and revision number to the string.

	dwSidSize=wsprintf(TextualSid, TEXT("S-%lu-"), dwSidRev );

	// Add a SID identifier authority to the string.

	if ( (psia->Value[0] != 0) || (psia->Value[1] != 0) )
	{
		dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid),
			TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"),
			(USHORT)psia->Value[0],
			(USHORT)psia->Value[1],
			(USHORT)psia->Value[2],
			(USHORT)psia->Value[3],
			(USHORT)psia->Value[4],
			(USHORT)psia->Value[5]);
	}
	else
	{
		dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid),
			TEXT("%lu"),
			(ULONG)(psia->Value[5]      )   +
			(ULONG)(psia->Value[4] <<  8)   +
			(ULONG)(psia->Value[3] << 16)   +
			(ULONG)(psia->Value[2] << 24)   );
	}

	// Add SID subauthorities to the string.
	//
	for (dwCounter=0 ; dwCounter < dwSubAuthorities ; dwCounter++)
	{
		dwSidSize+=wsprintf(TextualSid + dwSidSize, TEXT("-%lu"),
			*GetSidSubAuthority(pSid, dwCounter) );
	}

	return TRUE;
}


int WellKnownSid2Trustee(void)
{/* Q1. */
	const TCHAR SystemSid[] = _T("SYSTEM");	/* SYSTEM */
	PSID SidUser = NULL;	/* This is where the resultant SID is returned */
	DWORD cchName = 0, cchReferencedDomainName = 0;	/* Will hold buffer sizes */
	LPTSTR SidText = NULL;	/* Will hold the final SID */
	LPTSTR ReferencedDomainName = NULL;	/* Dummy variable */
	SID_NAME_USE peUse = SidTypeUser;	/* Dummy variable */


	/* First Call LookupAccountName() */
	LookupAccountName(NULL, SystemSid, SidUser, &cchName, NULL, &cchReferencedDomainName, &peUse);
	/* This was expected to fail. We should have allocated large enough buffers */

	SidUser = (PSID)calloc(cchName, 1);
	if(SidUser == NULL)
	{
		return 1;
	}

	ReferencedDomainName = (LPTSTR)calloc(cchReferencedDomainName, sizeof(TCHAR));
	if(ReferencedDomainName == NULL)
	{
		free(SidUser); SidUser = NULL;
		return 1;
	}

	/* Now let's try that again. */
	if(!LookupAccountName(NULL, SystemSid, SidUser, &cchName, ReferencedDomainName, &cchReferencedDomainName, &peUse))
	{
		free(SidUser); SidUser = NULL;
		free(ReferencedDomainName); ReferencedDomainName = NULL;
		return 1;
	}
	free(ReferencedDomainName); ReferencedDomainName = NULL;

	/* Now we have the SID: return the textual representation of that. */
	GetTextualSid(SidUser, NULL, &cchName);

	/* Did we forget something? */
	SidText = (TCHAR *)calloc(cchName, sizeof(TCHAR));
	if(SidText == NULL)
	{
		free(SidUser); SidUser = NULL;
		return 1;
	}
	GetTextualSid(SidUser, SidText, &cchName);

	/* Why would you need a TRUSTEE structure? It's rather useless to you. */
	{
		TRUSTEE TrusteeSid = {0};
		TrusteeSid.pMultipleTrustee = NULL;
		TrusteeSid.ptstrName = (LPTSTR)(SidUser);
		TrusteeSid.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
		TrusteeSid.TrusteeForm = TRUSTEE_IS_SID;
		TrusteeSid.TrusteeType = TRUSTEE_IS_UNKNOWN;
	}


	free(SidUser); SidUser = NULL;

	/* That should be it. */
	_tprintf(_T("%s"), SidText);
	free(SidText); SidText = NULL;


	return 0;
}


#define MAX_USERNAME_LENGTH 256

int Sid2UserName(const PSID UserSid, OUT LPTSTR ReferencedDomainName, OUT LPTSTR UserName)
{
	DWORD cchName = MAX_USERNAME_LENGTH, cchReferencedDomainName = MAX_USERNAME_LENGTH;
	SID_NAME_USE peUse = SidTypeUser;


	if(UserName==NULL)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		return 1;
	}

	/* Get the username */
	LookupAccountSid(NULL, UserSid, UserName, &cchName, ReferencedDomainName, &cchReferencedDomainName, &peUse);

	return 0;
}



int Sid2UserNamePrepare(void)
{
	/* Setup the current thread token. */
	HANDLE hToken = NULL;
	DWORD TokenInformationLength = 0, ReturnLength = 0;
	TOKEN_USER *TokenInformation = NULL;
	TCHAR UserName[MAX_USERNAME_LENGTH] = _T("");

	if(!OpenThreadToken(GetCurrentThread(), TOKEN_READ, TRUE, &hToken))
	{
		OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken);
	}

	/* Get the current user from GetTokenInformation(TokenUser) */
	GetTokenInformation(hToken, TokenUser, NULL, TokenInformationLength, &ReturnLength);
	TokenInformation = (TOKEN_USER *)calloc(ReturnLength, 1);
	if(TokenInformation == NULL)
	{
		CloseHandle(hToken); hToken = NULL;
		return 1;
	}
	TokenInformationLength = ReturnLength;

	if(GetTokenInformation(hToken, TokenUser, TokenInformation, TokenInformationLength, &ReturnLength))
	{/* Now that we have the SID, convert it to a user name */
		TCHAR ReferencedDomainName[MAX_USERNAME_LENGTH] = _T("");
		Sid2UserName(TokenInformation->User.Sid, ReferencedDomainName, UserName);
		_tprintf(_T("%s\\%s"), ReferencedDomainName, UserName);
	}

	free(TokenInformation); TokenInformation = NULL;
	CloseHandle(hToken); hToken = NULL;
	return 0;
}




BOOL IsAdminRunning(void)
{
	BOOL IsMember = FALSE;
	size_t i = 0;
	SID_IDENTIFIER_AUTHORITY peUse = SECURITY_NT_AUTHORITY;
	PSID SidUser = NULL;
	HANDLE ProcToken = NULL;
	LPVOID TokenInformation = NULL;
	TOKEN_GROUPS *TokenGroupList = NULL;
	DWORD TokenInformationLength = 0, ReturnLength = 0;

	/* Create a SID with the Admins RID */
	if(!AllocateAndInitializeSid(&peUse, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0,
		0, 0, 0, 0, 0, &SidUser))
	{
		return FALSE;
	}

	if(!OpenThreadToken(GetCurrentThread(), TOKEN_READ | TOKEN_DUPLICATE, TRUE, &ProcToken))
	{
		if(!OpenProcessToken(GetCurrentProcess(), TOKEN_READ | TOKEN_DUPLICATE, &ProcToken))
		{
			FreeSid(SidUser); SidUser = NULL;
			return FALSE;
		}
	}
	// DuplicateToken(ProcToken, SecurityImpersonation, &ImpersonationToken);

	/* Call GetTokenInformation(TokenGroups) */
	GetTokenInformation(ProcToken, TokenGroups, TokenInformation, TokenInformationLength, &ReturnLength);
	TokenInformationLength = ReturnLength;
	TokenInformation = calloc(TokenInformationLength, sizeof(BYTE));
	if(TokenInformation == NULL)
	{
		FreeSid(SidUser); SidUser = NULL;
		CloseHandle(ProcToken); ProcToken = NULL;
		return FALSE;
	}

	GetTokenInformation(ProcToken, TokenGroups, TokenInformation, TokenInformationLength, &ReturnLength);
	TokenGroupList = (TOKEN_GROUPS *)(TokenInformation);

	for(i = 0; i < TokenGroupList->GroupCount ; i++)
	{/* Search for the SID in the Groups Array */
		if(EqualSid(TokenGroupList->Groups[i].Sid, SidUser))
		{/* If found, and is enabled, we are an admin */
			if(TokenGroupList->Groups[i].Attributes & SE_GROUP_ENABLED ||
				TokenGroupList->Groups[i].Attributes & SE_GROUP_ENABLED_BY_DEFAULT )
			{
				IsMember = TRUE;
				break;
			}
		}
	}


	FreeSid(SidUser); SidUser = NULL;
	CloseHandle(ProcToken); ProcToken = NULL;
	free(TokenInformation); TokenInformation = NULL;

	return IsMember;
}


BOOL DoWhoAmI(void)
{
	/* returns false on failure */
	// BOOL bResult = FALSE;
	size_t i = 0;
	HANDLE ProcToken = NULL;
	TOKEN_GROUPS_AND_PRIVILEGES *tp = NULL;
	DWORD ReturnLength = 0, TokenInformationLength = 0;
	TCHAR *PrivilegeName = NULL;

	if( !OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, TRUE, &ProcToken) &&
		!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &ProcToken))
	{
		return FALSE;
	}

	/* Call GetTokenInformation(TokenGroupsAndPrivileges) */
	GetTokenInformation(ProcToken, TokenGroupsAndPrivileges, tp, TokenInformationLength, &ReturnLength);
	tp = (TOKEN_GROUPS_AND_PRIVILEGES *)(calloc(ReturnLength, 1));
	if(!tp)
	{
		CloseHandle(ProcToken); ProcToken = NULL;
		return FALSE;
	}
	TokenInformationLength = ReturnLength;
	GetTokenInformation(ProcToken, TokenGroupsAndPrivileges, tp, TokenInformationLength, &ReturnLength);

	/* Start off with AuthenticationId */
	_tprintf(_T("\r\nAuthentication ID:%d%d\r\n"), (tp->AuthenticationId.HighPart), (tp->AuthenticationId.LowPart));
	_tprintf(_T("\r\nPrivileges\r\n"));
	for(i = 0; i < tp->SidCount; i++)
	{/* Groups */
		TCHAR UserName[MAX_USERNAME_LENGTH] = _T("");
		TCHAR ReferencedDomainName[MAX_USERNAME_LENGTH] = _T("");
		Sid2UserName(tp->Sids[i].Sid, ReferencedDomainName, UserName);
		_tprintf(_T("\r\n%s\\%s: %d"), ReferencedDomainName, UserName, tp->Sids[i].Attributes);
	}

	_tprintf(_T("\r\nRestricted Sids\r\n"));
	for(i = 0; i < tp->RestrictedSidCount; i++)
	{/* Restricted Sids */
		TCHAR UserName[MAX_USERNAME_LENGTH] = _T("");
		TCHAR ReferencedDomainName[MAX_USERNAME_LENGTH] = _T("");
		Sid2UserName(tp->Sids[i].Sid, ReferencedDomainName, UserName);
		_tprintf(_T("\r\n%s\\%s: %d"), UserName, ReferencedDomainName, tp->RestrictedSids[i].Attributes);
	}

	_tprintf(_T("\r\nPrivileges\r\n"));
	for(i = 0; i < tp->PrivilegeCount; i++)
	{/* Privileges */
		ReturnLength = 0;
		LookupPrivilegeName(NULL, &tp->Privileges[i].Luid, NULL, &ReturnLength);
		ReturnLength++;
		/* Docs are rather unclear on the null terminator ambiguity. Better safe than sorry. */
		PrivilegeName = (TCHAR *)calloc(ReturnLength, sizeof(TCHAR));
		if(PrivilegeName == NULL)
			break;
		LookupPrivilegeName(NULL, &tp->Privileges[i].Luid, PrivilegeName, &ReturnLength);

		_tprintf(_T("\r\n%s: %d"), PrivilegeName, tp->Privileges[i].Attributes);
		free(PrivilegeName); PrivilegeName = NULL;
	}


	CloseHandle(ProcToken); ProcToken = NULL;
	free(tp); tp = NULL;
	return TRUE;
}



BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)
{
	/* returns false on failure */
	BOOL bResult = FALSE;
	HANDLE ProcToken = NULL;
	TOKEN_PRIVILEGES tp = {0};
	LUID luid = {0};

	if( !OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, TRUE, &ProcToken) &&
		!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &ProcToken))
	{
		return FALSE;
	}

	if(!LookupPrivilegeValue(NULL, lpszPrivilege, &luid))
	{
		CloseHandle(ProcToken); ProcToken = NULL;
		return FALSE;
	}

	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	tp.Privileges[0].Attributes = (bEnablePrivilege) ? SE_PRIVILEGE_ENABLED : 0;

	/* Enable the privilege or disable all privileges. */
	SetLastError(ERROR_SUCCESS);
	bResult = (!AdjustTokenPrivileges(ProcToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL) ||
		GetLastError() != ERROR_SUCCESS) ;
	CloseHandle(ProcToken); ProcToken = NULL;
	return bResult;
}


PSECURITY_DESCRIPTOR GetSecurityDesc(LPCTSTR FileName)
{
	PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
	DWORD LengthNeeded = 0;
	/* Find the length needed for the security descriptor */
	GetFileSecurity(FileName, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
		| SACL_SECURITY_INFORMATION, NULL, 0, &LengthNeeded);
	pSecurityDescriptor = (PSECURITY_DESCRIPTOR)(LocalAlloc(LPTR, LengthNeeded));
	if(pSecurityDescriptor == NULL)
	{
		return NULL;
	}
	SetLastError(ERROR_SUCCESS);
	GetFileSecurity(FileName, GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION
		| SACL_SECURITY_INFORMATION, pSecurityDescriptor, LengthNeeded, &LengthNeeded);

	return pSecurityDescriptor;
}


PSECURITY_DESCRIPTOR ConvertSecurityDescriptor(PSECURITY_DESCRIPTOR *ppSDIn)
{
	PACL AbsDAcl = NULL, AbsSAcl = NULL ;
	PSID AbsOwner = NULL, AbsGroup = NULL ;
	DWORD dwSize = 0, dwSizes[5] = {0} ;
	size_t i = 0 ;
	PSECURITY_DESCRIPTOR ppSDOut = *ppSDIn;
	/* Transfer ppSD into this variable. */

	if(ppSDOut == NULL || !(((SECURITY_DESCRIPTOR*)ppSDOut)->Control & SE_SELF_RELATIVE))
	{/* What do you mean? An African or European swallow? */
		SetLastError(ERROR_INVALID_PARAMETER);
		return NULL;
	}

	MakeAbsoluteSD(*ppSDIn, NULL, &dwSizes[0], NULL, &dwSizes[4], NULL, &dwSizes[3],
		NULL, &dwSizes[2], NULL, &dwSizes[1]) ;
	for(i = 0 ; i < 5 ; i++)
		dwSize += dwSizes[i] ;
	ppSDOut = (PSECURITY_DESCRIPTOR)
		(LocalAlloc(LPTR, 2 * sizeof(ACL) + 2 * sizeof(SID) + dwSize)) ;
	if(ppSDOut == NULL)
	{/* HACKHACK: Allocate a 2D array with one LocalAlloc */
		LocalFree(*ppSDIn); *ppSDIn = NULL;
		return NULL;
	}

	/* Now set the pointers to the appropriate offsets. */
	dwSize = dwSizes[0] ;
	AbsOwner = (PSID)((BYTE *)(ppSDOut) + dwSize) ;
	dwSize += sizeof(SID) + dwSizes[1] ;
	AbsGroup = (PSID)((BYTE *)(ppSDOut) + dwSize) ;
	dwSize += sizeof(SID) + dwSizes[2] ;
	AbsSAcl = (PACL)((BYTE *)(ppSDOut) + dwSize) ;
	dwSize += sizeof(ACL) + dwSizes[3] ;
	AbsDAcl = (PACL)((BYTE *)(ppSDOut) + dwSize) ;

	SetLastError(ERROR_SUCCESS) ;
	MakeAbsoluteSD(*ppSDIn, ppSDOut, &dwSizes[0], AbsDAcl, &dwSizes[4], AbsSAcl,
		&dwSizes[3], AbsOwner, &dwSizes[2], AbsGroup, &dwSizes[1]) ;
	LocalFree(*ppSDIn); *ppSDIn = NULL;
	return ppSDOut;
}


int PrintSecurityDescriptor(SECURITY_DESCRIPTOR *ppSD)
{
	DWORD i = 0;
	PACL pDacl = NULL;
	ACE_HEADER *pAce = NULL;
	TCHAR *UserName = NULL, *ReferencedDomainName = NULL;
	if(ppSD == NULL || ppSD->Revision < SECURITY_DESCRIPTOR_REVISION || ppSD->Control & SE_SELF_RELATIVE)
	{/* Assume that the security descriptor is in absolute form. (the compiler will warn if this isn't the case). */
		SetLastError(ERROR_INVALID_PARAMETER);
		return 0;
	}
	ReferencedDomainName = (TCHAR *)calloc(MAX_USERNAME_LENGTH, sizeof(TCHAR));
	UserName = (TCHAR *)calloc(MAX_USERNAME_LENGTH, sizeof(TCHAR));
	if(!UserName || !ReferencedDomainName)
	{
		free(UserName); UserName = NULL;
		free(ReferencedDomainName); ReferencedDomainName = NULL;
	}

	if(ppSD->Control & SE_DACL_PRESENT)
	{
		pDacl = ppSD->Dacl;
		_tprintf(_T("DACL:"));
		for(i = 0; i < pDacl->AceCount; i++)
		{
			GetAce(pDacl, i, &pAce);
			_tprintf(_T("\r\n"));
			switch(pAce->AceType)
			{
				case ACCESS_ALLOWED_ACE_TYPE:
				{
					ACCESS_ALLOWED_ACE *TypedAce = (ACCESS_ALLOWED_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("allow: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_DENIED_ACE_TYPE:
				{
					ACCESS_DENIED_ACE *TypedAce = (ACCESS_DENIED_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("deny: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_AUDIT_ACE_TYPE:
				{
					SYSTEM_AUDIT_ACE *TypedAce = (SYSTEM_AUDIT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("audit: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_ALARM_ACE_TYPE:
				{
					SYSTEM_ALARM_ACE *TypedAce = (SYSTEM_ALARM_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("alarm: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_ALLOWED_OBJECT_ACE_TYPE:
				{
					ACCESS_ALLOWED_OBJECT_ACE *TypedAce = (ACCESS_ALLOWED_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object allow: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_DENIED_OBJECT_ACE_TYPE:
				{
					ACCESS_DENIED_OBJECT_ACE *TypedAce = (ACCESS_DENIED_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object deny: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_AUDIT_OBJECT_ACE_TYPE:
				{
					SYSTEM_AUDIT_OBJECT_ACE *TypedAce = (SYSTEM_AUDIT_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object audit: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_ALARM_OBJECT_ACE_TYPE:
				{
					SYSTEM_ALARM_OBJECT_ACE *TypedAce = (SYSTEM_ALARM_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object alarm: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_ALLOWED_CALLBACK_ACE_TYPE:
				{
					ACCESS_ALLOWED_CALLBACK_ACE *TypedAce = (ACCESS_ALLOWED_CALLBACK_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("allow with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_DENIED_CALLBACK_ACE_TYPE:
				{
					ACCESS_DENIED_CALLBACK_ACE *TypedAce = (ACCESS_DENIED_CALLBACK_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("deny with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_AUDIT_CALLBACK_ACE_TYPE:
				{
					SYSTEM_AUDIT_CALLBACK_ACE *TypedAce = (SYSTEM_AUDIT_CALLBACK_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("audit with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_ALARM_CALLBACK_ACE_TYPE:
				{
					SYSTEM_ALARM_CALLBACK_ACE *TypedAce = (SYSTEM_ALARM_CALLBACK_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("alarm with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE:
				{
					ACCESS_ALLOWED_CALLBACK_OBJECT_ACE *TypedAce = (ACCESS_ALLOWED_CALLBACK_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object allow with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE:
				{
					ACCESS_DENIED_CALLBACK_OBJECT_ACE *TypedAce = (ACCESS_DENIED_CALLBACK_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object deny with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE:
				{
					SYSTEM_AUDIT_CALLBACK_OBJECT_ACE *TypedAce = (SYSTEM_AUDIT_CALLBACK_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object audit with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE:
				{
					SYSTEM_ALARM_CALLBACK_OBJECT_ACE *TypedAce = (SYSTEM_ALARM_CALLBACK_OBJECT_ACE *)pAce;
					Sid2UserName((PSID)&TypedAce->SidStart, ReferencedDomainName, UserName);
					_tprintf(_T("object alarm with callback: %s\\%s to %8x"), ReferencedDomainName, UserName, TypedAce->Mask);
					break;
				}
				case ACCESS_ALLOWED_COMPOUND_ACE_TYPE:
				default:
				{
					_tprintf(_T("Unknown ACE"));
					break;
				}
			}
		}
	}
	free(UserName); UserName = NULL ;
	free(ReferencedDomainName); ReferencedDomainName = NULL ;
	return 1;
}


PACL RebuildAclFromScratch(SECURITY_DESCRIPTOR *pSecurityDescriptor, void *pAceAdd)
{/** Return a carbon copy of the pSecurityDescriptor's DACL, in LocalAlloc()'ed memory.
*	And we're not allowed to use memcpy().
**/
	PSID SidStart = NULL;
	PACL pOldDacl = pSecurityDescriptor->Dacl, pNewDacl = NULL;
	ACE_HEADER *pAce = NULL;
	DWORD i = 0, n = pSecurityDescriptor->Dacl->AceCount, m = 0, TotalSize = sizeof(ACL);
	/* You should know how many ACEs your DACL contains, otherwise you should reread part 1. */


	/**	First calculate how large your ACE is going to be. Note! We're making this unnecessarily
	*	hard for ourselves! We can just use pSecurityDescriptor->Dacl->AclSize as an alternative.
	**/
	for(i = 0; i < n; i++)
	{
		GetAce(pOldDacl, i, &pAce);
		if( pAce->AceType > ACCESS_MIN_MS_OBJECT_ACE_TYPE &&
			pAce->AceType < ACCESS_MAX_MS_OBJECT_ACE_TYPE)
		{/** HACKHACK: the ACCESS_*_OBJECT_ACEs structures have the same signatures, therefore we can assume that
		*	the ACEs are of the same size. We can use this to simplify the calculations a bit. Don't rely on this
		*	being the case with later Windows versions.
		**/
			SidStart = &((ACCESS_ALLOWED_OBJECT_ACE *)pAce)->SidStart;
			if(!IsValidSid(SidStart))
			{/* skip the entry if the SID is invalid. */
				continue;
			}
			TotalSize += sizeof(ACCESS_ALLOWED_OBJECT_ACE) - sizeof(DWORD);
			TotalSize += GetLengthSid(SidStart);
			m++;	/* increment the object ace count */
		}
		else
		{/** HACKHACK: We're also assuming that the other structures are of the same size.
		*	This is even less likely to be true
		**/
			SidStart = &((ACCESS_ALLOWED_ACE *)pAce)->SidStart;
			if(!IsValidSid(SidStart))
			{/* skip the entry if the SID is invalid. */
				continue;
			}
			TotalSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD);
			TotalSize += GetLengthSid(SidStart);
		}
	}
	/* Add one more ACE type */
	TotalSize += sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(SidStart) - sizeof(DWORD);


	/* Phew!! And all we did was just calculate the length of the ACL! */


	pNewDacl = (PACL)LocalAlloc(LPTR, TotalSize);
	if(pNewDacl == NULL)
	{/* Allocate and initialize a buffer for the ACL. */
		return FALSE;
	}
	InitializeAcl(pNewDacl, TotalSize, ACL_REVISION);

	for(i = 0; i < n; i++)
	{/* look up the deny ACEs */
		GetAce(pOldDacl, i, &pAce);
		switch(pAce->AceType)
		{
			case ACCESS_DENIED_ACE_TYPE:
			case ACCESS_DENIED_OBJECT_ACE_TYPE:
			case ACCESS_DENIED_CALLBACK_ACE_TYPE:
			case ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE:
			{	/* For each deny ace found, add it to the end of the list */
				AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pAce, sizeof(pAce->AceSize));
				break;
			}
			default:
				break;
		}
	}
	for(i = 0; i < n; i++)
	{/* look up the allow ACEs */
		GetAce(pOldDacl, i, &pAce);
		switch(pAce->AceType)
		{
			case ACCESS_DENIED_ACE_TYPE:
			case ACCESS_DENIED_OBJECT_ACE_TYPE:
			case ACCESS_DENIED_CALLBACK_ACE_TYPE:
			case ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE:
				break;
			default:
			{	/* This time add the ACE to the end if it ISN'T a deny ACE */
				AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pAce, pAce->AceSize);
				break;
			}
		}
	}

	/* Finally, append the last ACE onto the end of this ACL */
	AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pAceAdd, pAce->AceSize);


	/* This is a nightmare... you should seriously consider one of the other techniques. */
	return pNewDacl;
}


ACCESS_ALLOWED_ACE *MakeAceToAdd(void)
{
	PSID SidStart = NULL;
	SID_IDENTIFIER_AUTHORITY peUse = SECURITY_NT_AUTHORITY;
	ACCESS_ALLOWED_ACE *pAceAdd = NULL;

	/* You should know how many ACEs your DACL contains, otherwise you should reread part 1. */
	if(!AllocateAndInitializeSid(&peUse, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0,
		0, 0, 0, 0, 0, &SidStart))
	{/* We're going to need a Builtin Admins SID */
		return NULL;
	}

	/* We intend to add this ACE to the end of the list. Build up a low level ACE: Admins Full Control. */
	pAceAdd = (ACCESS_ALLOWED_ACE *)calloc(sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(SidStart), 1);
	if(!pAceAdd)
	{
		FreeSid(SidStart); SidStart = NULL;
		return NULL;
	}
	pAceAdd->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;
	pAceAdd->Header.AceSize = (WORD)(sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(SidStart));
	pAceAdd->Header.AceFlags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE ;
	pAceAdd->Mask = FILE_GENERIC_READ;
	CopySid(GetLengthSid(SidStart), &pAceAdd->SidStart, SidStart);
	FreeSid(SidStart); SidStart = NULL;

	return pAceAdd;
}


int DoAccessCheck(PSECURITY_DESCRIPTOR OutSecDesc)
{
	HANDLE ProcToken = NULL, ImpersonationToken = NULL;
	PRIVILEGE_SET *PrivilegeSet = NULL;
	BOOL AccessStatus = FALSE;
	DWORD GrantedAccess = 0, PrivilegeSetLength = 0, DesiredAccess = FILE_GENERIC_WRITE;
	GENERIC_MAPPING GenericMapping =
	{
		READ_CONTROL | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA,
		FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_WRITE_DATA | FILE_APPEND_DATA,
		READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_EXECUTE,
		FILE_ALL_ACCESS
	} ;

	if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, TRUE, &ProcToken))
	{
		if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &ProcToken))
		{
			return 1;
		}
	}
	if(!DuplicateToken(ProcToken, SecurityImpersonation, &ImpersonationToken))
	{
		CloseHandle(ProcToken); ProcToken = NULL;
		return 1;
	}

	AccessCheck(OutSecDesc, ImpersonationToken, DesiredAccess, &GenericMapping, NULL, &PrivilegeSetLength,
		&GrantedAccess, &AccessStatus);

	PrivilegeSet = (PPRIVILEGE_SET)calloc(PrivilegeSetLength, 1);
	if(PrivilegeSet == NULL)
	{
		CloseHandle(ProcToken); ProcToken = NULL;
		CloseHandle(ImpersonationToken); ImpersonationToken = NULL;
		return 1;
	}

	AccessCheck(OutSecDesc, ImpersonationToken, DesiredAccess, &GenericMapping,	PrivilegeSet, &PrivilegeSetLength,
		&GrantedAccess, &AccessStatus);
	if(AccessStatus == TRUE)
	{
		_tprintf(_T("%x"), GrantedAccess==DesiredAccess);
	}

	free(PrivilegeSet); PrivilegeSet = NULL;
	CloseHandle(ProcToken); ProcToken = NULL;
	CloseHandle(ImpersonationToken); ImpersonationToken = NULL;
	return 0;
}



int _tmain(void)
{
	PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
	PACL pDacl = NULL;
	ACCESS_ALLOWED_ACE *pAceAdd = NULL;

#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_LEAK_CHECK_DF |
		_CRTDBG_ALLOC_MEM_DF | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)) ;
#endif /* _DEBUG. This detects memory leaks */

	WellKnownSid2Trustee();
	_tprintf(_T("\r\n"));

	Sid2UserNamePrepare();
	_tprintf(_T("\r\n"));

	_tprintf(_T("%d\r\n"), IsAdminRunning());

	DoWhoAmI();
	_tprintf(_T("\r\n"));

	SetPrivilege(SE_SECURITY_NAME, TRUE);
	{
		pSecurityDescriptor = GetSecurityDesc(_T("G:\\Documents and Settings"));
		pSecurityDescriptor = ConvertSecurityDescriptor(&pSecurityDescriptor);
		PrintSecurityDescriptor(pSecurityDescriptor);
		_tprintf(_T("\r\n"));

		pAceAdd = MakeAceToAdd();
		_tprintf(_T("\r\n"));

		pDacl = RebuildAclFromScratch(pSecurityDescriptor, pAceAdd);
		_tprintf(_T("\r\n"));
		DoAccessCheck(pSecurityDescriptor);
		_tprintf(_T("\r\n"));

		/* Free the buffers */
		free(pAceAdd) ; pAceAdd = NULL;

		LocalFree(pDacl); pDacl = NULL;
		LocalFree(pSecurityDescriptor); pSecurityDescriptor = NULL;
	}
	SetPrivilege(SE_SECURITY_NAME, FALSE);
	return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Mr. Shah is a reclusive C++/C# developer lurking somewhere in the depths of the city of London. He learnt physics at Kings' College London and obtained a Master in Science there. Having earned an MCAD, he teeters on the brink of transitioning from C++ to C#, unsure of which language to jump to. Fortunately, he also knows how to use .NET interop to merge code between the two languages (which means he won't have to make the choice anytime soon).

His interests (apart from programming) are walking, football (the real one!), philosophy, history, retro-gaming, strategy gaming, and any good game in general.

He maintains a website / blog / FAQ / junk at shexec32.serveftp.net, where he places the best answers he's written to the questions you've asked. If you can find him, maybe you can hire Mr. Shah to help you with anything C++[/CLI]/C#/.NET related Smile | :) .

Comments and Discussions