Have you ever wondered how an elevated process is created when you call ShellExecuteEx
with lpVerb
set to "runas"
? Well, I do.
[Hruska08] provides an excellent guide to understanding how ShellExecuteEx
works. Note that I wouldn’t be surprised if ShellExecuteEx
uses a lot of COM, as this has been explicitly implied by the documentation: Because ShellExecuteEx
can delegate execution to Shell extensions (data sources, context menu handlers, verb implementations) that are activated using Component Object Model (COM), COM should be initialized before ShellExecuteEx
is called. Some Shell extensions require the COM single-threaded apartment (STA) type [ShExEx]. In addition, you should read the TechNet article by Mark Russinovich [TechNet].
An idea struck my mind: what happens if the parent process is terminated before Consent UI returns? It’s rather simple to try this out. Fire up a PowerShell console, execute Start-Sleep 3; Start-Process notepad -Verb runas
, and lock your computer immediately. After a few seconds, unlock the computer, and consent.exe will be minimized. Now use the Close button on the title bar to close PowerShell, go to consent.exe and approve the elevation. Ta-da! Nothing happens.
Okay, but why? Lauching the elevated process is as simple as calling CreateProcessAsUser
with the full token of that user. [TechNet] says that the elevated application is launched by Application Information Service (AIS), and that AIS uses a new capability of CreateProcessAsUser
to set the “correct” parent (though AIS is technically the parent of the elevated process). Setting the parent of the newly created process to a non-existent could be the problem.
However, if you fire up two instances of PowerShell (say PS1 and PS2), in PS1, you execute $pid
and see its process ID (say 1234), and in PS2, you perform $proc = Get-Process -Id 1234
to keep a handle to PS1 open. Then you Start-Sleep 3; Start-Process notepad -Verb runas
, lock and unlock the computer, close PS1 and approve the consent. This time Notepad is launched smoothly, despite it gets orphaned as soon as it is created, as can be verified in Process Explorer [procexp].
If there is an outstanding handle to the process object of the parent process, launching is successful. Since setting the parent of a new process needs a handle to the new process, as documented in [UPTA], it is reasonable to guess that AIS is calling OpenProcess
on the process ID of the parent process. The process can still be opened via its process ID even if it has been terminated [Chen11].
Now comes an evil idea: what if I terminate the original parent process and open another process with the same process ID before consent is given? Try the following code:
# In one instance of PowerShell
$pid # Say the value is 1234
# In another instance of PowerShell
# Note, you must NOT keep a handle to
# 1234 in this instance!
while ($true)
{
$proc = Start-Process powershell -PassThru
if ($proc.Id -eq 1234)
{
break;
}
$proc.Kill()
}
Write-Host 'Found'
# Again in the first instance of PowerShell
Start-Sleep 3; Start-Process notepad -Verb runas
# After the above line starts executing,
# lock and unlock the computer, then close
# this instance of PowerShell, wait until
# the other instance shows 'Found', and
# give the consent.
Interestingly enough, the second instance of PowerShell seems to hang indefinitely. This surely confused me for a while. I opened Process Explorer [procexp], trying to find out what happened. It turns out that AIS (the parent svchost.exe of consent.exe) is holding a handle to the zombie PowerShell process open!
Dangerous action here! If you try out what is described in this paragraph, restart your computer afterwards to reduce the risk of running a corrupted system. Out of curiosity, I forcibly closed that process handle inside svchost.exe, waited until the second instance found a colliding process ID and gave the consent. The subprocess is not created.
It is reasonable to conclude that AIS holds a handle to the parent process open when it fires up consent.exe, but afterwards, it closes the handle and then reopens a handle to the process via its process ID (strange enough, huh?) before using it to set the parent of the elevated process. If either closing or reopening fails, the procedure fails.
It is still a mystery whether a wrong parent will be attached to the newly created process if a process with the same PID sneaks in during the CloseHandle
-OpenProcess
window.
I’d say it would be better if AIS opened another handle before closing the currently held one. Heck! Why bother closing and reopening? This way, the sneak-in window is fully closed, the subprocess can be created even when the parent process is gone, and the behaviour is not affected by the external state whether another handle to the parent process is held open. Note that the case that the handle opened by AIS is forcibly closed need not be considered, as one needs administrative privileges to do that — it rather involved being on the other side of this airtight hatchway [Chen14].
Disclaimer: The information in this blog entry is purely experimental and for entertainment, and it should not be used as a serious reference. You use the information at your own risk. Some references listed below are for entertainment only.
References
↩[Hruska08] Thomas Hruska, The Nitty Gritty, Vista UAC: The Definitive Guide, 2008, retrieved on 15 June 2018.
↩[ShExEx] ShellExecuteEx
function, Windows Shell Reference, MSDN, retrieved on 15 June 2018.
↩↩[TechNet] Mark Russinovich, Security: Inside Windows Vista User Account Control, TechNet Magazine, June 2007, retrieved on 15 June 2018.
↩↩[procexp] Mark Russinovich, Process Explorer, Sysinternals, 16 May 2017, retrieved on 15 June 2018.
↩[UPTA] UpdateProcThreadAttribute
function, Windows Process and Thread Reference, MSDN, retrieved on 15 June 2018.
↩[Chen11] Raymond Chen, When does a process ID become available for reuse?, The Old New Thing, 7 January 2011, retrieved on 15 June 2018.
↩[Chen14] Raymond Chen, It rather involved being on the other side of this airtight hatchway: Surreptitious file access by administrator, The Old New Thing, 3 July 2014, retrieved on 15 June 2018.
Please enable JavaScript to view the comments powered by Disqus.