Forcing Security to Stay On

Last time we looked at how the Whidbey version of CasPol uses a mutex to indicate the state of the security system.  One of the more interesting fallouts from this model is that is that we can actually use this information to prevent security from being turned off in the first place.

As I mentioned in the last post, the CLR looks for a mutex named \BaseNamedObjects\CLR_CASOFF_MUTEX, and if this mutex exists, is not abandoned, and is owned by the BUILTIN\Administrators group, it considers security to be in an off state.  The obvious CasPol -s off implementation given that information is to acquire the named mutex and set the ACL such that the CLR will recognize it.  From there, it's pretty easy to see that in order to prevent CasPol from successfully turning off security, we need to prevent it from acquiring the mutex.

How would we go about doing that?  According to the MSDN documentation, if CreateMutex is called with a non-NULL lpName parameter, it will return either a new mutex with the given name, or if the mutex already exists, it will return a handle to the existing object while setting the last error to ERROR_ALREADY_EXISTS.  Basically that's saying that Windows only allows one mutex with a given name on each system.

This means that if we create the mutex before CasPol does, we'll have a handle to the same mutex that CasPol and the CLR use to determine the security state.  If we're on Windows NT, we can make use of the Windows security system to prevent anyone else from having access to that object.  For CasPol, not having access to the mutex means that it can't possibly set it to the state the CLR is looking for.  More importantly if the CLR doesn't have access to the mutex, it can't use it to determine that security is off.

So our strategy is to simply create the mutex before anyone else on the machine, and ACL it down such that nobody is allowed access to it.  We can pull this off in less than 20 lines of code:

int _tmain()
{
     // setup security attributes for the Mutex
    SECURITY_ATTRIBUTES sa;
    ZeroMemory(&sa, sizeof(sa));
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = FALSE;
    
     // create a DACL to deny all access to the Everyone group
    TCHAR *securityDescriptor = _T("D:(D;OICI;GA;;;WD)");
    BOOL createdSecurity = ConvertStringSecurityDescriptorToSecurityDescriptor(
            securityDescriptor,
            SDDL_REVISION_1,
            &sa.lpSecurityDescriptor,
            NULL);
    if(!createdSecurity)
    {
        std::cout << "Could not create security descriptor." << std::endl;
        return 0;
    }
    
     // create the security off mutex with the handle
    HANDLE hMutex = CreateMutex(&sa, FALSE, _T("CLR_CASOFF_MUTEX"));
    if(hMutex == NULL)
    {
        std::cout << "Could not acquire mutex, security may already be off." << std::endl;
        return 0;
    }
    
     // wait to unlock
    std::cout << "Security is locked on. Press any key to unlock." << std::endl;
    _getch();

    CloseHandle(hMutex);
    return 0;
}

First we setup a SECURITY_ATTRIBUTES structure that does not allow handles to inherit, and attach a DACL containing a single ACE which denies all access to the Everyone group.  To create the DACL, we use the ConvertStringSecurityDescriptorToSecurityDescriptor function, which is available on Windows XP and later, so you'll need to use an XP machine to run this code, and define _WIN32_WINNT and WINVER to 0x0501 or higher before including windows.h and sddl.h

The string parameter to ConvertStringSecurityDescriptorToSecurityDescriptor looks a little daunting at first, but it's easy to break down:

D Create a DACL
( Begin the first ACE
D; Deny
OICI; Object and container inherit
GA; GENERIC_ALL access
;; No object or inherit GUID
WD; Everyone group
) End ACE

After creating the security attributes, we attempt to create the mutex with them.  If that fails, then it is possible the mutex already exists, perhaps because security is already off.  Once we acquire the mutex, we just wait for the user to press any key to close it and exit.

When running this code, you'll notice that attempting to turn security off with CasPol will fail, and security will always be enforced by the CLR.

Now for the disclaimer.  This code is really for demonstration purposes only, and is not a silver bullet against security being turned off.

  • For reasons we talked about last time, it does not affect the state of security on v1.x
  • There's an obvious race with CasPol ... whoever gets the mutex first wins
  • You cannot protect yourself against machine administrators.  If a machine administrator wants to turn security off, they can attack this code in several ways.  The most obvious being killing the process holding this mutex, or using one of several tools to just close the handle out from underneath it.