What is a Native Application?
Most Windows applications come in two “flavors”, GUI and console (command-line) applications. There are a few differences between the two, most notably the Windows Subsystem on top of which they are executed. The Subsystem can be seen by examining an executable’s PE header:
There are actually quite a few Subsystem options, but today we are going to be focusing our interest in the Native subsystem. Native Applications are PEs compiled to run against the Native Windows subsystem. Unlike the other two Subsystems you might already be familiar with, the Native one is quite bare-bones.
First of all, attempting to execute a Native Application through File Explorer (by double-clicking) or the command line yields the following error:
Our only option for executing Native Applications (aside from manually loading the binary in memory) is RtlCreateUserProcess.
Secondly, Native Applications can only depend on ntdll.lib, the static version of ntdll.dll. This is quite an important limitation, since there is also no possibility of dynamically importing libraries. Essentially, we have to stick to NTDLL exports. This happens because applications targeting the Native subsystem are generally expected to be executed early on in the userland bootstrap sequence, where other subsystems might not have been (fully) initialized.
Why develop a Native Application?
An interesting, but relatively undocumented execution mechanism in Windows resides in smss.exe, the Windows Session Manager. Session Manager, among other things, provides the functionality of auto-starting applications during the initialization of the user session. An example of an intended use case is filesystem verification, which happens by having smss.exe spawn autochk.exe during the “Welcome” logon screen.
This mechanism utilizes the following registry hive:
Notice the BootExecute multi-string entry which reads:
autocheck autochk *
This is the entry which triggers autochk.exe and passes it the argument “*”, the command that smss.exe executes at the “Welcome” screen. Notice how only the executable name is mentioned, not the extension or the full path. This is because in order for a Native Application to be executed by smss.exe through BootExecute, it needs to be located in C:\Windows\System32
.
The information outlined here can also be found in this blog post by Protexity. Digging a little deeper though, I uncovered some interesting insights on the inner workings of smss.exe
, the BootExecute key as well as a new registry entry that functions similar to BootExecute, the BootExecuteNoPnpSync key.
Why target smss.exe
?
Native Applications executed by smss.exe
through BootExecute do so under the context of NT Authority\SYSTEM
, with a plethora of granted privileges and very early in the usermode initialization, before the userland components of EDR solutions are initialized. As a result, there are quite a few offensive opportunities to this implementation, provided that we have:
- a way to remotely edit the HKLM registry hive of a host
- a way to plant a binary to
\??\C:\Windows\System32
on the target host.
From an offensive-security standpoint, it was decided to focus on opening process and thread handles using the NT Authority\SYSTEM
process token as a POC.
Evitan – The Native Application backdoor
Building Native Applications with Visual Studio requires a couple of configuration steps. Stack cookies have to be disabled (/GS-) and intrinsics are a good idea (/Oi). As far as linking is concerned, the Native Application must be linked ONLY against ntdll.lib
and the Subsystem has to be set to Native.
Finally, the Native Application does not begin its execution at int main()
, but rather at void NTAPI NtProcessStartup(PPEB peb)
.
After writing a quick hello-world Native Application to test and verify the project skeleton, I decided on this main loop for the backdoor:
- Begin execution
- Create a synchronization event
- Allocate some shared memory for communication
- Wait until the event is set by the client process
- Read the shared memory to identify the client process ID and command
- Get a handle to the client process
- Execute the commands and duplicate any resulting handles to the client process
- Go to step 4
I then borrowed Pavel’s implementation of NativeRun and incorporated it into the project, to allow testing Native Applications without going through smss.exe
.
Fleshing out the above main loop looks a bit like the following:
On the client side, an application that requests elevation from the backdoor looks like this:
Too good to be true…
The issue
Although through NativeRun both the backdoor and the client operated as expected, when spawned through smss.exe
the backdoor resulted in hanging the “Welcome” screen. This was an immediate road block, since another limitation was added to the list; Native Apps executed through BootExecute need to terminate their execution for smss.exe
to continue its operations. Or… Do they really?
I decided to break out Ghidra and start looking around at the inner workings of smss.exe
. During its setup stages it reads the BootExecute key, along with other relevant information from the registry and then proceeds to call SmpExecuteCommand
for each member in the SmpBootExecuteList
:
SmpExecuteCommand
calls SmpParseCommandLine
and then passes the result to SmpExecuteImage
:
SmpExecuteImage
is responsible for executing a Native Application and holds the root cause of our issue, a WaitForSingleObject
call that expects our process to terminate:
Luckily enough, our problem lies within a branch, controlled by some flags:
By reversing SmpParseCommandLine
we find more information on the supported flags. As it turns out, the following flags are supported:
BOOTEXECUTE_ASYNC
BOOTEXECUTE_SECURE
BOOTEXECUTE_DEBUG
BOOTEXECUTE_AUTOCHECK
They are set by comparing the first token of each command (remember the syntax?) to some predefined values which are:
async
(Executes a Native Application without waiting for it to terminate)
secure
debug
(The execution attempt is logged but the application is not actually spawned)
autocheck
(Bingo, our magic string!)
The data references look as follows:
And finally…
Proof of Concept
After a quick modification of the BootExecute key, dropping the backdoor to \??\C:\Windows\System32\Evitan.exe
and a quick reboot,
we can execute EvitanClient.exe
, the POC client application that will request elevated actions, in this case terminating a system process:
The complete project files accompanying this blog post can be found at the HackCraft Github.
Why not use token impersonation?
When Evitan was initially fleshed out, its goal was taking advantage of SE_DEBUG_PRIVILEGE and SE_TCB_PRIVILEGE to open a handle to the backdoor client process, receive a target thread handle and duplicate the handle to its own process using the acquired backdoor client process thread handle. Soon after it would duplicate its process token the same way and set it to the impersonation token for the targeted thread. The problem with this approach is that duplicating token handles across session boundaries is unsupported and as a result, the idea of transferring privileges through the token was dropped in favor of transferring privileged resources by handle duplication.
Features
The final version of Evitan shared along with this blog post contains the following list of features:
- Elevated Process / Thread Termination
- Token Session ID Swapping
- Process Memory Dumping
More features/commands can be added easily, as required.
Disclaimer
This blog post and the accompanying project files are provided only as a POC and are not expected to be production-grade, bug-free code. Please take this into consideration before utilizing Evitan.
References