[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

How to detect if a file is executable on Windows?

On 2/19/19, Chris Angelico <rosuav at gmail.com> wrote:
> I guess you have to define the question better for Windows, since
> there's no single definition of "executable". If you mean "typing just
> the base name of this file at the shell will result in it being run",
> then PATHEXT is the correct answer. If you mean "this thing is

PATHEXT is the list of  extensions that the shell appends
automatically when searching PATH. Otherwise it has nothing to do with
executability. I think older versions of PowerShell confused this. But
I just checked PowerShell in Windows 10, and I can run a .txt file
found in PATH without requiring .TXT in PATHEXT so long as I search
for the name including the extension. CMD has worked like this

Where CMD and PowerShell differ is with respect to execute access. In
CMD, when CreateProcess fails due to access denied, it gives up
instead of trying ShellExecuteEx. PowerShell tries ShellExecuteEx even
if CreateProcess fails with access denied. What CMD does makes more
sense to me. But neither is as sensible as a POSIX shell, which
continues searching PATH if a match doesn't have execute access.

> actually inherently executable", then you probably want to check if it
> begins MZ, but that's not certain (COM files still seem to be
> supported, and they have no header whatsoever). If you mean

PE executables can have any extension. CreateProcess doesn't care, but
when searching for the executable, it only tries appending .EXE if we
omit the extension. In other words, if we run "spam", it will find
"spam.exe" but not "spam.com". Note that the extensions in PATHEXT are
a high-level shell feature, as discussed above. This variable isn't
used by the base Windows API.

Windows comes with a few programs that use the .COM extension for
backward compatibility: chcp.com, format.com, mode.com, more.com, and
tree.com. In 64-bit Windows, these are 64-bit PE executables. Actual
16-bit MS-DOS files cannot run in vanilla 64-bit Windows since the NT
Virtual DOS Machine (ntvdm.exe) isn't supported.

If we want to know whether CreateProcess will succeed without actually
calling it, then we need to check for execute access via AccessCheck,
which requires the security descriptor from GetNamedSecurityInfo
(OWNER, DACL and LABEL info) and current access token from
OpenProcessToken, or OpenThreadToken if impersonating. Call
GetTokenInformation on the token to determine whether we have admin
access, which may be required. Verify that it's a valid image via
GetBinaryType. Once we know we have a PE image, map it as a data file
via LoadLibraryEx and get the embedded manifest via
FindResource/LoadResource in order to determine whether admin access
is required (i.e. requestedExecutionLevel, requireAdministrator). If
there's no embedded manifest, check for one beside it named
<exename>.manifest, e.g. "spam.exe.,manifest".

Or we can simply call CreateProcess with the flag CREATE_SUSPENDED
(4). Let the OS do all of the work for us. If it succeeds, call
TerminateProcess, and close the process and thread handles.

> "double-clicking this thing will run it", I think there are tools that
> allow you to do the registry lookup conveniently to see if something's
> associated.

Call AssocQueryString to have the shell comb through the rat's nest of
registry definitions to determine the ASSOCSTR_COMMAND template.
(Whatever we think we know about the shell's use of the registry, we
probably don't know the half of it. It is a nightmare.) Use the flag
ASSOCF_INIT_IGNOREUNKNOWN to prevent returning the "unknown" progid
that runs openwith.exe. We can split the template into the
CreateProcess parameters lpApplicationName and lpCommandLine via
SHEvaluateSystemCommandTemplate. We still have to implement our own
template parameter substitution for the "%1" (i.e. "%l" or "%L")
target and the %* remaining command-line arguments.