« Back to home

Administrator Protection Review

Windows Administrator Protection: A Red Team Analysis

I started researching Administrator Protection a few months back, but I must admit that I never really believed that it would ever make it to a release build of Windows in its current form.

Then I saw a post on BlueSky announcing that it is in-fact coming to Windows 11, and soon:

In this post, I’ll be dumping my research into Windows Administrator Protection. There isn’t anything too groundbreaking here as I only touched this technology on a surface level for tooling compatibility testing, but the hope is that anyone else diving into this area will find the content useful.

Note: Administrator Protection is still in active development and everything shown in this post is subject to change as this feature develops.

What is Admin Protection and Why Not Use UAC?

You can play around with Admin Protection on the canary build of Windows 11. I warn you in advance, you’ll see those elevation prompts more than before! And that’s by design; Microsoft created Administrator Protection to explicitly eliminate UAC backdoors, which means:

  • No EXE autoelevate backdoors
  • No COM elevate backdoors

These workarounds were implemented as a method of avoiding the UAC elevation prompt from appearing too often after users complained during Vista’s UAC debut. Microsoft has since reversed course by outwardly calling out the increase in elevation prompts in their release post:

With Administrator protection, auto-elevation is removed. Users will notice an increase in consent prompts, though many fewer than the Vista days as much work has been done to clean up elevation points in most workflows.

So other than the removal of backdoors, what else separates Administrator Protection from UAC?

Well, the big one is that split tokens are (mostly) gone. Previously under UAC, there were two distinct token states. An administrative user would have a token in medium integrity with all administrative privileges and security groups stripped away. After the user consents to a UAC prompt (e.g., consent.exe) the system grants a high integrity token that restores all privileges and groups. The caveat here is in the filtering, as both tokens belonged to the same user account. In Admin Protection mode, you now have a completely separate admin account with a prefix of admin_. In the case of my Windows system, xpn has a corresponding admin_xpn account when I need the admin privileges and security groups typically granted to a high integrity token. In the Admin Protection world, this is called a Shadow Admin account.

Existing Research

Before I dig too deep into this, I wanted to give a shoutout to Rudy Ooms at Call4Cloud. When I started looking into Shadow Admin accounts, his was the first blog post that I found and it covered Administrator Protection back in October 2024 in a lot of detail.

The reason that I continued beyond this post was because I wanted to understand the operational limitations and our tooling requirements for engagements (we like to be prepared). But I wanted to make it clear that Rudy’s post set the pace and is recommended reading.

Additionally, Microsoft released a detailed post called “Introducing Administrator Protection“.

There are sections from Microsoft’s post where “bypasses” were called out which were bugging me, mainly because I wanted to understand why known vulnerabilities still existed.

The reason that I also reference this is because it actually dives into a lot of details of the new functionality, including gaps in its implementation. Kudos for Microsoft being upfront about their design process.

What Makes a Shadow Admin Account?

Administrator Protection revolves around the concept of a Shadow Admin account. If we take a look in the service account manager (SAM) hive, we see that the Shadow Admin is just a standard administrator account with a few additional attributes set against it:

It is actually the ShadowAccountBackLink key which links to the security identifier (SID) of the regular user account which determines if an account is classed as a shadow admin or not. Without this key being set, the account is just another admin account and is subject to token filtering.

We can see that this is the case in samsrv.dll - SamrIsShadowAdminAccount, which checks if this registry value is present:

We can use samlib.dll - SamiIsShadowAdminAccount programmatically to determine if a SID is a Shadow Admin account:

typedef NTSYSAPI NTSTATUS(*p_SamIsShadowAdminAccount)(
	PSID sid,
	void* out1,
	void* out2,
	void* out3
	);

void *modHandle = (void*)LoadLibraryA("samlib.dll");

p_SamIsShadowAdminAccount _SamIsShadowAdminAccount;
_SamIsShadowAdminAccount = (p_SamIsShadowAdminAccount)GetProcAddress((HMODULE)modHandle, "SamiIsShadowAdminAccount");
	
if (_SamIsShadowAdminAccount == NULL)
{
	std::cout << "Failed to get address of SamIsShadowAdminAccount\n";
	return 1;
}
	
void* out1 = NULL;
void* out2 = NULL;
void* out3 = NULL;

PSID sid;

// SID to check
if (ConvertStringSidToSidA("S-1-5-21-1524884646-3830121960-1456475082-1003", &sid) == 0) {
	std::cout << "ConvertStringSidToSid failed\n";
	return 1;
}

NTSTATUS status = _SamIsShadowAdminAccount(sid, &out1, &out2, &out3);
if (status != 0) {
	 printf("Cannot find SID\n");
	 return;
}
	
if (out1 != (void*)0) {
	  printf("This is a shadow admin account\n");
} else {
	  printf("Not a shadow admin account\n");
}

How are Shadow Admin Accounts Created?

Shadow Admin accounts are actually created on first use. If we have a user named build, then admin_build will actually be created upon elevation.

To do this, Rudy Ooms notes that the ShadowAdmin::CreateShadowAdminAccount method is called from samsrv.dll.

From an RPC perspective, the entry method called is SamrFindOrCreateShadowAdminAccount and can be invoked with the samlib.dll - SamiFindOrCreateShadowAdminAccount API method:

typedef NTSYSAPI NTSTATUS(*p_SamiFindOrCreateShadowAdminAccount) (
	void* DomainHandle,
	void **out1,
	void** out2
);

void *modHandle = (void*)LoadLibraryA("samlib.dll");

p_SamiFindOrCreateShadowAdminAccount _SamFindOrCreateShadowAdminAccount;
_SamFindOrCreateShadowAdminAccount = (p_SamiFindOrCreateShadowAdminAccount)GetProcAddress((HMODULE)modHandle, "SamiFindOrCreateShadowAdminAccount");
if (_SamFindOrCreateShadowAdminAccount == NULL) {
	return 1;
}

void *modHandle = (void*)LoadLibraryA("samlib.dll");
if (modHandle == NULL) {
	return 1;
}

// SID to create ShadowAdmin account for
if (ConvertStringSidToSidA("S-1-5-21-3889136333-1358944941-3928491093-1001", &sid) == 0) {
	return 1;
}
	
void* arg1 = NULL;
void* arg2 = NULL;
	
NTSTATUS status = _SamFindOrCreateShadowAdminAccount((void*)(sid), &arg1, &arg2);
if (status != 0) {
	printf("SamiFindOrCreateShadowAdminAccount returned: %x\n", status3);
}

How Do Users Get a Shadow Account Token?

As with UAC, Consent.exe is responsible for creating an access token for the Shadow Admin account. This is done via LogonUserExExW. And, as seen below, no password is passed:

From within lsass.exe, lsasrv.dll is responsible for validating that the LogonUserExExW is coming from a permitted process. Within the LsapCanLogonShadowAdmin function, a check is first completed to make sure that the incoming request is from a process with a token set to SYSTEM integrity.

Next, the calling process needs to be in a allowlist. This is done in the LsapIsProcessOnShadowAdminAllowList.

At the time of checking, only Consent.exe is present in the list. This is a nice set of checks because, if you were SYSTEM, you could generate your own access to the admin_ account.

How Does LSASS Know Which Shadow Admin Account to Use?

Within the SAM hive, there is a registry value of ShadowAccountForwardLinkSid associated with the SID of the Shadow Admin user account.

The corresponding Shadow Admin account will have a ShadowAccountBackLinkSid registry value pointing to the SID of the account.

As a fun experiment, you can actually update the ShadowAccountForwardLinkSid to point to any account Shadow Admin SID. You will need to reboot the system as the SID is cached in the local security authority subsystem service (LSASS); however, upon elevation, you’ll find that elevated processes spawn as another account.

Below we can see this in action, where build2 spawns an elevated process as admin_build as we switch out the SID.

This will only work for other Shadow Admin accounts, as the LogonUserExExW uses a blank password.

Logging In As a Shadow Admin

If the account is determined to be a Shadow Admin, we cannot log in as this account using methods such as runas /user:admin_test due to the default blank password requirement.

But even if we allow this, we will get an Access Denied warning.

If we remove the ShadowAccountBackLinkSid registry value and enable the ability for passwordless login, we can login fine with a blank password.

Obviously, this means that LogonUserW will work as:

LogonUser(L"admin_build", L".", L"", LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)

Noted Bypasses – LocalAccountTokenFilterPolicy

There are still bypasses for Admin Protection, as Microsoft noted in their blog post here. So, if things are so locked down, why does this work?

The two bypasses Microsoft called out are James Forshaw’s “Bypassing UAC in the Most Complex Way Possible“ and SplinterCode’s “Bypassing UAC with SSPI Datagram Context“ techniques.

These both have one thing in common: they take advantage of LocalAccountTokenFilterPolicy.

If we pull apart lsasrv.dll, we find the referenced LsaISetSupplimentalTokenInfo function referenced in James’ post. This is called by the msv1_0 - SsprHandleAuthenticationMessage function upon establishing a connection.

This function is responsible for determining if our network access is downgraded to use the medium integrity token.

This area of code doesn’t look like it has been updated with the introduction of Administrator Protection, which means that existing “bypasses” still exist. The reason I put that in quotes is because you actually end up in a state where the token created is just a high integrity token for the user; not the admin_ shadow admin account.

For example, if we access the ADMIN$ share using a domain admin account over a remote SMB connection, we see that access is on behalf of the user, not the Shadow Admin account.

Similarly, if we use something like wmiexec.py to spawn a new process, that process will end up running with High Integrity within its own user account; not as a Shadow Admin account.

So, actually, what we have now are three states with the introduction of Administrator Protection:

  • The typical UAC style medium integrity token
  • The admin_ Shadow Account high integrity token
  • The old UAC type high integrity token

Things will potentially change as this tech evolves, but it answers the question that I had as to why this change didn’t fix those existing vulnerabilities.

Noted Bypasses – RunOnce

Microsoft also noted RunOnce as an auto-elevate method for Admin Protection.

It should be noted that not all auto-elevations have been removed. Namely, the Run and RunOnce registry keys found in the HKEY_LOCAL_MACHINE hive will still auto-elevate as needed. Appropriately, these keys are ACL’d such that only an administrator can modify them.

We can see this is the case by adding a new registry key.

On reboot and login, we get a Shadow Admin shell.

This works using the existing RaiProcessRunOnce RPC method invoked from explorer.exe‘s AicProcessRunOnce.

This RPC method is exposed via AppInfo service as it always was in UAC. The parameters passed to this method do not unfortunately influence the command executed, meaning that we would need admin access to write to the RunOnce registry key.

UIAccess Bypasses

As noted in Microsoft’s documentation, UIAccess Bypasses are partially prevented as current “UAC” based UIAccess elevations use auto-elevation of privileged processes before hijacking the UI. If, however, a legitimate app has been elevated, UIAccess can still be used to interact with the Window.

A good example is the UIAccess DLLHijack from R41N3RZUF477. We need to modify the OskSupport dynamic-link library (DLL) to send keys, but we see that this works just fine.

Fingerprinting Users & Blank Passwords

Shadow Admins give us a shot at anonymously enumerating if an admin has previously authenticated to a host.

If the admin user has never authenticated to the host, you’ll just get the regular Access Denied error.

This, of course, also means that if (for some reason) blank passwords are allowed on the host, then you have the ability to authenticate to the host remotely as the admin account without a password.

For example, if we use smbclient.py, we can access the server without a password.

However, and unfortunately, our token is going to be filtered 😟

Reflections and Conclusions

So, what do we have (other than a trail of “out of scope” UAC bypasses now being used to create a feature based off the hard work of researchers)?

Essentially, we have separate Admin accounts now for elevated actions. We also still have a weird scenario in which UAC elevated tokens do exist for admin sessions, but this feels like an unfinished feature at the moment. It will be interesting to see what happens when this rolls out in release.

What will also be interesting is that now files written to the users profile locations such as Desktop, Downloads etc., will belong to separate accounts depending on if the CreateFile was completed as an elevated user or not.