3

I have an application that needs to register controls when it is Run As Administrator and I would like the application to drop the elevated privileges when they are no longer needed. I have read that this can be done with AdjustTokenPrivileges, (Dropping privileges in C++ on Windows) but I have not found any sample code that will allow me to go from SECURITY_MANDATORY_HIGH_RID to SECURITY_MANDATORY_MEDIUM_RID. My code is written C++ using Visual Studio.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
Brentarsky
  • 41
  • 3
  • 4
    The right way to do this is to execute the code that requires elevation in a separate process. A good way to achieve this is with the COM elevation moniker. – David Heffernan Jun 19 '17 at 14:52
  • we can easy downgrade integrity level - system allow this by `SetTokenInformation` with `TokenIntegrityLevel`. also when you set less than `SECURITY_MANDATORY_HIGH_RID` system automatically disable some privileges in token and it can not more be enabled - this is one way process. but your token groups will be unchanged - you still will be member of *Administrators* - because this group is mandatory - it can not be disabled – RbMm Jun 19 '17 at 16:49
  • 1
    A simple way to do this without COM or complicated security API calls is to specify `asInvoker` in the manifest of the application and launch yourself elevated by calling `ShellExecuteEx()` with the `runas` verb when needed. The child process will only do the task that needs elevation and exits. Then the original process will continue to run unelevated. If original process was already launched elevated then ofc it can do the job by itself. – zett42 Jun 19 '17 at 19:01
  • Thank you for your comments, I am trying to support Windows 7 and 10 – Brentarsky Jun 21 '17 at 00:19

1 Answers1

3

if you want

sample code that will allow me to go from SECURITY_MANDATORY_HIGH_RID to SECURITY_MANDATORY_MEDIUM_RID.

you need open own process token with TOKEN_ADJUST_DEFAULT (for change integrity level - this is mandatory) and WRITE_OWNER (for change mandatory label in your token security descriptor - otherwise you not be able more open own token with write access more - but this is optional)

call SetTokenInformation with TokenIntegrityLevel let downgrade current integrity level. after this it already can not be raised.

also internally system disable some privileges in token, when we set integrity level below SECURITY_MANDATORY_HIGH_RID. i doubt that this is documented, but from my test, next privileges is disabled and can not be more enabled:

SeTakeOwnershipPrivilege
SeLoadDriverPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
SeImpersonatePrivilege
SeDelegateSessionUserImpersonatePrivilege

but you still will be member of admin group (S-1-5-32-544 Administrators) and this group can not be disabled by AdjustTokenGroups because function cannot disable groups with the SE_GROUP_MANDATORY attribute - but S-1-5-32-544 have this attribute. and change primary process token also not possible already (it possible only after process created (in suspended state) and before it begin executed (resumed) if we have SeAssignPrimaryTokenPrivilege )

so really your application will be in intermediate state after downgrade integrity level to medium - from one side you lost most significant privileges, but access to objects (files, registry keys) primary based not on privileges but group membership and mandatory label - vs integrity level. because Administrators group still will be enabled in your token and most objects not have explicit mandatory label (so medium by default) - your app still be able open/create files/keys which limited applications (admin under uac) can not. however if you downgrade you integrity level to SECURITY_MANDATORY_LOW_RID - you really got limited application, but most legacy code incorrect worked under LowLevel integrity

minimal code for downgrade integrity level:

ULONG SetMediumLevel()
{
    HANDLE hToken;

    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT, &hToken))
    {
        ULONG cbSid = GetSidLengthRequired(1);

        TOKEN_MANDATORY_LABEL tml = { { alloca(cbSid)} };

        ULONG dwError = NOERROR;

        if (!CreateWellKnownSid(WinMediumLabelSid, 0, tml.Label.Sid, &cbSid) ||
            !SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
        {
            dwError = GetLastError();
        }

        CloseHandle(hToken);

        return dwError;
    }

    return GetLastError();
}

but here exist thin point - token itself have security descriptor with explicit label. and high integrity process have High Mandatory Label with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP access policy. this mean that we no more can open our process token with write access (TOKEN_ADJUST_* ) (with read access can). this can create problems if app in some place try open self process token with this access (some bad design code can when need query own process token properties instead TOKEN_QUERY access ask TOKEN_ALL_ACCESS and fail in this point). for prevent this potential issue we can change mandatory label of our token security descriptor before downgrade it integrity:

ULONG SetMediumLevel()
{
    HANDLE hToken;

    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_DEFAULT|WRITE_OWNER, &hToken))
    {
        ULONG cbSid = GetSidLengthRequired(1);

        TOKEN_MANDATORY_LABEL tml = { { alloca(cbSid)} };

        ULONG dwError = NOERROR;
        if (CreateWellKnownSid(WinMediumLabelSid, 0, tml.Label.Sid, &cbSid))
        {
            SECURITY_DESCRIPTOR sd;
            ULONG cbAcl = sizeof(ACL) + FIELD_OFFSET(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + cbSid;
            PACL Sacl = (PACL)alloca(cbAcl);

            if (!InitializeAcl(Sacl, cbAcl, ACL_REVISION) ||
                !AddMandatoryAce(Sacl, ACL_REVISION, 0, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, tml.Label.Sid) ||
                !InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ||
                !SetSecurityDescriptorSacl(&sd, TRUE, Sacl, FALSE) ||
                !SetKernelObjectSecurity(hToken, LABEL_SECURITY_INFORMATION, &sd) ||
                !SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
            {
                dwError = GetLastError();
            }
        }

        CloseHandle(hToken);

        return dwError;
    }

    return GetLastError();
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Thanks for the post. Can I assume that the second code sample is only needed if I believe that my code will attempt to open a token with TOKEN_ALL_ACCESS after the elevated privileges are dropped. – Brentarsky Jun 21 '17 at 20:29
  • @Brentarsky - yes, if your code try open token with `TOKEN_ALL_ACCESS` , `GENERIC_WRITE` or any `TOKEN_ADJUST_*` - you got access denied. how ever can open for read (query) token. usually first code enough, but not always – RbMm Jun 21 '17 at 21:42