Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

The Windows Access Control Model: Part 2

, 27 Jun 2005 CPOL
This second part of the Access Control series will program with the basic Access Control structures.
boost.zip
boost
config
compiler
platform
stdlib
detail
mpl
aux_
config
preprocessed
bcc
bcc551
gcc
msvc60
msvc70
mwcw
no_ctps
no_ttp
plain
preprocessor
range_c
preprocessor
arithmetic
detail
array
comparison
config
control
detail
edg
msvc
detail
facilities
iteration
detail
bounds
iter
list
detail
edg
logical
punctuation
repetition
detail
edg
msvc
selection
seq
detail
slot
detail
tuple
regex
v3
test
detail
included
type_traits
detail
utility
userfun.zip
ATL
release
UserFun.exe
LowLevel
release
LowLevel.exe
2000
release
2000.exe
whoami.zip
Whoami
release
Whoami.exe
/** In order to compile this program you must have installed the boost library.
*	We need the regular expression class from it.
*
**/
#include "UserFun2000.h"
const size_t MAX_USERNAME_LENGTH = 256;


int _tmain(void)
{
#ifdef _DEBUG
	::_CrtSetDbgFlag(_CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_LEAK_CHECK_DF |
		_CRTDBG_ALLOC_MEM_DF | ::_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG)) ;
#endif /* _DEBUG. */
	::SetLastError(ERROR_SUCCESS);
	WellKnownSid2Trustee();
	std::wcout << std::endl;

	Sid2UserNamePrepare();
	std::wcout << _T("\r\n");

	std::wcout << bool(IsAdminRunning()) << _T("\r\n");

	DoWhoAmI();
	{
		SaferRaiiWrapper SaferObj(SAFER_LEVELID_UNTRUSTED);
		SaferObj.CreateProcessAsUser(_T("\"C:\\Windows\\Notepad.exe\""));
	}

	try {
		std::SecureClass::SetPrivilege(_T("SeSecurityPrivilege"), TRUE);
	} catch(std::runtime_error &) {/* non critical error */
	}
	GetSecurityDescs(_T("G:\\WUTemp\\Test Folder\\Tester.txt"));
	try {
		std::SecureClass::SetPrivilege(_T("SeSecurityPrivilege"), FALSE);
	} catch(std::runtime_error &) {/* non critical error */
	}

	try
	{
		std::wcout << _T("\r\nCreating class");
		std::SecureClass secObj1(std::SecureClass::ReadLen | std::SecureClass::CopyClass, ::GetCurrentThread());

		std::wcout << _T("\r\nlen is: ") << secObj1.get_len();

		std::wcout << _T("\r\nCopying class");
		std::SecureClass secObj2 = secObj1;

		std::wcout << _T("\r\nAttempting to write class");
		secObj2.set_len(5.) ;	/* This should throw. */

	} catch(std::runtime_error &e) {
		std::wcout << _T("\r\n") << e.what() << std::endl;
	}

	return 0;
}



int WellKnownSid2Trustee(void)
{
	const TCHAR SystemSid[] = _T("SY");
	PSID SidUser = NULL;

	if(! ::ConvertStringSidToSid(SystemSid, &SidUser))
	{
		throw std::logic_error("ConvertStringSidToSid Failed");
	}

	std::wcout << SystemSid;

	{
		TRUSTEE TrusteeSid = {0};
		::BuildTrusteeWithSid(&TrusteeSid, SidUser);
	}

	::LocalFree(SidUser); SidUser = NULL;

	return 0;
}

void Sid2UserName(IN const PSID &SidUser, OUT std::basic_string<TCHAR> &UserName)
{/** Ex. 2. Throws std::logic_error on failure. Don't pass in Well known Sids **/
	if(!IsValidSid(SidUser))
	{/* valid inputs? */
		throw std::logic_error("The Sid is bad.");
	}
	/* We assume that CoInitialize has been called */
	::SetLastError(ERROR_SUCCESS);
	try
	{
		/* First we need to initialize and connect to WMI. */
		WMI::ISWbemLocatorPtr Server = NULL;
		_bstr_t QueryString = _T("Select * From Win32_UserAccount Where SID = \"");
		Server.CreateInstance(__uuidof(WMI::SWbemLocator));
		WMI::ISWbemServicesPtr objWMI = Server->ConnectServer(_bstr_t(""), _bstr_t("root\\cimv2"), _bstr_t(), _bstr_t(), _bstr_t(), _bstr_t(), 0, NULL);

		/* Before continuing, we must finish building the WQL query */
		{
			LPTSTR TextualSid = NULL;
			::ConvertSidToStringSid(SidUser, &TextualSid);
			QueryString += TextualSid;
			LocalFree(TextualSid) ; TextualSid = NULL;
		}
		QueryString += _T("\"");
		{/* The query is now ready, Execute it! */
			WMI::ISWbemObjectSetPtr QueryResult = objWMI->ExecQuery(QueryString, _bstr_t("WQL"), 0, NULL);
			/* We should now have 1 result returned (as a collection). However, this collection still needs enumerating */
			if(QueryResult->Count > 0)
			{
				WMI::IEnumVARIANTPtr en = QueryResult->_NewEnum;
				_variant_t UserInstance;
				ULONG cnt = 0;
				en->Next(1, &UserInstance, &cnt);

				/* We now have the Win32_UserAccount we needed to query. Query its "Name". */
				QueryString = static_cast<WMI::ISWbemObjectPtr>(UserInstance)->Properties_->Item("Name", 0)->GetValue();
				/* This property is a BSTR that contains the answer! */
				UserName = static_cast<const TCHAR *>(QueryString);
				/* We've done it! now let's clean up and get out of here! */
			}
		}
	} catch (const _com_error &ex) {
		/* translate _com_error to logic_error. */
		boost::sized_array<TCHAR> ErrorText(66) ;
		::_stscanf(ErrorText.get(), _T("%65x"), ex.Error());
		::CoUninitialize();
		throw std::logic_error("Wmi Error occurred");
	}

}


int Sid2UserNamePrepare(void)
{
	HANDLE hToken = NULL;
	DWORD TokenInformationLength = sizeof(TOKEN_USER), ReturnLength = 0;

	boost::sized_array<BYTE> TokenBuffer(sizeof(TOKEN_USER)) ;
	std::basic_string<TCHAR> UserName;

	/* Setup the current thread token. */
	if(!::OpenThreadToken(::GetCurrentThread(), TOKEN_READ, TRUE, &hToken))
	{
		if(!::OpenProcessToken(::GetCurrentProcess(), TOKEN_READ, &hToken))
		{
			throw std::logic_error("Could not open access token");
		}
	}

	::SetLastError(ERROR_SUCCESS);
	::GetTokenInformation(hToken, TokenUser, TokenBuffer.get(), TokenInformationLength, &ReturnLength);
	if(::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
	{/* Reallocation is necessary */
		TokenInformationLength = ReturnLength;
		TokenBuffer.reset(new BYTE[ReturnLength]);

		if(!::GetTokenInformation(hToken, TokenUser, TokenBuffer.get(), TokenInformationLength, &ReturnLength))
		{
			::CloseHandle(hToken); hToken = NULL;
			throw std::logic_error("Could not retrieve user from token");
		}
	}

	/* Now that we have the SID, convert it to a user name */
	TOKEN_USER *TokenInformation = reinterpret_cast<TOKEN_USER *>(TokenBuffer.get());

	::CoInitialize(NULL);
	Sid2UserName(TokenInformation->User.Sid, UserName);
	::CoUninitialize();
	std::wcout << UserName.c_str();

	::CloseHandle(hToken); hToken = NULL;
	return 0;
}



bool IsAdminRunning(void)
{
	BOOL IsMember = FALSE;
	PSID SidUser = NULL;

	/** BUGBUG: this only checks if the user is in the Local Admins group.
	*	Then again, this entire function is a bug (design flaw).
	**/
	if(::ConvertStringSidToSid(_T("BA"), &SidUser) != TRUE)
	{
		throw std::logic_error("Could not create admin SID");
	}
	if(::CheckTokenMembership(NULL, SidUser, &IsMember) != TRUE)
	{
		throw std::logic_error("Could not determine if user is an admin");
	}

	::LocalFree(SidUser); SidUser = NULL;
	return static_cast<bool>(IsMember == TRUE);
}



void PrintAceString(const std::basic_string<TCHAR> &AceSddlSD)
{/**
*	ACE strings need to be handled separately. To get them:
*	 gets all ACE strings.
*	For each match, group 1 gets the ACE type, group 2 gets the inheritace,
*	group 3 gets the access mask, group 4 gets the object guid, group 5 gets the inherited guid
*
*	ACE strings need to be printed in UserName (allow|deny|inherit)
**/
	boost::wregex RegSearch(_T("\\((.*?);(.*?);(.*?);(.*?);(.*?);(.*?)\\)"));
	boost::wsmatch MatchResults;

	if(!AceSddlSD.empty() && boost::regex_search(AceSddlSD, MatchResults, RegSearch, boost::format_perl))
	{
		/* The user is in token 6. */
		std::wcout << static_cast<std::basic_string<TCHAR> >(MatchResults[6]).c_str() << _T(": ");

		/* Is it inherited? (ID?) */
		if(static_cast<std::basic_string<TCHAR> >(MatchResults[2]).find(_T("ID")) <
			static_cast<std::basic_string<TCHAR> >(MatchResults[2]).size())
			std::wcout << _T("inherited ");

		switch(static_cast<std::basic_string<TCHAR> >(MatchResults[1]).at(0))
		{/* Get the ACE Type. */
			case _T('A'):
				if(static_cast<std::basic_string<TCHAR> >(MatchResults[1]).size() > 1)
					std::wcout << _T("audit/alarm, ");
				else
					std::wcout << _T("allow, ");
				break;
			case _T('D'):
				std::wcout << _T("deny, ");
				break;
			default:
				/* print out the string by default. */
				std::wcout << static_cast<std::basic_string<TCHAR> >(MatchResults[1]);
				break;
		}

		/* print out the access mask untranslated */
		std::wcout << static_cast<std::basic_string<TCHAR> >(MatchResults[3]).c_str() << _T("\r\n");
	}
}



int TranslateSddl(const std::basic_string<TCHAR> &SddlSD)
{
	/** Although SDDL is already in a readable form, Our task requires us to make it even more
	*	readable, using this form:
	*	UserName (Allow|deny|inherit). This will require some regular expressions.
	**/

	/* Initialize the regex objects */
	boost::wregex RegSearch(_T("O:(.*?)[DGS]:"));
	boost::wsmatch MatchResults;

	/* Get the owner token */
	if(boost::regex_search(SddlSD, MatchResults, RegSearch, boost::format_perl | boost::match_partial))
	{
		std::wcout << _T("Owner=") << static_cast<std::basic_string<TCHAR> >(MatchResults[1]).c_str() << _T("\r\n");
	}

	/* Get the Group token */
	RegSearch.assign(_T("G:(.*?)[DOS]:"));
	if(boost::regex_search(SddlSD, MatchResults, RegSearch, boost::format_perl | boost::match_partial))
	{
		std::wcout << _T("Group=") << static_cast<std::basic_string<TCHAR> >(MatchResults[1]).c_str() << _T("\r\n");
	}
	std::basic_string<TCHAR>::size_type start, end;
	std::basic_string<TCHAR> ACEStrings = SddlSD;

	/* The ACE strings need special handling */
	RegSearch.assign(_T("D:(.*?)[GSO]:"));
	if(boost::regex_search(SddlSD, MatchResults, RegSearch, boost::format_perl | boost::match_partial))
	{/* loop through each ACE string. */
		ACEStrings = static_cast<std::basic_string<TCHAR> >(MatchResults[1]);
		while(!ACEStrings.empty())
		{
			start = ACEStrings.find_first_of(_T("("), 0);
			end = ACEStrings.find_first_of(_T(")"), start) + 1;
			/* Get pointers to the brackets */
			PrintAceString(ACEStrings.substr(start, end - start));
			ACEStrings.erase(0, end);
		}
	}

	/* SACL is just an ACL with an S */
	RegSearch.assign(_T("S:(.*?)[DOG]:"));
	if(boost::regex_search(SddlSD, MatchResults, RegSearch, boost::format_perl | boost::match_partial))
	{/* loop through each ACE string. */
		ACEStrings = static_cast<std::basic_string<TCHAR> >(MatchResults[1]);
		while(!ACEStrings.empty())
		{
			start = ACEStrings.find_first_of(_T("("), 0);
			end = ACEStrings.find_first_of(_T(")"), start) + 1;
			/* Get pointers to the brackets */
			PrintAceString(ACEStrings.substr(start, end - start));
			ACEStrings.erase(0, end);
		}
	}

	return 0;
}



int DoAccessCheck(PSECURITY_DESCRIPTOR OutSecDesc, DWORD DesiredAccess)
{
	/* This program performs an access check on the security descriptor. */
	HANDLE ProcToken = NULL, ImpersonationToken = NULL;
	/* We need an impersonation token, an access token, a generic mapping, and a desired access mask. */
	BOOL AccessStatus = FALSE;
	DWORD GrantedAccess = 0, PrivilegeSetLength = 0;
	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))
	{/* Make an impersonation token */
		::CloseHandle(ProcToken); ProcToken = NULL;
		::CloseHandle(ImpersonationToken); ImpersonationToken = NULL;
	}
	::MapGenericMask(&DesiredAccess, &GenericMapping);
	/* map generic accesses to specific accesses. */

	/* What size do we need our PRIVILEGE_SET? */
	::AccessCheck(OutSecDesc, ImpersonationToken, DesiredAccess, &GenericMapping, NULL, &PrivilegeSetLength,
		&GrantedAccess, &AccessStatus);

	boost::scoped_array<PRIVILEGE_SET> PrivilegeSet (reinterpret_cast<PRIVILEGE_SET *>
		(new BYTE[(PrivilegeSetLength + 1)]));

	/* OK, we're ready: AccessCheck()! */
	::AccessCheck(OutSecDesc, ImpersonationToken, DesiredAccess, &GenericMapping,	PrivilegeSet.get(), &PrivilegeSetLength,
		&GrantedAccess, &AccessStatus);
	if(AccessStatus == TRUE)
	{/* Results, results. */
		_tprintf(_T("%x"), GrantedAccess==DesiredAccess);
	}

	::CloseHandle(ProcToken); ProcToken = NULL;
	::CloseHandle(ImpersonationToken); ImpersonationToken = NULL;
	return 0;
}





const std::basic_string<TCHAR> DoPrintSecurityDescriptor(PSECURITY_DESCRIPTOR ppSD, SECURITY_INFORMATION psi)
{
	std::basic_string<TCHAR> Resultstr;
	LPTSTR SddlSD = NULL;
	/* ConvertStringSecurityDescriptorToSecurityDescriptor will allocate a buffer for us. */
	::ConvertSecurityDescriptorToStringSecurityDescriptor(ppSD, SDDL_REVISION_1, psi, &SddlSD, NULL);

	TranslateSddl(SddlSD);

	Resultstr = SddlSD;
	::LocalFree(SddlSD); SddlSD = NULL;
	return Resultstr;
}


DWORD ApplyNewSecurityDescriptor(const std::basic_string<TCHAR> &FNameStr, const std::basic_string<TCHAR> &SddlForm)
{
	/* We have an SDDL. Create a security descriptor from it. */
	PSECURITY_DESCRIPTOR ppSD = NULL;
	SECURITY_INFORMATION psi = 0;
	if(!::ConvertStringSecurityDescriptorToSecurityDescriptor(SddlForm.c_str(), SDDL_REVISION_1, &ppSD, NULL))
	{/* Wham! Instant security descriptor! See how easy that was? */
		throw std::logic_error("Security descriptor conversion failure");
	}

	if(::GetFileAttributes(FNameStr.c_str()) != INVALID_FILE_ATTRIBUTES)
	{/* Split up the security descriptor. */
		PACL pDacl = NULL, pSacl = NULL;
		PSID pOwner = NULL, pGroup = NULL;
		DWORD SDRevision = 0;
		SECURITY_DESCRIPTOR_CONTROL pControl = 0;
		BOOL bPresent = FALSE, bDefaulted = FALSE;
		if(::GetSecurityDescriptorControl(ppSD, &pControl, &SDRevision))
		{/* translate the Control flags */
			if(pControl & SE_DACL_PROTECTED)
				psi &= PROTECTED_DACL_SECURITY_INFORMATION;
			if(pControl & SE_SACL_PROTECTED)
				psi &= PROTECTED_SACL_SECURITY_INFORMATION;
			if(pControl & SE_DACL_AUTO_INHERIT_REQ)
				psi &= UNPROTECTED_DACL_SECURITY_INFORMATION;
			if(pControl & SE_SACL_AUTO_INHERIT_REQ)
				psi &= UNPROTECTED_SACL_SECURITY_INFORMATION;
		}

		if(::GetSecurityDescriptorDacl(ppSD, &bPresent, &pDacl, &bDefaulted))
		{/* Dacl present. By default, inherit. */
			psi &= DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION;
		}
		if(::GetSecurityDescriptorSacl(ppSD, &bPresent, &pSacl, &bDefaulted))
		{/* Sacl present */
			psi &= SACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION;
		}
		if(::GetSecurityDescriptorOwner(ppSD, &pOwner, &bDefaulted))
		{/* Owner present */
			psi &= OWNER_SECURITY_INFORMATION;
		}
		if(::GetSecurityDescriptorGroup(ppSD, &pGroup, &bDefaulted))
		{/* Owner present */
			psi &= GROUP_SECURITY_INFORMATION;
		}

		::SetNamedSecurityInfo(const_cast<LPTSTR>(FNameStr.c_str()), SE_FILE_OBJECT, psi, pOwner, pGroup, pDacl, pSacl);
	}
	else
	{
		SECURITY_ATTRIBUTES sAttribs = {sizeof(SECURITY_ATTRIBUTES), ppSD, FALSE};
		::CreateFile(FNameStr.c_str(), GENERIC_ALL, FILE_SHARE_READ, &sAttribs, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
	}


	::LocalFree(ppSD); ppSD = NULL;
	return 0;
}




DWORD GetSecurityDescs(const std::basic_string<TCHAR> &FNameStr)
{
	/* Get all possible security information for the object: 0xf000000f */
	SECURITY_INFORMATION psi = GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION |
		SACL_SECURITY_INFORMATION | PROTECTED_SACL_SECURITY_INFORMATION |
		PROTECTED_DACL_SECURITY_INFORMATION | UNPROTECTED_SACL_SECURITY_INFORMATION |
		UNPROTECTED_DACL_SECURITY_INFORMATION;
	PSECURITY_DESCRIPTOR ppSecurityDescriptor = NULL;
	DWORD dwErr = ::GetNamedSecurityInfo(const_cast<LPTSTR>(FNameStr.c_str()),
		SE_FILE_OBJECT, psi, NULL, NULL, NULL, NULL, &ppSecurityDescriptor);

	{
		PACL AbsDAcl = NULL, AbsSAcl = NULL ;
		PSID AbsOwner = NULL, AbsGroup = NULL ;
		DWORD dwSize = 0, dwSizes[5] = {0} ;
		PSECURITY_DESCRIPTOR ppSDRel = ppSecurityDescriptor ;/* Transfer ppSD into this variable. */

		::MakeAbsoluteSD(ppSDRel, NULL, &dwSizes[0], NULL, &dwSizes[4], NULL, &dwSizes[3],
			NULL, &dwSizes[2], NULL, &dwSizes[1]) ;
		for(size_t i = 0 ; i < 5 ; i++) dwSize += dwSizes[i] ;
		ppSecurityDescriptor = reinterpret_cast<PSECURITY_DESCRIPTOR>
			( ::LocalAlloc(LPTR, 2 * sizeof(ACL) + 2 * sizeof(SID) + dwSize) ) ;
		if(ppSDRel == NULL) throw std::logic_error("Could not allocate memory") ;
		/* HACKHACK: Allocate a 2D array with one LocalAlloc */

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

		::SetLastError(ERROR_SUCCESS) ;
		::MakeAbsoluteSD(ppSDRel, ppSecurityDescriptor, &dwSizes[0], AbsDAcl, &dwSizes[4], AbsSAcl,
			&dwSizes[3], AbsOwner, &dwSizes[2], AbsGroup, &dwSizes[1]) ;
		/* This security descriptor is now absolute */
		::LocalFree(ppSDRel) ; ppSDRel = NULL ;
	}

	/* Print out the contents of the security descriptor. */
	DoPrintSecurityDescriptor(ppSecurityDescriptor, psi);
	DoAccessCheck(ppSecurityDescriptor, FILE_GENERIC_WRITE);

	/* Build a new security descriptor in SDDL */
	std::basic_string<TCHAR> SddlForm = _T("O:BAG:BAD:AI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;GRGX;;;") +
		std::SecureClass::GetCurrentSid() + _T(")");

	ApplyNewSecurityDescriptor(FNameStr, SddlForm);

	/* Cleanup. */
	::LocalFree(ppSecurityDescriptor); ppSecurityDescriptor = NULL;
	return dwErr;
}

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)

Share

About the Author

oshah
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 | :) .

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.150129.1 | Last Updated 27 Jun 2005
Article Copyright 2005 by oshah
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid