Wednesday 11 September 2019

Overview of Windows Execution Aliases

I thought I'd blogged about this topic, however it turns out I hadn't. This blog is in response to a recent Twitter thread from Bruce Dawson on a "fake" copy of Python which Microsoft seems to have force installed on some peoples Windows 10 1903 installations. I'll go through the main observation in the thread that the Python executable is 0 bytes in size, how this works under the hood to start a process and I'll finish with a dumb TOCTOU bug which still exists in part of the implementation which _might_ be useful as part of an EOP chain.

Execution Aliases for UWP applications were introduced in Windows 10 Fall Creators Update (1709/RS3). For application developers this feature is exposed by adding an AppExecutionAlias XML element to the application's manifest. The manifest information is used by the AppX installer to drop the alias into the %LOCALAPPDATA%\Microsoft\WindowsApps folder, which is also conveniently (or not depending on your POV) added to the user PATH environment variable. This allows you to start a UWP application as if it was a command line application, including passing command line arguments. One example is shown below, which is taken from the WinDbgX manifest.

<uap3:Extension Category="windows.appExecutionAlias" Executable="DbgX.Shell.exe" EntryPoint="Windows.FullTrustApplication"> <uap3:AppExecutionAlias> <desktop:ExecutionAlias Alias="WinDbgX.exe" />
</uap3:AppExecutionAlias> </uap3:Extension>

This specifies an execution alias to run DbgX.Shell.exe from the file WinDbgX.exe. If we go to the WindowsApps folder we can see that there is a file with that name, and as mentioned in the Twitter thread it is a 0 byte file. Also if you try and open the file (say using the type command) it fails.

Directory listing of WindowsApps folder showing 0 byte WinDbgX.exe file and showing that trying to open file fails.

How can an empty file result in a process being created? Executing the WinDbgX.exe file inside a shell while running Process Monitor shows some interesting results which I've highlighted below:

Process Monitor output showing opens to WinDbgX with a "REPARSE" result and also a call to get the reparse point data.

The first thing to highlight is the CreateFile calls which return a "REPARSE" result. This is a good indication that the file contains a reparse point. You might assume therefore that this file is a symbolic link to the real target, however a symbolic link would still be possible to open which we can't do. Another explanation is the reparse point is a custom type, not understood by the kernel. This ties in with the subsequent call to FileSystemControl with the FSCTL_GET_REPARSE_POINT code which would indicate some user-mode code is requesting information about the stored reparse point. Looking at the stack trace we can see who's requesting the reparse point data:

Stack trace of FSCTL_GET_REPARSE_POINT showing calls from CreateProcessInternal

The stack trace shows the reparse point data is being queried from inside CreateProcess, through the exported function LoadAppExecutionAliasInfoEx. We can dig into CreateProcessInternal to see how it all works:

HANDLE token = ...; NTSTATUS status = NtCreateUserProcess(ApplicationName, ..., token); if (status == STATUS_IO_REPARSE_TAG_NOT_HANDLED) { LPWSTR alias_path = ResolveAlias(ApplicationName); PEXEC_ALIAS_DATA alias; LoadAppExecutionAliasInfoEx(alias_path, &alias); status = NtCreateUserProcess(alias.ApplicationName, ..., alias.Token); }

CreateProcessInternal will first try and execute the path directly, however as the file has an unknown reparse point the kernel fails to open the file with STATUS_IO_REPARSE_TAG_NOT_HANDLED. This status code provides a indicator to take an alternative route, the alias information is loaded from the file's reparse tag using LoadAppExecutionAliasInfoEx and an updated application path and access token are used to start new the new process.

What is the format of the reparse point data? We can easily dump the bytes and have a look in a hex editor:

Hex dump of reparse data with highlighted tag.

The first 4 bytes is the reparse tag, in this case it's 0x8000001B which is documented in the Windows SDK as IO_REPARSE_TAG_APPEXECLINK. Unfortunately there doesn't seem to be a corresponding structure, but with a bit of reverse engineering we can work out the format is as follows:

Version: <4 byte integer>
Package ID: <NUL Terminated Unicode String>
Entry Point: <NUL Terminated Unicode String>
Executable: <NUL Terminated Unicode String>
Application Type: <NUL Terminated Unicode String>

The reason we have no structure is probably because it's a serialized format. The Version field seems to be currently set to 3, I'm not sure if there exists other versions used in earlier Windows 10 but I've not seen any. The Package ID and Entry Point is information used to identify the package, an execution alias can't be used like a shortcut for a normal application it can only resolve to an installed packaged application on the system. The Executable is the real file to executed that'll be used instead of the original 0 byte alias file. Finally Application Type is the type of application being created, while a string it's actually an integer formatted as a string. The integer seems to be zero for desktop bridge applications and non-zero for normal sandboxed UWP applications. I implemented a parser for the reparse data inside NtApiDotNet, you can view it in NtObjectManager using the Get-ExecutionAlias cmdlet.

Result of executing Get-ExecutionAlias WinDbgX.exe

We now know how the Executable file is specified for the new process creation but what about the access token I alluded to? I actually mentioned about this at Zer0Con 2018 when I talked about Desktop Bridge. The AppInfo service (of UAC fame) has an additional RPC service which creates an access token from a execution alias file. This is all handled inside LoadAppExecutionAliasInfoEx but operates similar to the following diagram:

Operation of RAiGetPackageActivationToken.

The RAiGetPackageActivationToken RPC function takes a path to the execution alias and a template token (which is typically the current process token, or the explicit token if CreateProcessAsUser was called). The AppInfo service reads the reparse information from the execution alias and constructs an activation token based on that information. This token is then returned to the caller where it's used to construct the new process. It's worth noting that if the Application Type is non-zero this process doesn't actually create the AppContainer token and spawn the UWP application. This is because activation of a UWP application is considerably more complex to stuff into CreateProcess, so instead the execution alias' executable file is specified as the SystemUWPLauncher.exe file in system32 which completes activation based on the package information from the token.

What information does the activation token contain? It's basically the Security Attribute information for the package, this can't normally be modified from a user application, it requires TCB privilege. Therefore Microsoft do the token setup in a system service. An example token for the WinDbgX alias is shown below:

Token security attributes showing WinDbg package identity.

The rest of the activation process is not really that important. If you want to know more about the process checkout my talks on Desktop Bridge and the Windows Runtime.

I promised to finish up with a TOCTOU attack. In theory we should be able to create execution alias for any installed application package, it might not start a working process be we can use RAiGetPackageActivationToken to get a new token with explicit package security attributes which could be useful for further exploitation. For example we could try creating one for the Calculator package with the following PowerShell script (note this uses version information for calculator on 1903 x64).

Set-ExecutionAlias -Path C:\winapps\calc.exe `
     -PackageName "Microsoft.WindowsCalculator_8wekyb3d8bbwe" `
     -EntryPoint "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App" `
     -Target "C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_10.1906.53.0_x64__8wekyb3d8bbwe\Calculator.exe" `
     -AppType UWP1

If we call RAiGetPackageActivationToken this works and creates a new token, however it creates a reduced privilege UWP token (it's not an AppContainer but for example all privileges are stripped and the security attributes assumes it'll be in a sandbox). What if we wanted to create a Desktop Bridge token which isn't restricted in this way? We could change the AppType to Desktop, however if you do this you'll find RAiGetPackageActivationToken fails with an access denied error. Digging a bit deeper we find it fails in daxexec!PrepareDesktopAppXActivation, specifically when it's checking if the package contains any Centennial (now Desktop Bridge) applications.

HRESULT PrepareDesktopAppXActivation(PACTIVATION_INFO activation_info) { if ((activation_info->Flags & 1) == 0) { CreatePackageInformation(activation_info, &package_info); if (FAILED(package_info->ContainsCentennialApplications())) { return E_ACCESS_DENIED; // <-- Fails here. } } // ... }

This of course makes perfect sense, no point creating an desktop activation token for a package which doesn't have desktop applications. However, notice the if statement, if bit 1 is not set it does the check, however if set these checks are skipped entirely. Where does that bit get set? We need to go back to caller of PrepareDesktopAppXActivation, which is, unsurprisingly, RAiGetPackageActivationToken.

ACTIVATION_INFO activation_info = {}; bool trust_label_present = false; HRESULT hr = IsTrustLabelPresentOnReparsePoint(path, &trust_label_present); if (SUCCEEDED(hr) && trust_label_present) { activation_info.Flags |= 1; } PrepareDesktopAppXActivation(&activation_info);

This code shows that the flag is set based on the result of IsTrustLabelPresentOnReparsePoint. While we could infer what that function is doing let's reverse that as well:

HRESULT IsTrustLabelPresentOnReparsePoint(LPWSTR path,
bool *trust_label_present) { HANDLE file = CreateFile(path, READ_CONTROL, ...); if (file == INVALID_HANDLE_VALUE) return E_FAIL; PSID trust_sid; GetWindowsPplTrustLabelSid(&trust_sid); PSID sacl_trust_sid; GetSecurityInfo(file, SE_FILE_OBJECT, PROCESS_TRUST_LABEL_SECURITY_INFORMATION, &sacl_trust_sid); *trust_label_present = EqualSid(trust_sid, sacl_trust_sid); return S_OK; }

Basically what this code is doing is querying the file object for its Process Trust Label. The label can only be set by a Protected Process, which normally we're not. There are ways of injecting into such processes but without that we can't set the trust label. Without the trust label the service will do the additional checks which stop us creating an arbitrary desktop activation token for the Calculator package.

However notice how the check re-opens the file. This is occurring after the reparse point has been read which contains all the package details. It should be clear that here is a TOCTOU, if you can get the service to first read a execution alias with the package information, then switch that file to another which has a valid trust label we can disable the additional checks. This was an attack that my BaitAndSwitch tool was made for. If you build a copy then run the following command you can then use RAiGetPackageActivationToken with the path c:\x\x.exe and it'll bypass the checks:

BaitAndSwitch c:\x\x.exe c:\path\to\no_label_alias.exe c:\path\to\valid_label_alias.exe x

Note that the final 'x' is not a typo, this ensures the oplock is opened in exclusive mode which ensures it'll trigger when the file is initially opened to read the package information. Is there much you can really do with this? Probably not, but I thought it was interesting none the less. It'd be more interesting if this had disabled other, more important checks but it seems to only allow you to create a desktop activation token.

That about wraps it up for now. Embedding this functionality inside CreateProcess was clever, certainly over the crappy support for UAC which requires calling ShellExecute. However it also adds new and complex functionality to CreateProcess which didn't exist before, I'm sure there's probably some exploitable security bug in the code here, but I'm too lazy to find it :-)

Sunday 1 September 2019

The Art of Becoming TrustedInstaller - Task Scheduler Edition

2 years ago I wrote a post running a process in the TrustedInstaller group. It was pretty well received, and as others pointed out there's many way of doing the same thing. However in my travels I came across a new way I've not seen documented before, though I'm sure someone will point out where I've missed documentation. As with the previous post, this does require admin privileges, it's not a privilege escalation. Also I tested the behavior I'm documented on Windows 10 1903. Your mileage may vary on different versions of Windows.

It revolves around the Task Scheduler (obvious by the title I guess), specifically calling the IRegisteredTask::RunEx method exposed by the Task Scheduler COM API. The prototype of RunEx is as follows:

HRESULT RunEx(
  VARIANT      params,
  LONG         flags,
  LONG         sessionID,
  BSTR         user,
  IRunningTask **ppRunningTask
);

The thing we're going to use is the user parameter, which is documented as "The user for which the task runs." Cheers Microsoft! Through a bit of trial and error, and some reverse engineering it's clear the user parameter can take three types of string values:

  1. A normal user account. This can be the name or a SID. The user must be logged on at the time of starting the task as far as I can tell.
  2. The standard system accounts, i.e. SYSTEM, LocalService or NetworkService.
  3. A service account!
Number 3 is the one we're interested in here, it allows you to specify an installed service account, such as TrustedInstaller and the task will run as SYSTEM with the service SID included. Let's try it out.

The advantage of using the user parameter is the task can be registered to run as a normal user, and we'll change it at run time to be more sneaky. In theory you could directly register the task to run as TrustedInstaller, but then it'd be more obvious if anyone went looking. First we need to create a scheduled task, run the following script in PowerShell to create a simple task which will run notepad.

$a = New-ScheduledTaskAction -Execute notepad.exe
Register-ScheduledTask -TaskName 'TestTask' -Action $a

Now we need to call RunEx. While PowerShell has a Start-ScheduledTask cmdlet neither it, or the schtasks.exe /Run command allows you to specify the user parameter (aside, the /U parameter for schtask does not do what you might think). Instead as the COM API is scriptable we can just run some PowerShell again and use the COM API directly.

$svc = New-Object -ComObject 'Schedule.Service'
$svc.Connect()

$user = 'NT SERVICE\TrustedInstaller'
$folder = $svc.GetFolder('\')
$task = $folder.GetTask('TestTask')
$task.RunEx($null, 0, 0, $user)

After executing this script you should find a copy of notepad running as SYSTEM with with the TrustedInstaller group in the access token.


Enjoy responsibly.