User Interface Security in Windows

Posted on 27.07.2016

I have spent quite a lot of my career to date working with Windows in one way or another and a topic I find myself explaining quite often is how things like UAC work in Windows. I have written numerous posts in the past on sites like StackExchange in answer to various questions but I have never really written a full, thorough writeup. This is doing to be it.

If you have read the Windows Internals books, you may find you already know much of what I am about to say and you may find it disappointing. If you haven't, read on and I hope you enjoy.

Tokens, descriptors and identifiers

Let's begin with how Windows decides to trust a process. In order to do this, Windows assigns accounts (user and machine, local or domain members) with a Security Identifier. There are a number of well known SIDs and also some special SIDs. Objects themselves are assigned Access Control Lists containing Access Control Entries. Each ACE contains an access mask, which are the checkboxes you see in many security dialogs in Windows, and apply to each SID or RID (RID is a relative ID, depending on context).

When a user logs into the system and authentication is successful, the user is given an Access Token. As well as some system wide privileges said access token also contains the SID of the logged in user, from which security decisions can be taken.

Processes and threads in Windows all have a primary token describing the user to which they belong and used as part of that process' ability to access objects. It is in this sense that Windows follows the discretionary access control model with elements of RBAC.

Before we go any further, it is worth mentioning a special object in Windows systems, NT AUTHORITY\SYSTEM. This is the "System User" the operating system uses and has unlimited access to the system and is usually considered the end goal of privilege escalation, although realistically this ends as soon as you have any local administrator account, since the OS will not likely prevent you from getting system from this level of privilege.

I will also briefly mention impersonation at this stage. Threads in Windows are able to use additional tokens to impersonate other users (if they have SeImpersonatePrivilege). Thus a process or thread can actually have multiple tokens and a token can be a primary or an impersonation token, depending on context. For our purposes in this article impersonation is not particularly interesting, since the default configuration only allows this privilege for local administrators and service accounts, i.e. privileged accounts. Bamboo Let's take a look at two tokens. For these two tokens, I have chosen explorer.exe and procexp64.exe from my user, who is a member of the local administrators group.

Process explorer showing local token Process explorer showing local elevated token

As you can see, in spite of the fact they share the same SID, there are far more privileges assigned to procexp64.exe than explorer.exe. If we take a closer look using process explorer at the UI integrity level:

Process explorer UI Integrity difference

we will see these are different too. We will come back to this after we have discussed the differences between these two tokens.

User Access Control - How it works

Let's get to it then. When you log in as an administrator account on Windows Vista and above, you get a primary token with a filtered administrator token that is restricted to the point of being a normal user. We have seen this above, now, let's see it from WinDBG's point of view:

Notepad, running as admin but not elevated

kd> !process 0 1 notepad.exe
PROCESS ffff830f6bd8c080
    SessionId: 2  Cid: 0c10    Peb: 1cb6cc3000  ParentCid: 0e3c
    DirBase: 6f559000  ObjectTable: ffff96051fe21000  HandleCount: <Data Not Accessible>
    Image: notepad.exe
    VadRoot ffff830f6cc18490 Vads 82 Clone 0 Private 455. Modified 5. Locked 0.
    DeviceMap ffff96051f628e00
    Token                             ffff96051fe1c980
    ElapsedTime                       00:00:11.990
    UserTime                          00:00:00.000
    KernelTime                        00:00:00.000
    QuotaPoolUsage[PagedPool]         228560
    QuotaPoolUsage[NonPagedPool]      11272
    Working Set Sizes (now,min,max)  (0, 0, 0) (0KB, 0KB, 0KB)
    PeakWorkingSetSize                0
    VirtualSize                       2097268 Mb
    PeakVirtualSize                   2097272 Mb
    PageFaultCount                    0
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      624

kd> !exts.token -n ffff96051fe1c980
_TOKEN ffff96051fe1c980
TS Session ID: 0x2
User: S-1-5-21-1062316794-1009415690-174820158-1000 (no name mapped)
User Groups:
 00 S-1-5-21-1062316794-1009415690-174820158-513 (no name mapped)
    Attributes - Mandatory Default Enabled
 01 S-1-1-0 (Well Known Group: localhost\Everyone)
    Attributes - Mandatory Default Enabled
 02 S-1-5-114 (Well Known Group: NT AUTHORITY\Local account and member of Administrators group)
    Attributes - DenyOnly
 03 S-1-5-32-544 (Alias: BUILTIN\Administrators)
    Attributes - DenyOnly
 04 S-1-5-32-555 (Alias: BUILTIN\Remote Desktop Users)
    Attributes - Mandatory Default Enabled
 05 S-1-5-32-545 (Alias: BUILTIN\Users)
    Attributes - Mandatory Default Enabled
 06 S-1-5-4 (Well Known Group: NT AUTHORITY\INTERACTIVE)
    Attributes - Mandatory Default Enabled
 07 S-1-2-1 (Well Known Group: localhost\CONSOLE LOGON)
    Attributes - Mandatory Default Enabled
 08 S-1-5-11 (Well Known Group: NT AUTHORITY\Authenticated Users)
    Attributes - Mandatory Default Enabled
 09 S-1-5-15 (Well Known Group: NT AUTHORITY    his Organization)
    Attributes - Mandatory Default Enabled
 10 S-1-5-113 (Well Known Group: NT AUTHORITY\Local account)
    Attributes - Mandatory Default Enabled
 11 S-1-5-5-0-526457 Unrecognized SID
    Attributes - Mandatory Default Enabled LogonId
 12 S-1-2-0 (Well Known Group: localhost\LOCAL)
    Attributes - Mandatory Default Enabled
 13 S-1-5-64-10 (Well Known Group: NT AUTHORITY\NTLM Authentication)
    Attributes - Mandatory Default Enabled
 14 S-1-16-8192 (Label: Mandatory Label\Medium Mandatory Level)
    Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-1062316794-1009415690-174820158-513 (no name mapped)
Privs:
 19 0x000000013 SeShutdownPrivilege               Attributes -
 23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default
 25 0x000000019 SeUndockPrivilege                 Attributes -
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes -
 34 0x000000022 SeTimeZonePrivilege               Attributes -
Authentication ID:         (0,808fe)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2a00 ( Token in use )
Token ID: c5b2a            ParentToken ID: 80901
Modified ID:               (0, 8090a)
RestrictedSidCount: 0      RestrictedSids: 0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0      Capabilities: 0000000000000000
LowboxNumberEntry: 0000000000000000
Security Attributes:
Unable to get the offset of nt!_AUTHZBASEP_SECURITY_ATTRIBUTE.ListLink
Process Token TrustLevelSid: (null)

Process explorer, running as admin, elevated

kd> !process 0 1 procexp64.exe
PROCESS ffff830f6cdcb840
    SessionId: 2  Cid: 045c    Peb: 97d422000  ParentCid: 121c
    DirBase: 08ed6000  ObjectTable: ffff96051feee000  HandleCount: <Data Not Accessible>
    Image: procexp64.exe
    VadRoot ffff830f6cb5c7f0 Vads 172 Clone 0 Private 2837. Modified 110286. Locked 0.
    DeviceMap ffff96051ed57b60
    Token                             ffff96051fede060
    ElapsedTime                       00:00:04.967
    UserTime                          00:00:00.187
    KernelTime                        00:00:00.671
    QuotaPoolUsage[PagedPool]         333824
    QuotaPoolUsage[NonPagedPool]      24008
    Working Set Sizes (now,min,max)  (0, 0, 0) (0KB, 0KB, 0KB)
    PeakWorkingSetSize                0
    VirtualSize                       165 Mb
    PeakVirtualSize                   174 Mb
    PageFaultCount                    0
    MemoryPriority                    BACKGROUND
    BasePriority                      13
    CommitCharge                      3518
    Job                               ffff830f6cdd8ab0

kd> !exts.token -n ffff96051fede060
_TOKEN ffff96051fede060
TS Session ID: 0x2
User: S-1-5-21-1062316794-1009415690-174820158-1000 (no name mapped)
User Groups:
 00 S-1-5-21-1062316794-1009415690-174820158-513 (no name mapped)
    Attributes - Mandatory Default Enabled
 01 S-1-1-0 (Well Known Group: localhost\Everyone)
    Attributes - Mandatory Default Enabled
 02 S-1-5-114 (Well Known Group: NT AUTHORITY\Local account and member of Administrators group)
    Attributes - Mandatory Default Enabled
 03 S-1-5-32-544 (Alias: BUILTIN\Administrators)
    Attributes - Mandatory Default Enabled Owner
 04 S-1-5-32-555 (Alias: BUILTIN\Remote Desktop Users)
    Attributes - Mandatory Default Enabled
 05 S-1-5-32-545 (Alias: BUILTIN\Users)
    Attributes - Mandatory Default Enabled
 06 S-1-5-4 (Well Known Group: NT AUTHORITY\INTERACTIVE)
    Attributes - Mandatory Default Enabled
 07 S-1-2-1 (Well Known Group: localhost\CONSOLE LOGON)
    Attributes - Mandatory Default Enabled
 08 S-1-5-11 (Well Known Group: NT AUTHORITY\Authenticated Users)
    Attributes - Mandatory Default Enabled
 09 S-1-5-15 (Well Known Group: NT AUTHORITY\This Organization)
    Attributes - Mandatory Default Enabled
 10 S-1-5-113 (Well Known Group: NT AUTHORITY\Local account)
    Attributes - Mandatory Default Enabled
 11 S-1-5-5-0-526457 Unrecognized SID
    Attributes - Mandatory Default Enabled LogonId
 12 S-1-2-0 (Well Known Group: localhost\LOCAL)
    Attributes - Mandatory Default Enabled
 13 S-1-5-64-10 (Well Known Group: NT AUTHORITY\NTLM Authentication)
    Attributes - Mandatory Default Enabled
 14 S-1-16-12288 (Label: Mandatory Label\High Mandatory Level)
    Attributes - GroupIntegrity GroupIntegrityEnabled
Primary Group: S-1-5-21-1062316794-1009415690-174820158-513 (no name mapped)
Privs:
 05 0x000000005 SeIncreaseQuotaPrivilege          Attributes -
 08 0x000000008 SeSecurityPrivilege               Attributes - Enabled
 09 0x000000009 SeTakeOwnershipPrivilege          Attributes -
 10 0x00000000a SeLoadDriverPrivilege             Attributes - Enabled
 11 0x00000000b SeSystemProfilePrivilege          Attributes -
 12 0x00000000c SeSystemtimePrivilege             Attributes -
 13 0x00000000d SeProfileSingleProcessPrivilege   Attributes -
 14 0x00000000e SeIncreaseBasePriorityPrivilege   Attributes -
 15 0x00000000f SeCreatePagefilePrivilege         Attributes -
 17 0x000000011 SeBackupPrivilege                 Attributes - Enabled
 18 0x000000012 SeRestorePrivilege                Attributes - Enabled
 19 0x000000013 SeShutdownPrivilege               Attributes -
 20 0x000000014 SeDebugPrivilege                  Attributes - Enabled
 22 0x000000016 SeSystemEnvironmentPrivilege      Attributes -
 23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default
 24 0x000000018 SeRemoteShutdownPrivilege         Attributes -
 25 0x000000019 SeUndockPrivilege                 Attributes -
 28 0x00000001c SeManageVolumePrivilege           Attributes -
 29 0x00000001d SeImpersonatePrivilege            Attributes - Enabled Default
 30 0x00000001e SeCreateGlobalPrivilege           Attributes - Enabled Default
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes -
 34 0x000000022 SeTimeZonePrivilege               Attributes -
 35 0x000000023 SeCreateSymbolicLinkPrivilege     Attributes -
Authentication ID:         (0,808da)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: User32             TokenFlags: 0x2000 ( Token in use )
Token ID: ce720            ParentToken ID: 0
Modified ID:               (0, cfdc4)
RestrictedSidCount: 0      RestrictedSids: 0000000000000000
OriginatingLogonSession: 3e7
PackageSid: (null)
CapabilityCount: 0      Capabilities: 0000000000000000
LowboxNumberEntry: 0000000000000000
Security Attributes:
Unable to get the offset of nt!_AUTHZBASEP_SECURITY_ATTRIBUTE.ListLink
Process Token TrustLevelSid: (null)

If you'll look carefully, you can see that the first token has a parent token ID of 80901. It doesn't come from process explorer, since process explorer did not launch notepad.exe, but we would expect to find such a token on the system somewhere with the same full privileges as our procexp64.exe process.

So, UAC is actually a fairly simple premise. Administrator accounts run normal tools and utilities as the filtered token and those that require or request elevation and receive permission get the full token.

How do you request elevation?

If you have looked at Windows programs closely, you'll see some have little shields next to them indicating elevation is required:

Icon with shield

This happens because the application has a manifest embedded in it that looks something like this:

<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
  <requestedPrivileges>
     <requestedExecutionLevel
        level="requireAdministrator"
     />
  </requestedPrivileges>
</security>
</trustInfo>

which requests that the application must be run as an administrator. Other possible values are "highestAvailable" and "asInvoker", which the loader will try to satisfy as needed. We will not talk about the ui attribute in this post.

Applications can also elevate themselves, albeit not directly. They must use ShellExecute with the runas verb, triggering "run as administratror", which the user must then consent to. You can see a programmatic sample of this available on the MSDN.

How does consent happen and what is this secure desktop?

Good question. Let us break this down into several stages.

Sessions, Window Stations and Desktops

Windows displays are made up of several components - sessions, window stations and desktops. This blog post details the difference quite nicely, but I shall cover it here again with screenshots for completeness.

Sessions are exactly what they sound like - the ongoing time a user is logged into the system. There is one session for each user logged into the system.

There is also a "Session 0" in Vista and above. This session holds various NT services as part of Session 0 isolation.

Now let's look at some logged in sessions:

Console session and rdp session Console session, switched user and rdp session

In the first example I have two accounts logged in, Administrator on the local console (called console) and Admin1 on RDP. In the second example, I switched users on the console and connected Admin2. As you can see, each user has their own session, given an ID and a name. The console and tdp-tcp#3 sessions are active whereas Session 1 is Disconnected.

Window stations are a different beast. Each session contains a main Window station, WinSta0, which is the active window station and is the only one that will be displayed for that session, whether attached to a display or not. Any other Window Stations exist "blind" and may not be attached.

Finally, each Window station can contain multiple desktops. On Vista and above each session holds a Winlogon desktop, for logon actions and UAC, and a default desktop for normal activity.

The "secure desktop", as will be shown later and mentioned with consent.exe, is "winlogon". Brian's blog unfortunately has a slight inaccuracy in suggesting there is a second desktop for UAC.

I have mentioned session 0 isolation and this was touted as an important feature of Vista and above. In these OSes any Windows services registered with the service manager run with Session 0. As Brian's blog correctly explains, given permission they can display a GUI on a per-service created desktop in session 0 for which the user is prompted to switch, activating the desktop on that user's Window Station temporarily. If the user does not permit the service to display GUI items, all GUI calls succeed, but are ignored. You can read more about interactive services here

What about the logon screen, you may well ask? Let's do something crazy and have a look:

Screen showing sessions of the logon desktop

In this screenshot, I have used the set hotkey trick to run cmd.exe on the login desktop and then started up the various tools. As you can see, session 1 is created for UI display even at this stage, before the user is created and the explorer.exe shell run as that user.

Now, why would you go to all this effort? In order to explain, it is best to link to the MSDN Walkthrough and functionx tutorials on creating Windows GUI applications. In particular, we consider the WndProc function:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        break;
    case WM_COMMAND:
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }

    return 0;
}

This is a core aspect of any Windows window, not forgetting that controls themselves are also Windows. Given any Window Handle (hwnd) it is possible to SendMessage to that window and simulate an action, including for example a WM_COMMAND with the correct parameters to click a button.

However, Window Handles are relative to the desktop they are on. You cannot send a window message, so isolating desktops prevents malicious programs from driving the UI via messages. Restricting who can access the "winlogon" desktop prevents any old program from switching to that desktop and sending messages in the same way. This is the purpose of the secure desktop.

UIPI

Clearly, though, administrative applications, once approved, do not run on the secure desktop but run side by side. Clearly, if a higher privileged application had "execute as admin" as a menu option, a lower privileged application could simply send it messages to trigger that menu or perform some other privileged action, either escalating its own privilege or achieving the desired goal.

Enter UIPI. There are four integrity levels, System, which are OS-owned components, High for privileged, administrator-token-bearing applications, medium for normal applications and low, which is used for example by internet explorer. According to Windows Internals, informational messages are allowed upwards, but other messages such as WM_COMMAND are filtered out. This prevents a lower integrity process from modifying a higher integrity one.

Integrity levels also apply to to objects more generally to prevent modification. Windows uses the write half of the integrity maxim (the opposite of Bell-LaPadula) "no read down, no write up". Thus, you cannot write to an object of higher integrity than what you yourself hold.

Process and thread objects (and corresponding access to their memory except by defined IPC interfaces) also implement No-Read-Up, meaning you cannot modify a higher integrity process at all.

Pretty much makes sense. Administrative applications are theoretically invisible and unaccessible to lower privilege applications, except as started by the system or approved by the user.

From execution request to launching consent.exe

In order to understand how this works, you must first understand how a process is loaded. There are essentially two ways to do it. The programmatic way via ShellExecute/runas will automatically request the application information service, which lives in the service host (svchost.exe, the undocumented DLL-as-a-service interface Microsoft use) to request elevation. However, what happens if you use the other programmatic way i.e. CreateProces ? Or ShellExecute with the open verb?

When a process loads in Windows, the image loader is actually contained in NtDll with various Ldr calls. Thus, the first stage of launching a process loads NtDll and resolves other outstanding issues, such as DLL linking and processing manifests, setting up SxS records, process virtualization and a whole host of complex things Windows does to ensure compatibility and fight DLL-hell for which it is so famed. What happens, though, if the manifest requests elevation? Windows Internals gives us the answer:

The process is destroyed and an elevation request is sent to the AppInfo service

There you go. This service, as part of AiLaunchConsentUI is responsible for launching consent.exe or allowing elevation automatically, depending on various system registry settings. It is somewhat of a black box, but with symbols and IDA you can see the process here:

IDA screenshot showing AiLaunchConsent running consent.exe

Further investigation will reveal that the %p parameter there, which is a pointer to _CONSENTUI_PARAM_HEADER structure which is read by consent.exe using NtReadVirtualMemory.

Once consent.exe has launched, goes through the following process:

  1. Take a screenshot of the current desktop.
  2. Dim it by applying an overlay of black with transparent pixels set.
  3. Switch to the desktop "winlogon".
  4. Apply this background to the desktop.
  5. Display the consent dialog.
  6. Clear the background.
  7. Switch back to the main desktop.

Obligatory IDA screenshot for CuiGetUserDesktopSnapshot:

Consent.exe CuiGetUserDesktopSnapshot code

If you ever wanted to know how to take a screenshot of a system programmatically from C++, studying this code is a valuable exercise.

Another obligatory IDA screenshot opening the WinLogon Desktop:

Reverse engineered consent.exe showing OpenDesktopW for "winlogon"

As previously mentioned, this is the secure desktop in Windows.

Once this is displayed and confirmation is given, the process is created in the context of the user with the full, rather than filtered token, and the administrative application runs, in the High Integrity UIPI context. The AppInfo (AIS) service takes care of launching the process in a detached way, such that it is not the child of svchost.exe and that it belongs to the user, by launching with CreateProcessAsUser(). From Windows Internals and the MSDN, as well as reversing appinfo.dll, we can see that appinfo.dll uses STARTUPINFOEX structures and the UpdateProcThreadAttribute() function to set the parent ID using PROC_THREAD_ATTRIBUTE_PARENT_PROCESS.

That, ladies and gentleman, is how you go from a normal token to an elevated token.

Footnote: User interface Security in Linux

Does not have a UIPI equivalent and can be insecure (for now).

Revisions: