|Informative Information for the Uninformed|
If one has been using Windows Vista for some time, none of the behavior that has just been described should come across as new. However, there are some cases that have not yet been discussed which one might have observed from time to time with Windows Vista. For example, although programs are typically restricted from being able to synthesize input across integrity levels, there are some limited circumstances where this is permitted. One easy to see instance of this is the on-screen keyboard program (osk.exe) which, despite running without a UAC prompt, can generate keyboard input messages that are transmitted to other processes, even elevated administrative processes. This would at first appear to be a break in the security system; questions along the lines of "If one program can magically send keystrokes to higher integrity processes, why can't another?" come to mind. However, there are in fact some carefully-designed restrictions that are intended to prevent a user (or a program) from arbitrarily being able to execute custom code with this ability.
First of all, in order to request special access to send unrestricted keyboard input, a program's main executable must resolve to a path within the Program Files or Windows directory1. Additionally, any such program must also be signed with a valid digital signature from any trusted code signing root. This is a fairly useless check from a security perspective, in the author's opinion, as anybody can pay a code signing authority to get a code signing certificate in their own name; code signing certificates are not a guarantee of malware-free (or even bug-free) code. Although it would be easy to bypass the second check with a payment to a certificate issuing authority, a plain user cannot so easily bypass the first check relating to the restriction on where the program main executable may be located.
Even if a user cannot launch custom code directly as a program with access to simulate keystrokes to higher integrity processes (known as "uiaccess" internally), one would tend to get the impression that it would be possible to simply inject code into a running osk.exe instance (or other process with uiaccess). This fails as well, however; the process that is responsible for launching osk.exe (the same broken service that is responsible for launching the UAC consent user interface, the "Application Information" (appinfo) service) creates osk.exe with a higher than normal integrity level in order to use the integrity level security mechanism to block users from being able to inject code into a process with access to simulate keystrokes.
When the appinfo service receives a request to launch a program that may require elevation, which occurs when ShellExecute is called to start a program, it will inspect the user's token and the application's manifest to determine what to do. The application manifest can specify that a program runs with the user's integrity level, that it needs to be elevated (in which case a consent user interface is launched), that it should be elevated if and only if the current user is a non-elevated administrator (otherwise the program is to be launched without elevation), or that the program requests the ability to perform keystroke simulation to high integrity processes.
In the case of a launch request for a program requesting uiaccess, appinfo!RAiLaunchAdminProcess is called to service the request. The process is then verified to be within the (hardcoded) set of allowed directories by appinfo!AiCheckSecureApplicationDirectory. After validating that the program is being launched from within an allowed directory, control is eventually passed to appinfo!AiLaunchProcess which performs the remaining work necessary to service the launch request. At this point, due to the "secure" application directory requirement, it is not possible for a limited user (or a user running with low integrity, for that matter) to place a custom executable in any of the "secure" application directories.
Now, the appinfo service is capable of servicing requests from processes of all integrity levels. Due to this fact, it needs to be capable of determining the correct integrity level to create a new process from at this point. Because the new process is not being launched as a full administrator in the case of a process requesting uiaccess, no consent user interface is displayed for elevation. However, the appinfo service does still need a way to protect the new process from any other processes running as that user (as access to synthesize keystrokes is considered sensitive). For this task, the appinfo!LUASetUIAToken function is called by appinfo to protect the new process from other plain user processes running as the calling user. This is accomplished by adjusting the token that will be used to create the new process to run at a higher integrity level than the caller, unless the caller is already at high integrity level (0x3000). The way LUASetUIAToken does this is to first try to query the linked token associated with the caller's token. A linked token is a second, shadow token that is assigned when a computer administrator logs in with UAC enabled; in the UAC case, the user normally runs as a restricted version of themselves, without their administrative privileges (or Administrators group membership), and at medium integrity level.
If the calling user does indeed have a linked token, LUASetUIAToken retrieves the integrity level of the linked token for use with the new process. However, if the user doesn't have a linked token (i.e. they are logged on as a true plain user and not an administrator running without administrative privileges), then LUASetUIAToken uses the integrity level of the caller's token instead of the token linked with the caller's token (in other words, the elevation token). In the case of a computer administrator this approach would normally provide sufficient protection, however, for a limited user, there exists a small snag. Specifically, the integrity level that LUASetUIAToken has retrieved matches the integrity level of the caller, so the caller would still have free reign over the process.
To counteract this issue, there is an additional check baked into LUASetUIAToken to determine if the integrity level that was selected is at (or above) high integrity. If the integrity level is lower than high integrity, LUASetUIAToken adds 16 to the integrity level (although integrity levels are commonly thought of as just having four values, that is, low, medium, high, and system, there are 0x1000 unnamed integrity levels in between each named integrity level). So long as the numeric value of the integrity level chosen is greater than the caller's integrity level, the new process will be protected from the caller. In the case of the caller already being a full, elevated administrator, there's nothing to protect against, so LUASetUIAccess doesn't attempt to raise the integrity level above high integrity.
After determining a final integrity level, LUASetUIAToken changes the integrity level in the token that will be used to launch the new process to match the desired integrity level. At this point, appinfo is ready to create the process. If needed, the user profile block is loaded and an environment block is created, following which advapi32!CreateProcessAsUser is called to launch the uiaccess-enabled application for the caller with a raised integrity level. After the process is created, the output parameters of CreateProcessAsUser are marshalled back into the caller's process, and AiLaunchProcess signals successful completion to the caller.
If one has been following along so far, the question of ``How does all of this relate to Internet Explorer Protected Mode'' has probably crossed one's mind. It turns out that there's a slight deficiency in the protocol outlined above with respect to creating uiaccess processes. The problem lies in the fact that AiLaunchProcess returns the output parameters of CreateProcessAsUser back to the caller's process. This is dangerous, because in the Windows security model, security checks are done when one attempts to open a handle; after a handle is opened, the access rights requested are forever more associated with that handle, regardless of who uses the handle. In the case of appinfo, this turns out to be a real problem because appinfo, being the creator of the new process, is handed back a thread and process handle that grant full access to the new thread and process, respectively. Appinfo then marshals these handles back to the caller (which may be running at low integrity level). At this point, a privilege escalation problem has occured; the caller has been essentially handed the keys to a higher integrity process. While the caller would never normally be able to open a handle to the new process on its own, in this case, it doesn't have to, as the appinfo service does so on its behalf and returns the handles back to it.
Now, in the ShellExecute case, the client stub for the appinfo AiLaunchAdminProcess routine doesn't want (or need) the process or thread handles, and closes them immediately after. However, this is obviously not a security barrier, as this code is running in the untrusted process and could be patched out. As such, there exists a privilege escalation hole of sorts with the appinfo service. It can be abused to, without user interaction, leak a handle to a higher integrity process to a low integrity process (such as Internet Explorer when operating in Protected Mode). Furthermore, even Internet Explorer in Protected Mode, running at low integrity, can request to launch an already-existing uiaccess-flagged executable, such as osk.exe (which is conveniently already in a "secure" application directory, the Windows system directory). With a process and thread handle as returned by appinfo, it is possible to inject code into the new process, and from there, as they say, the rest is history.