[Misc Series #5] How Windows CMD and PowerShell Execute a File?
This blog will note down some mechanism that Windows implemented to select an application to open a file. Both cmd.exe
and powershell.exe
were tested in this case.
Let’s try this by using an executable file named calc.exe
which has renamed into calc.pdf
. During the experiment, cmd.exe able to run the executable regardless of the file extension name. However, powershell.exe will select default application that associated with the file extension name to open the file.
Command used in the experiment:
PS> calc.pdf (Result: Launch MSEdgePDF)
CMD> calc.pdf (Result: Pop calc.exe application)
General explanation on the behavior:
- In
powershell.exe
, the file extension name will be extracted and compared it within the user registry to search for default application to open a file.CreateProcessW
will execute later based on the application command found in registry. - In
cmd.exe
, windows directly first execute the input file viaCreateProcessW
before performing any registry search to look for default application to open the file. There are MZ byte check within the kernel code ofCreateProcessW
and this is why we can run any executable file regardless of the file extension.
Notes for file that executed via powershell.exe
Most of the relevant functions were found in windows.storage.dll
, at least for user mode code.
The extracted extension name will fill up the %s
from registry subkey HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\%s\UserChoice
. It will then be HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice
. Registry value ProgId
contains the default application for pdf
file. In this case, it is MSEdgePDF
.
After retrieve the ProgId
value, MSEdgePDF
, its command value will retrieve from registry key Computer\HKEY_CLASSES_ROOT\%s\shell\open\command
. The %s replaced with MSEdgePDF
and becomes Computer\HKEY_CLASSES_ROOT\MSEdgePDF\shell\open\command
.
Once retrieved the application command from the registry key above, it will fill up any remaining placeholder value and CreateProcessW
will be trigger the open the file passed into the parameter.
Notes for file that executed via cmd.exe
In normal case, if NtCreateUserProcess
able to create a process in suspended mode
, it will break the while loop and continue with image section creation (Based on my roughly check in the function itself) then followed by NtResumeThread
to continue with the execution of new process.
If the input file passed to in the function CreateProcessW
return error code (0xC000012F, STATUS_INVALID_IMAGE_NOT_MZ), It will do some handling before performing the registry check to find associated application to open the file as mentioned in the method above.
To find out where does the 0xC000012F
error code comes from, I did some tracing within NtCreateUserProcess
🕵️
As the error code mentioned something related with MZ header. I will be using an executable file (In my case is calc.exe) that starts with NZ
instead of MZ
. So that I can trigger the error and trace it accordingly.
After some tracing was done, error code 0xC000012F
can found in MiCreateImageFileMap
🥲😌
Here is the flow to get into the MiCreateImageFileMap
function.
nt!NtCreateUserProcess -> MmCreateSpecialImageSection -> MiCreateSection -> MiCreateImageOrDataSection -> MiCreateNewSection -> MiCreateImageFileMap
As you can the code snippet in MiCreateImageFileMap
, there is a first two bytes check for MZ
strings. If the first two bytes of the input file doesn’t match the condition, it will return with the error code, 0xC000012F
which is matched with the error code found in the CreateProcessW
in user mode.
By using the malformed file, we are able to identify the code which perform the validation for windows executable file😋. This trick is quite useful when you want to determine something in a code where you don’t have much understanding on it✌️
Conclusion
It’s a “boring” investigation, but now it is good to know that you can run executable file with any extension name in cmd.exe but not powershell.exe 🤣